Toolarity

Typst Page Numbering: Title Page, Roman Numerals, Page X of Y

Fix Typst Published October 11, 2026

Turning on page numbers in Typst is one line: #set page(numbering: "1"). The problems start with the second thing you ask for: a footer with your own text, a title page that shouldn’t count, Roman numerals for the preface, “Page 3 of 10”, or numbers at the top of the page. Each of those has a rule behind it that is written down somewhere in the docs, usually as a single sentence, and each one produces a page that looks wrong without any error or warning.

I compiled every snippet on this page with the two official Typst CLI builds listed in the tested box above, read the header and footer text back out of each PDF page with a small Python script, and checked that both versions print the same thing. They did, in every case. The screenshots are rendered from the same code on small 10 × 7 cm pages so the numbers are readable.

Short answer (tested 2026-09-30):

You wantDo thisThe trap
Your own footer text plus a page numberPut counter(page).display(...) inside footer: context [...]Setting footer: switches numbering: off completely
No number on the title page, body starts at 1Title page with numbering: none, then #counter(page).update(1) at the top of the first body pageWithout the update, the body starts at 2: the title page still counts
Roman numerals for the preface, then 1, 2, 3numbering: "i", then #set page(numbering: "1") + #counter(page).update(1)A set page rule in the middle of the text starts a new page by itself
“Page X of Y”numbering: "1 of 1"Y is the counter’s final value, so the preface shows “i of iii” when it has 2 pages
Page numbers at the topnumber-align: top + rightA reset with update(1) doesn’t reach the header on that page. Use update(0) before the break instead
Number on the outer edge (right on odd, left on even)Custom footer that checks calc.odd(here().page())number-align can’t alternate on its own

This is the first one almost everybody hits:

// footer-override.typ
#set page(numbering: "1", footer: [My custom footer])
Page A
#pagebreak()
Page B

Both pages show “My custom footer” and no page number. Typst compiles without complaint. The page reference says so in one sentence at the end of the numbering parameter: “If an explicit footer (or header for top-aligned numbering) is given, the numbering is ignored.” (typst.app/docs/reference/layout/page/, read 2026-09-30). The footer is where the number would have gone, so a custom footer replaces it.

The fix is to draw the number yourself inside the footer. counter(page).display() takes the same patterns as numbering, and both: true makes a pattern like "1 of 1" fill in the total as well:

// footer-with-number.typ
#set page(footer: context [
  My Report #h(1fr) #counter(page).display("1 of 1", both: true)
])
Page A
#pagebreak()
Page B

This prints “My Report” on the left and “1 of 2”, “2 of 2” on the right. Note the context before the footer’s content: without it you get a compile error (section 7).

Two rows of two rendered pages from Typst. Row A, numbering "1" plus footer set to "My custom footer": both pages show only "My custom footer" at the bottom and no page number; red caption says no page number on either page, no error or warning. Row B, number drawn inside the footer with counter(page).display("1 of 1", both: true): the pages show "My Report" on the left and "1 of 2", "2 of 2" on the right.

2. No number on the title page, but the body starts at 2

Turning numbering off for the title page only hides the number. The page is still counted:

// title-page-counts.typ
#set page(numbering: none)
Title page
#pagebreak()
#set page(numbering: "1")
Body A
#pagebreak()
Body B

The title page is blank at the bottom, and the two body pages are numbered 2 and 3. Typst’s page counter goes up at every page break, whether or not a number is shown. To start the body at 1, reset the counter at the top of the first body page:

// title-page-reset.typ
#set page(numbering: none)
Title page
#pagebreak()
#set page(numbering: "1")
#counter(page).update(1)
Body A
#pagebreak()
Body B

Now the body pages are 1 and 2. The same pattern works for any number of unnumbered pages in front: with a cover and a copyright page before the reset, the first body page was still 1.

Two rows of three rendered pages from Typst. Row A, title page with numbering none, then numbering "1": the title page has no number and the body pages show 2 and 3; red caption says the unnumbered title page still counted as page 1. Row B, the same plus counter(page).update(1) after the title page: the body pages show 1 and 2.

3. Roman numerals for the preface, then 1, 2, 3

// roman-then-arabic.typ
#set page(numbering: "i")
Preface A
#pagebreak()
Preface B
#set page(numbering: "1")
#counter(page).update(1)
Chapter A
#pagebreak()
Chapter B
#pagebreak()
Chapter C

