A PDF carries more than its text: the author's name, the app and version that made it, creation and edit dates, a second copy of all that in XMP, and document IDs. It can also hold the EXIF data of every photo placed in it, GPS included, plus digital signatures naming the signer, and Content Credentials. If you generate, collect or pass on PDFs in code, here's how to remove that in Python, and what the popular recipes miss.

We tested each recipe on four real PDFs: a Word export, a PDF with an iPhone photo in it, a PDF signed with Acrobat Sign, and an Adobe Express PDF with Content Credentials.

Do pypdf and pikepdf remove all PDF metadata?

Recipe Word PDF PDF with a photo Signed PDF Adobe Express PDF
pypdf: copy pages to a new PdfWriter Clean Photo's GPS and phone model left Signature gone, but bookmarks and form fields lost too Clean
pypdf: clone_from, then metadata = None XMP left: author, Word version, dates Photo's GPS left XMP and signature left XMP and Content Credentials left
pikepdf: delete /Info and the root /Metadata Clean Photo's GPS left Signature left Content Credentials left
The better pikepdf recipe below Clean Clean Signature left Content Credentials left
Our API Clean Clean Clean Clean

Three things catch people out:

  • writer.metadata = None in pypdf removes the document properties but not the XMP packet, which holds the same author, software and dates. Anything that reads XMP still sees them.
  • Copying pages into a new writer drops the document's structure along with its metadata. On the signed PDF, the bookmark and the form field disappeared too. pypdf also stamps its own name as the Producer.
  • Photos inside a PDF keep their own EXIF. No document-level recipe touches them. In our test PDF, every recipe but one left the iPhone model and the GPS position of the photo on page one.

How to remove metadata from a PDF in Python

This removes the document properties, every XMP packet in the file (the document's, pages' and images'), app-private data, and the metadata inside embedded JPEG photos. It saves a full rewrite, so the old values can't be recovered from an earlier version of the file.

import pikepdf

def strip_jpeg(data):
    """Drop a JPEG's metadata segments; keep JFIF, the colour profile and the image data."""
    out, pos = bytearray(data[:2]), 2
    while pos + 4 <= len(data) and data[pos] == 0xFF:
        marker = data[pos + 1]
        if marker == 0xDA:                                    # image data: keep the rest
            out += data[pos:]
            break
        end = pos + 2 + int.from_bytes(data[pos + 2:pos + 4], "big")
        seg = data[pos:end]
        is_meta = 0xE0 <= marker <= 0xEF or marker == 0xFE
        if not is_meta or seg[4:9] == b"JFIF\0" or seg[4:16] == b"ICC_PROFILE\0":
            out += seg
        pos = end
    return bytes(out)

def remove_pdf_metadata(src, dst):
    with pikepdf.open(src) as pdf:
        if "/Info" in pdf.trailer:
            del pdf.trailer.Info                              # author, software, dates
        for obj in pdf.objects:                               # every XMP packet and app data
            if isinstance(obj, (pikepdf.Dictionary, pikepdf.Stream)):
                for key in ("/Metadata", "/PieceInfo"):
                    if key in obj:
                        del obj[key]
            if isinstance(obj, pikepdf.Stream) and obj.get("/Filter") == "/DCTDecode":
                obj.write(strip_jpeg(obj.read_raw_bytes()), filter=pikepdf.Name.DCTDecode)
        pdf.save(dst)                                         # a full rewrite, not an update

On our files, it cleaned the Word PDF and the photo PDF completely. The embedded photo's pixels were identical, and qpdf reported all four outputs as valid. What it doesn't do:

  • Digital signatures stay, with the signer's certificate and signing date. Removing them means editing the form fields and page widgets that hold them.
  • Content Credentials (C2PA) stay. In a PDF they're an attached file plus per-image data, not metadata.
  • Photos in other formats (JPEG 2000, or images stored uncompressed) aren't covered; only JPEG streams are.
  • No check. It doesn't tell you whether anything identifying is left.

Is there an API to remove PDF metadata?

Our metadata removal API handles all of the above, and the same call works for images, video and RAW files:

import os, requests

API = "https://api.aimetadatacleaner.com"
HEADERS = {"Authorization": "Bearer " + os.environ["AMC_API_KEY"]}

def clean_pdf(path, out_path, keep_signature=False):
    with open(path, "rb") as f:
        r = requests.post(f"{API}/v1/clean", headers=HEADERS, files={"file": f},
                          data={"keep_signature": "true"} if keep_signature else {})
    report = r.json()
    if not r.ok:
        raise RuntimeError(report["error"]["message"])
    with open(out_path, "wb") as out:
        out.write(requests.get(report["download_url"]).content)
    return report

On the photo PDF it returned clean, removing "Dates and times" and "Photo metadata inside the PDF (EXIF, GPS, XMP)". Every cleaned PDF is opened again and checked before it's returned.

Signatures are removed by default, because they name the signer. Pass keep_signature=True to keep them. The report then says what that means: "It still names the signer and the signing date, and PDF readers will show it as invalid because cleaning changed the file."

The same thing with curl:

curl -X POST https://api.aimetadatacleaner.com/v1/clean \
  -H "Authorization: Bearer $AMC_API_KEY" \
  -F [email protected]

The API is part of the Business plan ($49 a month: 10,000 files, 1TB, files up to 2GB). Password-protected PDFs aren't supported yet. For a folder of PDFs, see our bulk PDF guide. Without code, our PDF cleaner does the same on the website.

pikepdf or an API: which should you use?

  • Your own PDFs, generated by your code: set the metadata you want when you create them, and use the pikepdf recipe as a final pass.
  • PDFs from outside (uploads, client documents, anything with photos or signatures): the API, or the pikepdf recipe plus your own handling of signatures and C2PA, plus a check.

Frequently asked questions

Does pypdf remove XMP metadata?

Not with writer.metadata = None. That clears the document information dictionary only. The XMP packet in the document catalogue stays unless you remove it too.

Does removing PDF metadata change the document?

No. Text, images, fonts, links and layout stay the same. Removing a signature removes the signature, not the signed content. Copying pages into a new pypdf writer is the exception: it loses bookmarks and form fields.

Can removed PDF metadata be recovered?

Only if the tool appended an update instead of rewriting the file, which ExifTool does on its own. pikepdf's save(), pypdf's write() and our API all write a new file.

Can you remove all metadata from a PDF?

Yes, but "all" is more than the document properties: XMP packets, app data, photos inside the PDF, digital signatures and Content Credentials. The pikepdf recipe above covers everything except signatures and Content Credentials; our API covers those too.