File & PDF Tools

Add PDF Page Numbers

Add configurable page numbers to a PDF header or footer.

Add PDF page numbers
Write configurable page numbers in a header or footer.
Local browser processing

Add PDF Page Numbers

Write numbers in a header or footer. {page} is the current number and {total} is the document page count.

Output Rules

  • Configure start number, size, and six positions
  • Browser fonts provide Latin and non-Latin formats
  • The source file is unchanged

Add PDF Page Numbers stamps a running counter into the top or bottom margin of every page of one PDF and saves it as a new file inside the browser. A pattern such as Page {page} of {total} sets the wording, alongside a start value, a 6–72 pt size, and six positions — useful for reports, contracts, and handouts.

Every number is an embedded PNG, not text

The pattern is drawn by the browser on a canvas at twice the requested size, in a 500-weight sans-serif filled with #111827, then encoded as a transparent PNG, embedded in the document, and drawn with scale(0.5) so the rendered height matches the point size you typed. No PDF text operator is involved, which means the digits cannot be selected, searched, copied, or recovered by a text-extraction pass, and a tagging checker sees an image with no alternate description. That cost buys non-Latin formats: the 14 standard PDF fonts hold no CJK glyphs, so a real text layer would require shipping a subsetted TTF, while the canvas path borrows whatever face the browser already has.

Margins are measured from the MediaBox

Placement calls page.getSize(), which reports the MediaBox in PDF points — 1 pt is 1/72 inch, A4 is 595.28 × 841.89, Letter is 612 × 792 — and ignores both /Rotate and the CropBox. A footer image gets its bottom edge at exactly the margin you type, anywhere from 0 to 300 pt, the default 36 pt being half an inch. The image box is 2.2 × the font size with the glyphs centred, so a 10 pt number at a 36 pt margin shows its ink around 47 pt from the edge. On a page carrying /Rotate 90 the footer moves to one side and reads sideways; on a page with a reduced CropBox a generous margin can push the number outside the visible area.

Pattern placeholders and the start offset

The pattern receives two literal, case-sensitive replacements, {page} and {total}. A pattern lacking the exact string {page} is refused, so a total-only footer cannot be built, and any other brace token such as {Page} prints verbatim; repeats are all substituted, so {page} / {page} duplicates correctly. The start value accepts integers from -999999 to 999999, making 0 and negative counters legal, but it shifts only {page}: {total} is always the physical page count of the output. This mode has no page-range field, unlike watermark mode, so every page is numbered and an uncounted cover ends the document on a footer reading 21 of 20.

Worked example

Numbering a three-page file with the cover not counted

Input
A 3-page A4 PDF, pattern Page {page} / {total}, start number 2, size 10 pt, margin 36 pt, position bottom center
Output
The three footers read Page 2 / 3, Page 3 / 3 and Page 4 / 3. Each is a 22 pt tall image box centred horizontally with its bottom edge at y = 36, saved as the original name plus -page-numbers.pdf.

The offset moves only {page} while {total} stays fixed at the document three pages, which is why the last footer reads 4 / 3. The 22 pt height comes from 2.2 × the 10 pt size, with the glyphs centred inside that box, so the ink sits slightly further from the paper edge than the stated 36 pt margin implies.

Limits and pitfalls

  • Size is clamped by validation, not by rendering: values below 6 or above 72 pt, and margins below 0 or above 300 pt, are rejected with an error before any bytes are written.
  • All pages are numbered, because this mode has no range field; leaving a cover unnumbered requires a start of 2, after which the last footer disagrees with {total}.
  • The digits are images, so they cannot be selected or searched, extraction tools return nothing for them, and no PDF page labels are created — a viewer sidebar still counts from 1.
  • Each page embeds its own PNG plus a soft mask, since pdf-lib caches no images, so a long document grows with page count even when only one digit changes.
  • Input is capped at 100 MB per file, and gaps appear on pages whose CropBox is smaller than the MediaBox, because margins are measured against the MediaBox.

Related tools

When it goes wrong

I typed 1.5 as the start number and the page answered NUMBER_START:-999999-999999.

