The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Use wkhtmltopdf’s footer substitution variables through Snappy. For a centered counter, set footer-center to Page [page] of [topage]; wkhtmltopdf replaces [page] with the current page and [topage] with the document’s final page number.
What Snappy is actually doing
KnpLabs Snappy is a PHP wrapper. It does not calculate page numbers itself: setOption() passes options to the wkhtmltopdf binary installed on your server. Consequently, the deployed binary, its Qt build, and its available command-line options determine the final footer behavior.
Snappy’s README expects a separately installed wkhtmltopdf 0.12.x binary. The wkhtmltopdf command-line manual cited for these options documents version 0.12.6 with patched Qt. Confirm the binary used by your application rather than assuming that a globally installed executable is the one Snappy invokes.
Add a plain-text page counter
This is the smallest working implementation. The option name is written without the command-line -- prefix.
#1 Best Overall
<?php
use KnpSnappyPdf;
$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');
$snappy->setOption('footer-center', 'Page [page] of [topage]');
$snappy->setOption('margin-bottom', '18mm');
$snappy->setOption('footer-spacing', '5');
$html = '<h1>Monthly report</h1><p>Report content</p>';
$snappy->generateFromHtml($html, '/tmp/report.pdf');
Open the resulting PDF and you should see a centered footer such as “Page 1 of 4”. The same substitution string can be placed on the left or right:
$snappy->setOption('footer-left', 'Page [page] of [topage]');
// or
$snappy->setOption('footer-right', 'Page [page] of [topage]');
Use only one of the three positional options unless you intentionally want different content in multiple footer positions.
What the two page tokens mean
[page]is the number of the page currently being rendered.[topage]is the number of the last page in the printed document.
The values are substituted by wkhtmltopdf during rendering. Do not replace them with PHP variables before calling Snappy; doing so would produce one static value on every page.
Reserve enough room for the footer
A footer occupies the bottom margin area. If the margin is too small, text can be clipped, overlap document content, or appear to be missing. Set the bottom margin and footer spacing explicitly, then adjust them for the font and content used by your report.
Rank #2
$snappy->setOption('margin-bottom', '22mm');
$snappy->setOption('footer-spacing', '6');
margin-bottom reserves the page area; footer-spacing controls the gap between the document body and the footer. Excessive spacing can push the footer outside the physical page, so increase values gradually and inspect the rendered PDF.
Use an HTML footer for styling or extra metadata
Choose footer-html when plain text is not enough—for example, when you need a rule, custom fonts, alignment, branding, or several fields. The option points to a separate footer HTML document. wkhtmltopdf supplies substitution values to that document through query parameters.
$snappy->setOption('footer-html', __DIR__ . '/footer.html');
$snappy->setOption('margin-bottom', '25mm');
$snappy->setOption('footer-spacing', '4');
$snappy->generateFromHtml($html, '/tmp/report.pdf');
Create footer.html as a complete, self-contained page. The page and topage elements below are populated from the query string that wkhtmltopdf appends when it loads the footer.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
html, body { margin: 0; padding: 0; }
body { font: 9pt Arial, sans-serif; color: #555; }
.footer { border-top: 0.3mm solid #bbb; padding-top: 2mm; text-align: center; }
</style>
</head>
<body>
<div class="footer">
Page <span class="page"></span> of <span class="topage"></span>
</div>
<script>
(function () {
var params = new URLSearchParams(window.location.search);
document.querySelector('.page').textContent = params.get('page') || '';
document.querySelector('.topage').textContent = params.get('topage') || '';
}());
</script>
</body>
</html>
The footer mechanism supports a wider token set, including [page], [frompage], [topage], [webpage], [section], [subsection], [date], [isodate], [time], [title], [doctitle], [sitepage], and [sitepages]. For a simple counter, the built-in text option is less fragile; use HTML when you genuinely need markup.
Choose the right footer option
| Requirement | Recommended option | Why |
|---|---|---|
| Centered “Page X of Y” | footer-center |
One option and no separate document. |
| Left- or right-aligned text | footer-left or footer-right |
Direct positional control. |
| Rules, CSS, logos, or multiple fields | footer-html |
Markup and styling are available. |
| Untrusted, user-supplied footer content | Sanitized, isolated HTML | Reduces the risk associated with local files and scripts. |
Installation and version checks
- Install wkhtmltopdf separately on the host that renders PDFs.
- Locate the exact executable, for example
/usr/local/bin/wkhtmltopdf, and pass that path tonew Pdf(). - Run the executable’s version command in the same environment as PHP and verify the actual 0.12.x build.
- Render a small fixture containing enough content to produce at least two pages.
- Check the footer, total-page value, bottom margin, and output on the deployed server—not only on a development workstation.
Different packages can be built with different patches or capabilities. If an option works locally but not in production, compare the executable path and version before changing application code.
Troubleshoot missing, clipped, or incorrect numbers
The footer is absent
- Confirm that the option is named
footer-center,footer-left, orfooter-right, not the command-line form with leading hyphens. - Verify that Snappy is invoking the binary you expect and that the installed build supports headers and footers.
- Increase
margin-bottomand render again.
The footer is clipped or overlaps content
- Increase the bottom margin to create physical space.
- Reduce
footer-spacingif the footer is being pushed beyond the page edge. - For an HTML footer, reduce its font size, padding, or border thickness.
Every page shows the same number
Make sure you passed the literal strings [page] and [topage] to wkhtmltopdf. A PHP interpolation step or a template escape can turn them into static text before rendering.
“Of” shows no final page number
- Test with a document that definitely spans multiple pages.
- Check that the token is exactly
[topage], including brackets and spelling. - If using
footer-html, inspect the generated footer URL and confirm that the script reads the suppliedtopagequery value.
The HTML footer is blank
- Use a self-contained footer file with inline CSS and JavaScript.
- Check file permissions and the path passed to
footer-html. - Do not assume browser-only APIs or external assets will load in the renderer’s environment.
It works on one machine but not another
Compare wkhtmltopdf versions, patched-Qt builds, executable paths, fonts, and runtime permissions. Reproduce the issue with a minimal HTML file before modifying the full report.
Free tools Windows power users keep installed
One-click scans. No signup required.
Security considerations for HTML and local files
Snappy’s documentation warns that enabling --enable-local-file-access can be dangerous when HTML or JavaScript is untrusted. Avoid enabling it unless the report requires local assets. Sanitize user input, keep footer files under controlled directories, and run the renderer in an appropriate sandbox. Never place secrets in query strings or in HTML that a user can modify.
Rank #4
Performance and reliability practices
- Reuse a configured
Pdfinstance when generating reports with the same renderer settings. - Keep the footer lightweight; external images, web fonts, and scripts add failure points.
- Use a representative multi-page fixture in automated tests so both current-page and final-page substitution are checked.
- Record the wkhtmltopdf version and binary path with deployment metadata.
- Render to a temporary file, verify that it exists and is non-empty, then move it to its final destination.
The page count is known only during rendering, so a correct “X of Y” value requires wkhtmltopdf to complete pagination. Timeouts, missing fonts, malformed HTML, or blocked resources can prevent a trustworthy result even when the PHP call itself succeeds.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is to capture a web report or documentation page as an image or PDF rather than maintain a wkhtmltopdf server, ScreenshotNeo provides a website screenshot API and MCP server. A single request can return PNG, JPEG, WebP, or PDF; it accepts the URL and handles the browser session for you.
See the ScreenshotNeo API documentation for all parameters. This cURL request captures a page:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://itechguides.com -o shot.webp
Equivalent clients are available when your automation is written in PHP, Python, or Node.js:
Best Value
<?php
$q = http_build_query(['access_key' => 'YOUR_API_KEY', 'url' => 'https://itechguides.com']);
$data = file_get_contents("https://api.screenshotneo.com/v1/shot?$q");
file_put_contents('shot.webp', $data);
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://itechguides.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://itechguides.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Before capture, ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Start with a free ScreenshotNeo account.
Final implementation checklist
- Use
footer-centerwithPage [page] of [topage]for the direct solution. - Set and test
margin-bottomandfooter-spacing. - Use
footer-htmlonly when CSS or markup is needed. - Verify the deployed wkhtmltopdf 0.12.x binary and build.
- Keep untrusted HTML away from unrestricted local-file access.
Frequently Asked Questions
Can I start numbering at a value other than 1?
The documented footer substitutions expose the current and final rendered page; changing the displayed starting number requires transforming the footer value or using a different pagination design.
Does the footer appear on every page automatically?
Yes. Header and footer options are applied during wkhtmltopdf pagination, provided the footer fits within the configured page margins.
Which option should I use for a company logo?
Use footer-html so the footer can contain markup and styling; reserve sufficient bottom margin for its height.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