This prints i, ii, then 1, 2, 3. Notice there is no #pagebreak() before the second set page. I didn’t forget it: a set page rule that changes page settings ends the current page on its own. That is convenient here, but it also means you can’t use set page(numbering: ...) to switch the style partway down a page. In a separate test, a set page(numbering: "1") placed after the first line of a page pushed the rest of the text onto a new page.

4. “Page X of Y”: why the preface says “i of iii”

The "1 of 1" pattern shows the current page and a total. The total is not the number of pages in the PDF. It is the page counter’s final value. The page reference says it directly: “These are logical numbers controlled by the page counter, and may thus not match the physical numbers. Specifically, they are the current and the final value of counter(page).”

Combine that with a counter reset and the preface gets a total that belongs to the chapters:

// x-of-y-preface.typ
#set page(numbering: "i of i")
Preface A
#pagebreak()
Preface B
#set page(numbering: "1 of 1")
#counter(page).update(1)
Chapter A
#pagebreak()
Chapter B
#pagebreak()
Chapter C

The chapters are fine (“1 of 3” to “3 of 3”), but the two preface pages say “i of iii” and “ii of iii”: the counter ends at 3, so every “of” says 3, written in the preface’s numerals. The simplest fix is to leave the total off the preface (numbering: "i"). If you want the total there too, put a label at the end of the preface and read the counter at that point:

// x-of-y-preface-fixed.typ
#set page(numbering: "i", footer: context align(center,
  counter(page).display() + " of " + numbering("i", ..counter(page).at(<front-end>))
))
Preface A
#pagebreak()
Preface B
#metadata(none) <front-end>
#set page(numbering: "1 of 1", footer: auto)
#counter(page).update(1)
Chapter A
#pagebreak()
Chapter B
#pagebreak()
Chapter C

This gives “i of ii”, “ii of ii”, then “1 of 3” to “3 of 3”. footer: auto hands the footer back to numbering for the chapters.

Two groups of five rendered pages from Typst. Row A, "i of i" for the preface and "1 of 1" plus a counter reset for the chapters: the preface pages read "i of iii" and "ii of iii", the chapter pages read "1 of 3", "2 of 3", "3 of 3"; red caption says the total is the counter&#x27;s final value (3), not the preface&#x27;s 2 pages. Row B, preface total read from a label with counter(page).at(<front-end>): the preface pages read "i of ii" and "ii of ii" and the chapters still read "1 of 3" to "3 of 3".

If you need the real number of sheets, including unnumbered ones, count physical pages with a label on the last page instead of reading the counter:

// physical-total.typ
#set page(footer: context align(center)[
  Sheet #here().page() of #locate(<last>).page()
])
Cover
#pagebreak()
#counter(page).update(1)
Body A
#pagebreak()
Body B
#metadata(none) <last>

This prints “Sheet 1 of 3”, “Sheet 2 of 3”, “Sheet 3 of 3”, ignoring the reset. In the same file, counter(page).final() returned 2.

5. Page numbers at the top ignore the reset on that page

Moving the numbers to the header with number-align: top breaks the reset from sections 2 and 3:

// top-numbers-reset.typ
#set page(numbering: "i", number-align: top + right)
Preface A
#pagebreak()
Preface B
#set page(numbering: "1")
#counter(page).update(1)
Chapter A
#pagebreak()
Chapter B

The header reads i, ii, 3, 2. The first chapter page shows 3, and the page after it shows 2. With "1 of 1" at the top and a title page, I got “1 of 2”, “2 of 2”, “2 of 2”.

