DEV Community

PDFnoted
PDFnoted

Posted on Edited on

PDF metadata: the fields you didn't know you were publishing

A while ago someone sent me a "cleaned" contract to look at. They had removed the client name from the body text, double-checked every page, and felt good about it.

Then I ran pdfinfo on it.

Title:          Acme Corp - MSA v2
Author:         J. Rivera
Creator:        Microsoft® Word for Microsoft 365
Producer:       Microsoft® Word for Microsoft 365
CreationDate:   Wed Sep 17 14:22:01 2025
ModDate:        Mon Oct  6 09:41:33 2025
Enter fullscreen mode Exit fullscreen mode

The client name was still there — in the title field, which almost nobody thinks to check. So was the author's name, the exact software used, and two timestamps that revealed how long the negotiation had dragged on.

Nothing in the document body was wrong. The file itself was talking.

Where metadata actually lives

There are two places, and this trips people up constantly:

  1. The Info dictionary — the old-school /Info object. Author, Title, Subject, Keywords, Creator, Producer, dates.
  2. XMP — an XML packet embedded as a metadata stream. Added by Adobe products and most modern exporters, and it can contain a different set of values than the Info dictionary.

If you clear one and not the other, you've half-cleaned the file. I've seen tools that only touch /Info and leave XMP completely intact, which means the author name survives.

There's also the document's /ID — a pair of hashes generated at creation. It's not personally identifying on its own, but it's stable across saves, which makes it useful for tracking a specific file. If you've ever wondered how a leaked document gets traced back to a source, this is usually the mechanism.

Reading it

Command line (poppler):

pdfinfo contract.pdf
pdfinfo -meta contract.pdf   # dumps the XMP too
Enter fullscreen mode Exit fullscreen mode

Or exiftool, which shows both stores:

exiftool -a -G1 contract.pdf
Enter fullscreen mode Exit fullscreen mode

In Python:

from pypdf import PdfReader

r = PdfReader("contract.pdf")
print(r.metadata)          # Info dictionary
print(r.xmp_metadata)      # XMP packet, may be None
Enter fullscreen mode Exit fullscreen mode

In JavaScript, pdf-lib doesn't surface XMP directly, but reading the raw PDF bytes and grepping for the XMP header works well enough as a check:

const fs = require("fs");
const buf = fs.readFileSync("contract.pdf");
const start = buf.indexOf(Buffer.from("<?xpacket"));
const end = buf.indexOf(Buffer.from("<?xpacket end"));

if (start !== -1) {
  console.log(buf.slice(start, end + 20).toString("utf8"));
} else {
  console.log("no XMP packet found");
}
Enter fullscreen mode Exit fullscreen mode

Clearing it

Python, both stores:

from pypdf import PdfReader, PdfWriter

r = PdfReader("contract.pdf")
w = PdfWriter()
w.append_pages_from_reader(r)
w.add_metadata({"/Title": "", "/Author": "", "/Subject": "",
                "/Keywords": "", "/Creator": "", "/Producer": ""})
# pypdf 不写 XMP,追加页面等于丢弃旧 XMP 包
with open("clean.pdf", "wb") as f:
    w.write(f)
Enter fullscreen mode Exit fullscreen mode

Note what that does: rebuilding the file from its pages drops the old XMP packet, because you're writing a new document rather than patching the old one. That's the reliable way to get both stores.

Command line alternative:

qpdf --empty --pages contract.pdf 1-z -- clean.pdf
Enter fullscreen mode Exit fullscreen mode

Same principle — rebuild rather than patch.

The part people get wrong

Clearing metadata does not clean the document. Two things metadata removal will never fix:

Text you "deleted" but didn't. White-on-white text, text under an image, text covered by a rectangle. All still in the content stream. Metadata tools do not touch this.

Previous versions of the file. PDFs can carry incremental updates, so an earlier draft can still be in there. Rebuilding the file (as above) usually drops this too, which is a nice side effect.

So after cleaning, always verify against the content, not the properties:

pdftotext clean.pdf - | grep -i "acme\|confidential\|draft"
Enter fullscreen mode Exit fullscreen mode

If that comes back empty, and pdfinfo clean.pdf shows no author, then the file is clean.

Why this is worth two minutes

Metadata leaks are the ones nobody notices, because there's no visual cue. You open the file, it looks fine, you send it. The recipient — or anyone who ends up with it later — can read your author name, your software versions, your working hours, and often your client's name.

For internal documents it's untidy. For anything crossing a border, or going to a counterparty, or landing in a public filing, it's the kind of thing that's very cheap to prevent and very awkward to explain afterward.

The whole check is two commands. Worth making it a habit.

Top comments (0)