What a PDF metadata strip has to remove
“I am adding a remove-metadata option to a PDF tool and need it to actually take the metadata out of the file, not just out of the fields a viewer shows.”
Removing a PDF's metadata is a feature people are adding to their tools right now, and the
specifications read alike. One
starts from the observation that "Compression rewrites the PDF, but the original title, author,
and other document info can ride along", and proposes an option that will "clear these Info
fields on the compressed file: Title, Author, Subject, Keywords, Creator."
Another sets out to "Read common PDF
metadata: title, author, subject, keywords, creator, producer and creation/modification dates
where present", and its acceptance test is that the "Cleaned output opens normally and no longer
contains the cleared fields."
Both are reasonable specifications, and both are written as a list of fields. This post is about what that list leaves out. There are two separate ways a strip can leave the metadata in the file, and neither one is caught by opening the output and checking the fields. (Nothing here is a claim about how either project implemented its feature. Only the issues were read.)
A PDF keeps metadata in two places
pypdf's metadata guide states it in its first line:
PDF files can have two types of metadata: "Regular" and XMP ones. They can both exist at the same time.
The "regular" one is the document information dictionary, reached through the file's trailer as
/Info. It holds the familiar fields: title, author, subject, keywords, creator, producer, and
the creation and modification dates. Every field in the two specifications above is one of its
keys.
The other is XMP, an XML packet stored as a separate object and referenced from the document catalog. It carries the same information and a good deal more, and it is where the standard went. pikepdf's metadata guide describes the older store this way:
DocumentInfo, which is deprecated and removed as of PDF 2.0, but still relevant.
That is the direction of travel rather than the whole story. ExifTool's PDF module
(PDF.pm,
lines 124 to 150) marks CreationDate and ModDate "not deprecated in PDF 2.0", and warns about
the other information-dictionary entries in a 2.0 file rather than refusing them. Either way, a
file can carry both stores, and plenty of files in circulation do.
The two are meant to agree. pikepdf again, on why its own interface writes both at once:
For backward compatibility, both should contain the same content.
Nothing in the file format holds them to that. They agree because a writer took the trouble, and they stop agreeing the moment something edits one of them on its own. pikepdf's guide carries a warning to that effect about its own API: manual changes to the XMP stream object "will not be synchronized with live PdfMetadata object or the DocumentInfo block."
So a field list built from /Info keys describes one store. The author's name can be sitting in
the other.
The common recipe clears one of them
Here is the whole of pypdf's "Removing metadata entry" section, which is what you find when you look up how to take a PDF's metadata off:
from pypdf import PdfWriter
writer = PdfWriter("example.pdf")
# Remove Metadata (/Info entry)
writer.metadata = None
# Save the new PDF to a file
writer.write("out-meta-remove.pdf")
Read the comment. It says /Info entry, and that is precisely what it removes. The setter's own
docstring in
_writer.py
(lines 1588 to 1590) is explicit that XMP is out of its reach:
Note that some PDF files use (XMP) metadata streams instead of document information dictionaries, and these metadata streams will not be accessed by this function, but by :meth:
~xmp_metadata.
The code is not wrong and the documentation is not misleading. The gap is between "remove the
metadata", which is the feature, and "the /Info entry", which is the operation.
Some tools remove by adding
ExifTool is a long-standing metadata tool that handles PDF among many other formats. Its PDF documentation explains how it writes, and it is worth reading to the end:
ExifTool may be used to write native PDF and XMP metadata to PDF files. It uses an incremental update technique that has the advantages of being both fast and reversible. If ExifTool was used to modify a PDF file, the original may be recovered by deleting the PDF-update pseudo-group (with -PDF-update:all= on the command line).
Reversible. The same page then names the consequence itself, as the second of two disadvantages:
All metadata edits are reversible. While this would normally be considered an advantage, it is a potential security problem because old information is never actually deleted from the file.
An incremental update is a legitimate way to save a PDF: instead of rewriting the file, you append your changed objects and a new cross-reference section to the end of it. The appended objects take precedence over the earlier ones with the same numbers, so a reader opening the file sees the new values. The old objects are still there. They are simply no longer the ones anything points at.
That is what "the original may be recovered" means. It is recoverable because it was never removed.
ExifTool does say so at the time. Its writing code (line 750) carries this line, which fires when a PDF or XMP metadata group is deleted:
$et->Warn('ExifTool PDF edits are reversible. Deleted tags may be recovered!', 1);
That is a warning, printed by a step that is often one line inside a larger pipeline whose output nobody reads.
Why reading the file back proves nothing
The natural acceptance test is to open the result and check the fields. Both failures pass it.
If the strip cleared /Info and left XMP, anything that reports the document information
dictionary reports an empty one, correctly.
If the removal was appended, every reader resolves the newest revision, because that is what the format tells it to do. The tool that wrote the update will read it back and report the fields as gone. It is right about what the document now says and silent about what the file still contains.
File size gives a hint, not a verdict. An appended removal makes the file larger, because appending cannot shrink it. But a full rewrite can also come out larger, for instance if it writes streams with different compression, so a bigger output is a reason to look, not proof that anything was left behind.
What a strip has to do
- Clear both stores.
/Infofrom the trailer and the/MetadataXMP packet from the catalog, in the same operation. In pypdf that is two properties,metadataandxmp_metadata. A field list in a specification should name both. - Rewrite the file rather than updating it. ExifTool's own documentation names the fix in the same paragraph as the problem: "qpdf --linearize in.pdf out.pdf". This works because a full rewrite walks the document from its current trailer and writes out what it reaches. qpdf's manual puts it plainly: "Ordinarily any object that is not referenced in a traversal of the document from the trailer dictionary will be discarded." The superseded objects are not reachable, so they are not written.
- Test the bytes, not the fields, and know each test's reach. A PDF library answers for the
newest revision, so asking it what the fields say cannot see an appended update. A raw search
of the output for the strings you removed, the author's name for one, can, but it misses text
inside compressed streams. qpdf's
--qdfmode writes a copy in which "all streams that can be uncompressed are uncompressed", and its manual suggests adding--object-streams=disablewhen inspecting; searching that copy finds what the current document holds in either store. It is a rewrite, though, so it also drops the superseded objects. Use the uncompressed copy for the two stores, and the raw bytes, with the same blind spot for compressed streams, for earlier revisions. - Count revisions carefully, if you count them at all. Each incremental update appends its
own
%%EOFmarker, so extra markers usually mean earlier saved versions. But a linearized file (one with a/Linearizeddictionary at the start, as qpdf's recommended rewrite produces) is built with a first cross-reference table and trailer near the front of the file as well as the main one at the end. qpdf's linearization notes refer to "the first cross reference table" and the "/Prev key of the first trailer dictionary". So a single, clean linearized revision can carry two markers. The IRS Form W-9 PDF, measured for this post, is linearized and has three: one at byte 496 that closes the front section, one that closes the main body, and one appended update. Counting markers and subtracting one would report two earlier versions where there is one. Check for/Linearizedfirst, or follow the trailer chain, before calling a count a revision count. - Know what metadata removal is not. It does not touch page content, annotations and reviewer comments, embedded file attachments, or the values sitting in form fields. Those are separate places, and each one has to be dealt with on its own.
Where AegisPDF fits
AegisPDF is our browser-based PDF tool. The document is never uploaded, which is the property that matters when the file you are cleaning is the one you cannot send to a web converter in the first place.
Its Sanitize pass was checked against the engine source for this post, and on the two failures above it does the following.
- It removes both stores. When the metadata option is on, the pass removes
/Infofrom the trailer and/Metadatafrom the catalog in the same operation. - It rewrites rather than appends. The pass drops every object nothing references any more
and saves the document as a single revision. The engine's own test asserts exactly one
%%EOFin the output. - The sanitize pass fails closed. If the file cannot be parsed or written back, the pass stops with an error. It does not hand back the original bytes as if they were cleaned.
Inspecting a file is free and changes nothing; applying the removal is the paid part. And one limit, which the app states in its interface when it detects invisible text in a file: sanitizing does not remove page text. Text that is invisible because it sits under an image is page content, and removing it is what redaction is for.
What this post covers, and what it does not
Every claim about pypdf, pikepdf, ExifTool and qpdf comes from those projects' own documentation and source, fetched as raw files at pinned release tags (pypdf 6.19.0, pikepdf v10.16.0, ExifTool 13.59, qpdf v12.4.2) on 1 October 2026 and quoted above. None of the four was run, so what you have read is what the projects document about themselves, not a measurement of them. The one measurement is the marker count in the W-9 file, made by reading its bytes. Behaviour may differ in other versions. The two feature requests quoted at the top were read as issues; neither project's code was.
The AegisPDF claims were checked against the engine source, not our own marketing copy. The post does not cover encrypted PDFs, PDF/A, metadata attached to individual pages or images rather than to the document, or what any particular PDF viewer chooses to display.