Compress PDF
Shrink any PDF file entirely in your browser — no signup, no upload, no watermarks. Pick lossless, balanced, or strong compression. Text and vector graphics stay selectable and searchable; embedded JPEG images are re-encoded only when you choose to. Sources cited.
How it works
A PDF is, in spec terms, a body of numbered objects (pages, fonts, images, content streams) plus a cross-reference table that tells a reader the byte offset of each object. Most of a modern PDF's bulk lives in two places: the raster images embedded as Image XObjects (ISO 32000-2 §8.9.5) and the uncompressed metadata around the page tree. This tool targets both, in two independent passes — and never touches the vector text or graphics that make a PDF text-selectable.
Pass one is lossless and runs at every level. The PDF is re-saved through pdf-lib with useObjectStreams: true. This activates PDF 1.5+ object streams (ISO 32000-2 §7.5.8) and compressed cross-reference streams (§7.5.7), which together pack dictionaries and small objects into Flate-compressed containers. The savings on a PDF that has never been optimised before are typically 5–15%; on one already written with object streams the savings are minimal.
Pass two runs only at the Balanced and Strong levels. The compressor walks every indirect object via context.enumerateIndirectObjects(), finds every Image XObject (/Subtype /Image) whose filter is /DCTDecode (a JPEG byte stream), and re-encodes it through the browser's Canvas API at the quality factor for the chosen level — 0.78 for Balanced and 0.55 for Strong. Each new JPEG replaces the stream contents only when the re-encoded bytes are actually smaller; otherwise the original image is kept intact. Existing alpha masks ( /SMask) and any private vendor dictionary keys are preserved across the swap.
For each input the compressor does these steps in order:
- Validate. Confirms the MIME type, a non-zero size, and a per-file cap of 100.0 MB. The first 1 KB is scanned for the literal
%PDF-n.mheader (§7.5.2), and the last 2 KB for the%%EOFterminator (§7.5.5). Files missing either are rejected with an explanation. - Estimate image count. A byte-stream scan counts occurrences of
/Subtype /Image(excluding the/ImageMaskand related names). This is fast and works on every uncompressed PDF. The exact count is reported after the walk, and the two are cross-checked — if they disagree by more than one, the page tells you which path was authoritative. - Restructure. pdf-lib re-saves the document with object streams and a compressed cross-reference. This pass alone is lossless and always runs.
- Re-encode JPEGs (Balanced/Strong only). Every JPEG Image XObject above 4.0 KB is decoded via
new Image().src = url, drawn onto a same-size offscreen canvas, and re-encoded viacanvas.toBlob("image/jpeg", q). Images that don't actually shrink are left untouched. - Honesty check. If the final byte size is greater than or equal to the source, the tool hands you back your original file unchanged and tells you so. Producing a larger "compressed" file is not a feature.
The merge is performed entirely client-side: the PDF bytes never leave your browser tab, and the pdf-lib library (~270 KB) is dynamic-imported only when you press Compress — so the initial page bundle stays small.
Worked examples
Compression levels at a glance
Frequently asked questions
Sources & references
- ISO 32000-2 — PDF 2.0 specification (file header §7.5.2, trailer §7.5.5, cross-reference streams §7.5.7, object streams §7.5.8, Image XObjects §8.9.5)
- pdf-lib — MIT-licensed pure-JavaScript PDF library used for the object-stream re-save and indirect-object walk
- pdf-lib source on GitHub (open-source, audited)
- MDN — HTMLCanvasElement.toBlob (used for in-browser JPEG re-encoding)
- W3C HTML 5.2 — canvas.toBlob() quality parameter specification
The PDF header and trailer validators were cross-checked against ISO 32000-2 on 2026-05-11. The JPEG re-encode path was exercised against pdf-lib's PDFRawStream API the same day, and the pdf-lib dependency is pinned in package.json and re-verified on each major-version bump.
Related tools
Comments & feedback
Spotted a bug or want an improvement? Tell us — our team reviews every comment, and good ideas get built. Comments are public and anonymous.
Found a bug, edge case, or want to suggest an improvement?
Email me at [email protected] — most fixes ship within 24 hours.