Flattening a PDF merges form fields, annotations, comments, and signature appearances into the static page content, so the result behaves like a printed page rather than an interactive document. Use the PDF4.dev flatten PDF tool for a free browser-based flatten in under 10 seconds, or the code examples below for batch automation in Node.js, Python, and the command line.
What flattening a PDF actually does
Flattening a PDF is the process of converting interactive PDF objects into part of the static page content stream. Before flattening, a PDF contains an AcroForm dictionary listing every form widget and an Annots array on each page describing every comment, highlight, stamp, or signature appearance. After flattening, those objects are gone: the visual result of each widget is drawn directly onto the page using vector text and shape operators.
The technical effect is described in the PDF 1.7 specification (ISO 32000-1), section 12.7 (Interactive Forms). Form widgets are defined as a subtype of annotations. Flattening removes both the widget annotation and its parent field entry, drawing the widget's current appearance stream onto the page content stream instead.
In practical terms: a filled-in tax form, after flattening, becomes a non-editable document where the values look identical but cannot be tabbed through, clicked, or changed. The form is now a picture of itself.
Why flatten a PDF? The three main reasons
There are three concrete cases where flattening is the right answer.
Locking submitted form data. When a user fills a fillable PDF and emails it back, the form values live in the AcroForm dictionary, not the page content. Any other Acrobat user can re-open the file and change those values without leaving an audit trail. Flattening bakes the values into the page so they cannot be silently edited.
Guaranteeing consistent rendering across viewers. Different PDF viewers render annotations and form widgets inconsistently. Adobe Acrobat displays them using the appearance stream embedded in the widget. Mobile readers, Apple Preview, browser-native PDF viewers (Chrome, Edge), and many SaaS preview iframes either ignore appearance streams, render them with a different default font, or hide them entirely. Flattening removes the variable: everyone sees the same pixels because the widget is no longer interactive.
Preparing files for print. Print drivers ignore non-printing annotations by default. Sticky notes, comments, and review markups will not appear on paper unless they are flattened into the page. The PDF/A archival format (ISO 19005) also forbids most types of annotations, so flattening is a common preprocessing step before converting to PDF/A. See the PDF/A compliance guide for the full conversion pipeline.
The quick way: free online flatten tool
The PDF4.dev flatten PDF tool flattens any PDF in your browser in under 10 seconds. Files are processed locally with pdf-lib and never uploaded to a server, which matters for confidential documents like signed contracts, NDAs, or internal forms.
Steps:
- Open pdf4.dev/tools/flatten-pdf
- Drag your PDF onto the upload zone (or click to pick a file)
- Click "Flatten PDF"
- Download the flattened result
The tool removes form fields, widget annotations, and most markup annotations. Layers (OCGs) and digital signatures need a different approach, covered later in this article.
Flatten a PDF in Node.js with pdf-lib
pdf-lib is a pure JavaScript library for reading and writing PDFs without any native dependencies or browser binary. It exposes a form.flatten() method that handles AcroForm widgets in one call.
import { PDFDocument } from "pdf-lib";
import { readFileSync, writeFileSync } from "fs";
async function flattenPdf(inputPath: string, outputPath: string) {
const bytes = readFileSync(inputPath);
const doc = await PDFDocument.load(bytes, { ignoreEncryption: true });
try {
const form = doc.getForm();
form.flatten();
} catch {
// No form fields present — still re-save to clean the document
}
const flattenedBytes = await doc.save();
writeFileSync(outputPath, flattenedBytes);
console.log(`Flattened ${inputPath} → ${outputPath}`);
}
flattenPdf("filled-form.pdf", "flattened.pdf");Install pdf-lib:
npm install pdf-libThe form.flatten() call iterates every AcroForm field, draws its current appearance stream onto the page content, and removes the widget annotation. The ignoreEncryption: true option lets pdf-lib open password-removed PDFs that still carry an encryption dictionary. If your PDF is still encrypted, remove the password first.
One caveat: pdf-lib only flattens AcroForm widgets and basic annotations. Markup annotations like highlights and free text are not always merged into the content stream. For those, use PyMuPDF or Ghostscript below.
Flatten a PDF in Python
Python has three reliable approaches. PyMuPDF is the fastest and most thorough, pypdf is pure Python with no system dependencies, and pdfrw works for older Python 2 codebases.
import pymupdf # pip install pymupdf
def flatten_pdf(input_path: str, output_path: str) -> None:
doc = pymupdf.open(input_path)
for page in doc:
# Bake widget appearances into page content
for widget in page.widgets() or []:
widget.update()
# Convert annotations into static content
for annot in page.annots() or []:
page.apply_redactions()
annot.update()
# garbage=4 removes orphaned objects, deflate=True compresses streams
doc.save(output_path, garbage=4, deflate=True, clean=True)
doc.close()
print(f"Flattened → {output_path}")
flatten_pdf("filled-form.pdf", "flattened.pdf")Install dependencies:
# PyMuPDF (fastest, handles annotations and widgets)
pip install pymupdf
# pypdf (pure Python, no native binaries)
pip install pypdfPyMuPDF (also known as fitz) is built on the MuPDF C library and handles a wider range of annotation types than pypdf. It also produces smaller output files thanks to the garbage=4, deflate=True, clean=True save options, which prune orphaned objects and recompress every content stream.
Flatten a PDF on the command line
Two command-line tools handle batch flattening reliably: Ghostscript and qpdf. Both work on Linux, macOS, and Windows, both are open source, and both are scriptable.
Ghostscript
Ghostscript re-distills the PDF through its pdfwrite device, which forces every annotation and form widget to be rendered into the page content stream as part of the conversion.
gs -dNOPAUSE -dBATCH -dSAFER \
-sDEVICE=pdfwrite \
-dPrinted=true \
-sOutputFile=flattened.pdf \
filled-form.pdfThe -dPrinted=true flag tells Ghostscript to render each annotation in its print appearance, which is what flattening simulates. For best results on text-heavy forms, add -dPDFSETTINGS=/prepress to preserve embedded fonts at full resolution.
Ghostscript is the most thorough flattener because it re-creates the entire PDF content stream from scratch. It handles AcroForms, XFA forms, annotations, and even some layer (OCG) configurations. The trade-off is that it can re-encode embedded images, which may slightly increase or decrease file size depending on the source.
qpdf
qpdf is a structural PDF tool that can flatten annotations without re-rendering the page content, which makes it faster than Ghostscript and preserves the original byte-level fidelity of the page.
qpdf --flatten-annotations=all filled-form.pdf flattened.pdfThe --flatten-annotations=all flag merges every annotation appearance into the page content stream. Other options:
| Flag | Effect |
|---|---|
--flatten-annotations=all | Flatten every annotation |
--flatten-annotations=print | Only flatten annotations marked as printable |
--flatten-annotations=screen | Only flatten annotations marked as screen-only |
--generate-appearances | Regenerate widget appearances before flattening (use for forms filled by tools that did not draw their own) |
For form widgets specifically, combine --generate-appearances with --flatten-annotations=all so qpdf draws the current values before merging them.
qpdf --generate-appearances --flatten-annotations=all filled-form.pdf flattened.pdfThis pair of flags is the most reliable command-line approach for flattening filled forms.
Flatten methods compared
Different flattening approaches handle different PDF features. Pick the right tool for what you actually need to remove.
| Tool | Form widgets | Markup annotations | Digital signatures (visual) | Layers (OCG) | Speed | Output size |
|---|---|---|---|---|---|---|
| PDF4.dev flatten tool | Yes | Partial | Yes (breaks crypto) | No | Fast | Same or smaller |
| pdf-lib (Node.js) | Yes | Partial | Yes (breaks crypto) | No | Fast | Same or smaller |
| PyMuPDF (Python) | Yes | Yes | Yes (breaks crypto) | Partial | Very fast | Smaller |
| Ghostscript | Yes | Yes | Yes (breaks crypto) | Yes | Slow | Variable |
| qpdf | Yes | Yes | Yes (breaks crypto) | No | Very fast | Same |
Throughput numbers vary by document complexity and source PDF structure. Test on your own files before committing to a tool for batch processing.
For most automation pipelines, qpdf is the best command-line choice because it is fast, lossless, and scriptable. For Python codebases, PyMuPDF is faster than pdf-lib via Node and handles more annotation types. For Node.js workflows that already use pdf-lib, the built-in form.flatten() is enough for the common case of locking AcroForm values.
Flatten a PDF before sending it to the PDF4.dev API
If you generate filled forms through the PDF4.dev render API, the cleanest approach is to skip flattening entirely: render the final PDF directly from your HTML template with the values already inlined, so there is no AcroForm to flatten in the first place.
const response = await fetch("https://api.pdf4.dev/v1/render", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PDF4_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
template_id: "tax-form",
data: {
taxpayer_name: "Acme Corp",
tax_year: 2026,
total_due: "$12,400.00",
},
}),
});
const pdfBuffer = await response.arrayBuffer();The resulting PDF has no widgets to flatten because the values are part of the page content from the moment the document is rendered. This is faster than rendering a fillable form, filling it, then post-processing to flatten, and it produces a smaller file.
When the input is a third-party PDF you do not control, flatten it client-side with pdf-lib or via a worker that calls qpdf, then store the flattened version.
Batch flattening multiple PDFs
For folders containing hundreds or thousands of filled forms, loop over the files with a concurrency limit. The example below uses Node.js with p-limit to cap parallel processing at 4 files at a time, which keeps memory usage predictable on large batches.
import { readdirSync } from "fs";
import path from "path";
import pLimit from "p-limit"; // npm install p-limit
const limit = pLimit(4);
const inputDir = "./forms-in";
const outputDir = "./forms-flattened";
const files = readdirSync(inputDir).filter((f) => f.endsWith(".pdf"));
await Promise.all(
files.map((file) =>
limit(() =>
flattenPdf(
path.join(inputDir, file),
path.join(outputDir, file)
)
)
)
);
console.log(`Flattened ${files.length} files`);For very large batches (10,000 or more files) where memory is a concern, switch to qpdf via a shell loop. qpdf is a separate process per file, so the operating system handles cleanup automatically and there is no JavaScript heap to manage.
mkdir -p forms-flattened
for f in forms-in/*.pdf; do
qpdf --generate-appearances --flatten-annotations=all \
"$f" "forms-flattened/$(basename "$f")"
doneCommon issues when flattening
Form values disappear after flattening. This usually means the PDF contains widgets without appearance streams, which happens with forms filled by some browsers or by minimal PDF editors. Run qpdf with --generate-appearances first, or use PyMuPDF's widget.update() loop, to draw the current values before merging.
Digital signature breaks. Flattening always breaks cryptographic signature verification because it modifies the byte content the signature hashes. The visual signature stays in place, but Acrobat will mark the signature as invalid. If the signature must remain verifiable, do not flatten that PDF. Instead, protect it with a password to prevent edits without altering the byte content.
File size grew after flattening. Ghostscript can re-encode images at higher quality than the source, increasing the final size. Use qpdf or pdf-lib for byte-preserving flattening, or add -dPDFSETTINGS=/printer (instead of /prepress) to Ghostscript to lower the image quality target. For a deeper look, see how to compress a PDF without losing quality.
Annotations reappear in some viewers. Some PDF viewers ignore the absence of an Annots array on a page if the AcroForm dictionary in the document catalog still points at field references. After flattening, also delete the catalog's /AcroForm entry. The Python pypdf example above does this explicitly.
Layers still toggle on and off. Optional Content Groups (PDF layers) are not annotations, so most flatteners leave them alone. To flatten layers, run Ghostscript with -dPrinted=true, which forces every visible layer to be merged into the base content stream. After saving, the layer panel in Acrobat will be empty.
When to flatten vs when not to flatten
Flatten when the PDF will be archived, printed, sent to a counterparty as a final version, or rendered by a viewer you do not control (mobile, web, third-party software). Flatten when the goal is to lock in form data so it cannot be silently changed.
Do not flatten when the PDF is still being filled out, when the form is shared with multiple reviewers who need to add comments, when the document carries a digital signature you need to keep verifiable, or when the recipient needs to extract data from form fields programmatically.
For one-off flattening of a single document, the free online tool is the fastest path. For automated pipelines, pick qpdf or PyMuPDF based on the language you already use.
After flattening, the next common step is often adding a watermark for confidentiality or merging the flattened document with cover pages into a final delivery.
Start generating PDFs
Build PDF templates with a visual editor. Render them via API from any language in ~300ms.


