Skip to content

Add Metadata To An Existing PDF

Open the document bytes with Recipe and set standard fields and custom Info dictionary keys with info(options), or use custom(key, value) for an explicit custom entry. Both are written when endPDF() returns the finished document.

import { createRecipe } from "@muhammara/wasm";

var Recipe = await createRecipe();
var pdf = new Recipe(inputBytes);

var outputBytes = pdf
  .info({
    author: "Example Clinic",
    title: "Prescription",
    subject: "Issued 2026-03-01",
    keywords: ["prescription", "signed"],
    ReportId: "X-123",
  })
  .custom("2.16.76.1.4.2.2.1", "oid-professional")
  .custom("2.16.76.1.4.2.2.2", "oid-uf-professional")
  .custom("2.16.76.1.12.1.1", "oid-document")
  .endPDF();

info() with no arguments returns the current metadata, which is how you read the values back:

var metadata = new Recipe(outputBytes).info();

console.log(metadata["2.16.76.1.4.2.2.1"]); // "oid-professional"

info covers author, title, subject, and keywords; an array of keywords is joined into a single Info value. Every other key is added as a custom Info entry, so custom(key, value) is shorthand for info({ [key]: value }). As on native, info({ ReportId: "X-123" }) and custom("ReportId", "X-123") write the same entry. Custom array values passed to info are joined with ", "; other values are converted to strings. When both methods set the same custom key, the last call wins. Constructor options still only initialize standard metadata; pass custom keys to info or custom. Any name the PDF Info dictionary accepts works, including dotted OID strings, so identifiers such as 2.16.76.1.4.2.2.1 need no escaping.

What The Round Trip Changes

Three behaviours are worth knowing before you rely on custom entries.

Keys come back lower-cased from a reopened document. While the Recipe is open, info() reports the names you supplied. Read back from the produced bytes they are normalized, so ReportId returns as reportid. Numeric OID keys are unaffected because they contain no letters.

Custom entries do not survive the next modification. Recipe carries the standard fields of a source document into its output, but not custom Info entries, so a second editing pass drops them. Re-apply custom entries with info or custom each time you rewrite a document that must keep them.

Recipe stamps its own provenance. Producer and Creator are always set to MuhammaraJS values, and the source document's originals are preserved as source-Producer, source-Creator, and source-ModDate. ModDate is set to the time of the edit.

These entries live in the document Info dictionary. Writing XMP metadata is a separate mechanism and is not exposed by Recipe.