Rotate PDF pages
Rotate one page, a range, or every page of a PDF by 90°, 180°, or 270° — fully inside your browser, no signup, no upload. The page content stays byte-identical; only the page's /Rotate entry changes, so there is no quality loss.
How it works
A PDF page object stores its rotation in a single inheritable entry named /Rotate (ISO 32000-2 §7.7.3.3). The value must be a multiple of 90, interpreted clockwise in degrees, defaulting to 0 when the entry is absent. Rotating a page does not require touching the page's content stream — the text, vectors, and images stay byte-identical; PDF readers apply the angle when drawing. That is why a one-line metadata edit can re-orient a 200 MB scanned book in milliseconds.
The rotation is performed by pdf-lib, an MIT-licensed pure-JavaScript PDF library. It is loaded dynamically when you press Rotate — until then the page bundle stays small. Inside the React component the flow is: PDFDocument.load(bytes) parses the source, doc.getPages() yields the page handles, and for every selected index we read page.getRotation().angle, add your delta, normalise to (sum % 360 + 360) % 360, and write it back with page.setRotation(degrees(next)).
Before pdf-lib runs, the tool performs four deterministic checks so the plan can be validated up front:
- Validate the source. Confirms the PDF 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 (ISO 32000-2 §7.5.2), and the last 2 KB for the%%EOFterminator (§7.5.5). Files missing either are rejected with a specific message. - Read the exact page count. pdf-lib parses the catalog and page tree and reports
getPageCount(). That number is the ceiling for every page in your selection — out-of-bounds entries become inline errors before the rotation runs. - Build a page selection. "All pages" expands to
[1..total]. "Specific pages" parses a comma-separated expression — single pages, inclusive ranges, and open-ended ranges. The set is deduplicated and sorted so a page listed twice still rotates exactly once. - Cross-check the rotation arithmetic. At component mount the lookup table
applyRotationByTable[current][delta]is compared against the modulo formula for every (current, delta) pair in{0,90,180,270}² — twelve pairs in total. If any disagree the Rotate button is disabled. Two independent implementations agreeing is the verification floor: a bug in one is caught by the other.
For each selected page, the new rotation is (currentRotation + delta) mod 360. Pages outside the selection are not touched at all, so their /Rotateentry stays byte-identical and even round-trip-comparison tools see no diff. The output is serialised through pdf-lib's save() and offered as a Blob URL download.
Rotation is one step in a larger document-cleanup flow, and the sibling tools on this site follow the same in-browser, no-upload contract. If a scan arrived as separate files, stitch them with the PDF merger first, then rotate the combined file here. To pull a single mis-oriented page out before fixing it, use the PDF splitter. And because a /Rotate edit never re-rasterises the page, the output stays as small as the input — if you also need to shrink a heavy scan, the PDF compressor is the right next step rather than the rotator.
Worked examples
Frequently asked questions
Sources & references
- ISO 32000-2 — PDF 2.0 specification (file header §7.5.2, trailer §7.5.5, page tree §7.7.3.2, page /Rotate entry §7.7.3.3)
- pdf-lib — MIT-licensed pure-JavaScript PDF library used for setRotation / save
- pdf-lib source on GitHub (open-source, audited)
- MDN — File API (used for in-browser file reads, no upload)
The PDF header, trailer, and rotation cross-check validators were checked against ISO 32000-2 on 2026-05-11. 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.