CauseThe start value passes through validateInteger, which rejects anything failing Number.isInteger by throwing a machine code built from the label and range. That code has no entry in the friendly-error table, so it is printed verbatim. The counter itself is later built by plain addition, numberStart + index, so fractions were never representable.

FixEnter a whole number from -999999 to 999999. When the footer needs a decimal, write it into the pattern as literal text next to {page} instead of trying to make the counter fractional.

I wrote {Page} of {total} and it refused with a must-include-{page} error.

CauseThe gate is a literal String.includes test for the exact lowercase token, and substitution uses replaceAll with that same literal. Matching is case-sensitive and recognises only two names, so {Page}, {PAGE} or {页码} satisfy neither the check nor the substitution.

FixType {page} and {total} in lowercase, spelled exactly. Any other brace token is only ever printed as text, which is what the rejected pattern would have produced anyway.

A scanned PDF that opens sideways in my reader gets its number up the left edge, lying on its side.

CauseBoth coordinates come from page.getSize(), which reports the unrotated MediaBox, and the image is drawn with rotate: 0. The viewer then applies the page /Rotate 90 on top, so the edge the code calls the bottom is displayed as the left edge and the digits read sideways.

FixNeutralise the stored rotation first with the rotate tool: it adds the angle and takes the remainder mod 360, so adding 270 to a page carrying /Rotate 90 lands on 0. Number the pages afterwards.

My footer is clipped at both ends after I appended a document title to the pattern.

CauseThe label is rasterised at twice the requested size with width measureText(text).width + fontSize * 2 and drawn at half scale, so its drawn width is roughly the text width plus the font size in points. Once that exceeds the page width, centring puts x at half a negative difference and the overflow is clipped at the MediaBox.

FixCheck the widest label, normally the last page, against the page width minus both margins: on A4 that is 595.28 pt, about 523 pt usable at the default 36 pt margin. Shorten the title, lower the size, or switch to a corner position, which is clipped on one side only.

The numbers jog left and right as I scroll a PDF that mixes page sizes.

CauseEach page derives x from its own width, so centre and right placements follow that page edge rather than a fixed axis. A4 is 595.28 pt wide and Letter 612 pt, a 16.72 pt difference that shows up as a visible step at every size change.

FixPick bottom-left or top-left, where x is the margin alone and every page shares one axis. If centred numbers are required, unify the page size before numbering rather than after.

A 252 by 144 pt business-card file keeps its numbers completely invisible.

CauseBottom placement sets y to the margin value itself, and nothing compares it with the page height. A 300 pt margin starts drawing 156 pt above the top edge of a 144 pt tall card, so the whole image box falls outside the MediaBox and the file looks untouched.

FixKeep the margin well under the shorter side of the page, for example 12 to 18 pt on a card, and test the largest value you plan to use on the smallest page in the set before numbering the rest.

One concrete scenario

A twelve-page contract whose cover must not carry page 1

An A4 contract runs twelve pages with a cover first, and the eleven body pages have to read Page 1 of 11 at 10 pt with a 36 pt margin, which gives a 22 pt tall image box whose ink sits 12.7 mm off the edge. Loaded whole, this mode stamps every page, so the cover becomes Page 1 of 12 and the body opens at Page 2 of 12. Raising the start to 2 fails too, because {total} is the physical page count of the saved file and the last sheet reads Page 13 of 12. Split the cover off, number the eleven-page remainder from 1, then merge it back; drawn numbers are page content and survive the copy.

Frequently Asked Questions

How do {page} and {total} work?
{page} becomes the current page number and {total} the page count: entering "第 {page} / {total} 页" produces "第 3 / 20 页" on every page. Set the start number to 2 when the cover should not count.
Will numbers cover my content?
Numbers render in the top/bottom margin zones across six positions. If content runs to the page edge, use a smaller size or a different position; for formal documents, footer-center is the convention that interferes least.
Is my original file modified?
No — the tool outputs a new file and leaves the original untouched, so experiment freely. Note that stacking page numbers twice prints doubled text: always regenerate from the un-numbered original.

Content reviewed: