How to Add Page Numbers and Headers with DocRaptor
Add repeating page numbers and headers to DocRaptor PDFs with CSS page regions, running strings, HTML flows, and named page styles.
To add repeating page numbers or headers with DocRaptor, define page-margin regions inside an @page rule. Put the current page and document total in the region with counter(page) and counter(pages). Reserve enough top or bottom margin for the content: page regions occupy the margin area and do not expand it automatically.
1. Add a page-number footer
This CSS produces a footer such as “Page 1 of 20” on each page:
@page {
margin-bottom: 36pt;
@bottom {
content: "Page " counter(page) " of " counter(pages);
}
}
For only the current page number, use content: counter(page). The counters are built in. You can also apply a counter style, such as alphabetic numbering, where that format suits the document. If you reset page numbering for a section, verify the resulting numbering against the total-document numbering you intend to show.
DocRaptor’s page-number tutorial and CSS page reference document page counters and page-region content.
2. Add a repeating text header or footer
Use @top or @bottom in the @page rule. A top region can hold a fixed title while a bottom region numbers pages:
@page {
margin-top: 48pt;
margin-bottom: 36pt;
@top {
content: "Quarterly report";
}
@bottom {
content: "Page " counter(page) " of " counter(pages);
}
}
Keep simple, fixed labels as text in the region. Set font, alignment, and other presentation rules as needed, then allow enough margin for the rendered result. DocRaptor’s headers and footers guide explains page regions and running headers.
3. Make a running header from document content
For a header that follows the current chapter title, set a named string on the heading and read it in the top region:
h1 {
string-set: chapter-title content();
}
@page {
margin-top: 48pt;
@top {
content: string(chapter-title);
}
}
Use this when the header should reflect content rather than repeat one hard-coded label. Check the first page and pages around chapter transitions to ensure the intended heading appears.
4. Put richer HTML in a repeating page region
For formatted content such as a company name and a short footer note, move an HTML element into a named static flow and reference that flow from the page region. Put the flow element near the beginning of the HTML document so it is available when earlier pages are laid out.
<body>
<style>
footer { flow: static(footer-html); }
@page {
margin-bottom: 60pt;
@bottom {
content: flow(footer-html);
}
}
footer strong { display: block; }
</style>
<footer>
<strong>Company name</strong>
<span>Confidential report</span>
</footer>
<main>
<h1>Report</h1>
<p>Document content goes here.</p>
</main>
</body>
Style the flow with ordinary CSS for typography and alignment. The footer’s tallest rendered version must fit inside the reserved margin. A footer placed at the end of a long document may not appear on earlier pages because the parser has not encountered its flow yet.
5. Use a different layout for a title page or section
Assign a named page style to an element, then define a matching @page rule. A page-name change inserts a page break, so plan the document structure around that behavior:
#title-page { page: title; }
.chapter { page: chapter; }
@page title {
@top { content: ""; }
@bottom { content: ""; }
}
@page chapter {
margin-top: 44pt;
margin-bottom: 36pt;
@top { content: "Chapter guide"; }
@bottom {
content: "Page " counter(page) " of " counter(pages);
}
}
Use named pages when the title page should omit running content or a section needs a different header. Named page styling controls the layout; it does not by itself define a section-numbering policy. For numbering resets, use the documented counter-reset pattern deliberately and inspect the generated PDF.
See DocRaptor’s page-rules guide for named pages and page selectors.
6. Generate a PDF with the DocRaptor API
Include your HTML and CSS in a JSON request to https://api.docraptor.com/docs. The following runnable cURL example writes the returned PDF bytes to report.pdf. Replace the placeholder with your API key and HTML content:
curl -X POST https://api.docraptor.com/docs \
-H 'Content-Type: application/json' \
-d '{
"user_credentials": "YOUR_API_KEY",
"doc": {
"document_type": "pdf",
"document_content": "<html><head><style>@page { margin-bottom: 36pt; @bottom { content: \\\"Page \\\" counter(page) \\\" of \\\" counter(pages); } }</style></head><body><h1>Report</h1><p>Document content.</p></body></html>"
}
}' \
--output report.pdf
For production, build the JSON with a JSON encoder rather than manually escaping complex HTML inside a shell string. DocRaptor’s API overview describes the request and PDF response. In test mode, generated documents are watermarked.
Python example
import requests
html = """<!doctype html>
<html>
<head>
<style>
@page {
margin-top: 48pt;
margin-bottom: 36pt;
@top { content: \"Quarterly report\"; }
@bottom { content: \"Page \" counter(page) \" of \" counter(pages); }
}
</style>
</head>
<body><h1>Report</h1><p>Document content.</p></body>
</html>"""
response = requests.post(
"https://api.docraptor.com/docs",
json={
"user_credentials": "YOUR_API_KEY",
"doc": {
"document_type": "pdf",
"document_content": html,
},
},
timeout=90,
)
response.raise_for_status()
with open("report.pdf", "wb") as pdf_file:
pdf_file.write(response.content)
print("Pages:", response.headers.get("X-DocRaptor-Num-Pages"))
Node.js example
const html = `<!doctype html>
<html>
<head>
<style>
@page {
margin-top: 48pt;
margin-bottom: 36pt;
@top { content: "Quarterly report"; }
@bottom { content: "Page " counter(page) " of " counter(pages); }
}
</style>
</head>
<body><h1>Report</h1><p>Document content.</p></body>
</html>`;
const response = await fetch("https://api.docraptor.com/docs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_credentials: "YOUR_API_KEY",
doc: { document_type: "pdf", document_content: html },
}),
});
if (!response.ok) {
throw new Error(`DocRaptor request failed: ${response.status} ${await response.text()}`);
}
const pdf = Buffer.from(await response.arrayBuffer());
await import("node:fs/promises").then(({ writeFile }) => writeFile("report.pdf", pdf));
console.log("Pages:", response.headers.get("X-DocRaptor-Num-Pages"));
The response header X-DocRaptor-Num-Pages reports the generated PDF page count. Use it as a useful check, then inspect the PDF itself: the count cannot tell you whether a header was clipped or a title page received the right style.
7. Set margins and verify the rendered pages
- Estimate the height of the tallest header and footer, including padding and line height.
- Set
margin-topandmargin-bottomin@pagewith enough space for those regions. - Generate the PDF and inspect the first page, an ordinary middle page, the last page, and every named-page transition.
- Check long or wrapped header text, page-number formatting, and whether the main content still has enough usable page area.
DocRaptor documents a default page margin of 0.75in, but a default is not a suitable measurement for every designed header or footer. Set the margins intentionally. Its documentation warns that margins do not automatically resize to fit region content. After upgrading an older Prince-based setup, revisit margins if a region that previously overflowed into the page body is now clipped; the Prince 12 migration note describes changed page-margin overflow behavior.
8. Choose the right page-region pattern
| Need | Pattern | Watch for |
|---|---|---|
| Current and total page count | @bottom with counter(page) and counter(pages) |
Reserve enough bottom margin. |
| Fixed repeating label | Text in @top or @bottom |
Long text can wrap or clip. |
| Header follows chapter content | string-set on a heading and string() in the region |
Check pages at heading transitions. |
| Formatted HTML header or footer | Move an element into a named static flow | Place the element near the start of the HTML and reserve its full height. |
| Title page differs from body pages | Named page rules | A page-name change inserts a page break. |
9. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Footer or header is missing | The matching margin region is not defined, or a named page uses a different rule. | Confirm the content is in @top or @bottom under the applicable @page rule. Check the page name assigned to the element. |
| Region is clipped or partly hidden | The top or bottom margin is too small for the content. | Increase the relevant page margin and regenerate. Margins do not automatically grow to fit the region. |
| HTML footer appears only on later pages | The static-flow element occurs late in the document. | Move the flow markup near the beginning of the HTML, before the main document content. |
| Page count is absent or unexpected | The wrong counter is used, or a reset changes current-page numbering. | Use counter(page) for the current page and counter(pages) for the total. Review counter resets and named-page boundaries in the PDF. |
| Title page has a header unexpectedly | The title page is using the general page rule. | Assign a named page to the title element and define its own top and bottom regions. |
| Request fails instead of returning a PDF | Invalid credentials, malformed JSON, or an incorrect API request. | Check the API key, ensure the request is valid JSON with PDF document type and document content, and inspect the API error response. |
| PDF has a test watermark | The request is running in test mode. | Use the appropriate live-mode configuration for the intended production output. |
10. Performance, reliability, and cost considerations
Page regions are a layout feature; the dossier does not establish a rendering benchmark or a speed advantage for any particular header pattern. Keep headers and footers concise when possible, since taller regions consume margin space and leave less room for body content. HTML flows offer richer formatting but add placement and sizing details to validate.
For reliable output, treat the generated PDF as the final authority: inspect representative page types, use X-DocRaptor-Num-Pages as a page-count check, and retain examples that cover title pages, section transitions, and long content. The documented API returns PDF bytes on successful synchronous generation. No pricing details are established by the cited research, so check DocRaptor’s current pricing directly before estimating production cost.
11. Or skip the browser setup
If your next step is to capture a rendered web page for a report, preview, or record, ScreenshotNeo is a website screenshot API and MCP server for developers. Its one-call API returns an image or PDF; it does not replace DocRaptor’s CSS page-region method for adding repeating PDF headers and page numbers.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for request options. Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo and get 1,000 free screenshots a month, no card required.
12. FAQ
Can I number pages alphabetically?
Yes. DocRaptor’s page-number tutorial demonstrates an alphabetic current-page format. Choose a counter style appropriate to the numbering scheme and verify how it interacts with any reset.
Can a page region contain HTML?
Yes. Move the HTML element into a named flow and reference that flow from the page region. Put the flow near the start of the document.
Does a named page automatically restart numbering?
No. Named pages control page styling. Treat numbering resets as a separate choice and verify the output.
Does the API page-count header prove the footer rendered?
No. X-DocRaptor-Num-Pages reports page count. Inspect the PDF to confirm region placement and clipping.