This has been reported on the Typst issue tracker more than once (#2828, #3835), and both reports were closed without a code change. In #3835 (closed 2024-08-22), a Typst maintainer explains: “The page counter is updated in the main content of the page, which is after the page’s header, so the result is not reflected there.” That matches what I measured. When I printed the raw counter value in both places on the reset page, the header said 2 and the footer said 1. Bottom numbers see the reset and top numbers don’t.

The fix given in both issues is to set the counter to 0 before the page break, so the automatic +1 at the break lands on 1:

// top-numbers-fixed.typ
#set page(numbering: "i", number-align: top + right)
Preface A
#pagebreak()
Preface B
#counter(page).update(0)
#set page(numbering: "1")
Chapter A
#pagebreak()
Chapter B

Header: i, ii, 1, 2. Correct.

Don’t copy this fix into a document with numbers at the bottom. The footer of the last preface page is laid out after the update(0), so it shows zero, and in Roman numerals zero is written “n”:

// bottom-numbers-update0.typ
#set page(numbering: "i")
Preface A
#pagebreak()
Preface B
#counter(page).update(0)
#set page(numbering: "1")
Chapter A
#pagebreak()
Chapter B

Footer: i, n, 1, 2. So the rule is: numbers at the bottom, update(1) after the break. Numbers at the top, update(0) before it. If the last page before the break has no number at all (a title page with numbering: none), update(0) before the break works for both.

A side effect worth knowing: the page-setup guide’s example for hiding the header on the title page, if counter(page).get().first() > 1, keeps working after a reset to 1. The header on the first body page still reads the old value (2), so the header does appear there.

6. Where the update goes on the page

The page-setup guide says counter(page).update(1) “should be placed at the start of a page because it will otherwise create a page break.” In my tests it did not create a page break. Instead the new value applied to the whole page, including the text above the update:

// update-mid-page.typ
#set page(numbering: "1")
Page A
#pagebreak()
Top of second page.

#counter(page).update(5)

Bottom of second page.
#pagebreak()
Third page

Three pages, numbered 1, 5, 6, with both paragraphs of the second page on the same page. I got the same result with the update inside a paragraph, with update(n => n + 10), and with the update on the first page. I’d still follow the guide and put the update at the top of the page. The result is the same, and the code shows what you meant.

7. “can only be used when context is known”

Reading the counter in a header or footer without context fails:

// no-context.typ
#set page(footer: [Page #counter(page).display()])
Page A
error: can only be used when context is known

The compiler adds two hints: “try wrapping this in a context expression” and “the context expression should wrap everything that depends on this function”. Add context in front of the footer’s content, as in section 1. counter(page).get() fails with the same error. You don’t need context to update the counter, only to read it.

8. Numbers on the outer edge: right on odd pages, left on even pages

number-align takes one alignment for every page. The page-setup guide notes that alternating between even and odd pages “is not currently supported using this property”. A custom footer can do it:

// odd-even.typ
#set page(footer: context {
  let n = counter(page).display("1")
  if calc.odd(here().page()) { align(right, n) } else { align(left, n) }
})
Page A
#pagebreak()
Page B
#pagebreak()
Page C

Pages 1 and 3 have the number on the right, page 2 on the left. I used here().page(), the physical page, so the sides follow the physical sheets even after a counter reset (tested with a cover page and a reset: right, left, right, left). If your book starts on a left-hand page, swap the two branches.

9. The table of contents uses each page’s own style

If you’re worried that the outline will list preface headings as 1, 2 after you switch styles, it doesn’t:

// outline-styles.typ
#set page(numbering: "i")
#outline()
= Preface
#pagebreak()
#set page(numbering: "1")
#counter(page).update(1)
= Chapter One
#pagebreak()
= Chapter Two

The outline lists “Preface … i”, “Chapter One … 1”, “Chapter Two … 2”.

What I’d do

For a typical report or thesis: title page with numbering: none, preface in "i" with no total, #counter(page).update(1) right after the set page that switches to "1" or "1 of 1", and numbers at the bottom. That is the setup where every default does what it looks like it does. If the design needs numbers at the top, switch the reset to update(0) before the break (section 5) and check the first page of each section in the PDF. That’s the page that goes wrong.

How to check this yourself

Compile any snippet above and look at the first page after each reset. That’s where every problem on this page shows up. If you’d rather not scroll through a PDF, this prints the text in the top and bottom margins of every page. It uses PyMuPDF (pip install pymupdf):

# margins.py - usage: python3 margins.py file.pdf
import sys, pymupdf
for i, page in enumerate(pymupdf.open(sys.argv[1]), 1):
    h = page.rect.height
    top = [b[4].strip() for b in page.get_text("blocks") if b[3] < 60]
    bottom = [b[4].strip() for b in page.get_text("blocks") if b[1] > h - 60]
    print(f"page {i}: top={top} bottom={bottom}")
$ typst compile top-numbers-reset.typ
$ python3 margins.py top-numbers-reset.pdf

On top-numbers-reset.typ it should print 3 at the top of page 3.

What this test does not cover

  • Only the official macOS (Apple Silicon) CLI builds in the tested box, tested 2026-09-30. I didn’t test the web app.
  • PDF output only. I didn’t test HTML export.
  • For number-align I tested top + right, bottom (center, the default) and bottom + right. I didn’t test documents that switch between top and bottom numbering partway through.
  • The claim in section 6 (no page break from a mid-page update) held in the four layouts I tried. It contradicts one sentence in the official guide, so if your layout is more complex, check the page count after adding an update.
  • The “working as intended” explanation in section 5 comes from comments by Typst team members on GitHub issues #2828 and #3835 (read 2026-09-30), not from the reference docs.