ScreenshotNeo

BlogHTML to image & PDF

How to Convert a Webpage to PDF with Page Numbers Using Playwright

Save a webpage as a paginated PDF with Playwright. Add page numbers, set print options, and fix common layout and rendering problems.

By the ScreenshotNeo team4 October 20268 min read

Use Playwright’s page.pdf() with displayHeaderFooter: true and a footerTemplate containing pageNumber and totalPages spans. Playwright renders with print CSS by default, and the footer appears only if you reserve space for it with PDF margins.

1. Create a numbered PDF with Playwright

This runnable JavaScript example opens a page, waits for navigation, and saves an A4 PDF with a right-aligned “current / total” page number footer.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'networkidle' });

    await page.pdf({
      path: 'page.pdf',
      format: 'A4',
      printBackground: true,
      displayHeaderFooter: true,
      margin: {
        top: '20mm',
        right: '15mm',
        bottom: '20mm',
        left: '15mm',
      },
      footerTemplate: `
        <div style="width: 100%; font-size: 9px; padding: 0 15mm; text-align: right;">
          <span class="pageNumber"></span> / <span class="totalPages"></span>
        </div>
      `,
    });
  } finally {
    await browser.close();
  }
})();

Install Playwright in your project and use the browser binaries associated with that installed version. Playwright’s versions require corresponding browser binaries; the official browser guide explains browser installation and version matching. See the Page PDF API reference for the current option details.

  1. Set displayHeaderFooter: true. It defaults to false.
  2. Put <span class="pageNumber"></span> and <span class="totalPages"></span> in footerTemplate or headerTemplate.
  3. Set a bottom margin large enough for the footer. PDF margins default to zero, so a footer can otherwise collide with document content or sit outside the useful page area.

Playwright also provides the template classes date, title, and url. Header and footer templates are separate from the page document: scripts in them do not run, and the page’s styles are not available there. Put needed styling inline in the template.

2. Choose print or screen styling

page.pdf() uses print CSS media by default. This is usually appropriate for a document because the page can define print-specific layout with @media print and @page.

// Default: render using print CSS.
await page.pdf({ path: 'print-layout.pdf', displayHeaderFooter: true, footerTemplate });

// Opt in to screen CSS when the screen layout is the desired output.
await page.emulateMedia({ media: 'screen' });
await page.pdf({ path: 'screen-layout.pdf', displayHeaderFooter: true, footerTemplate });

Choose one rendering mode deliberately. A responsive page may have very different widths, navigation, or visibility rules in print and screen media, which can change pagination and therefore the total page count.

3. Set paper size, margins, and print output

Use either a named paper format or dimensions that fit your use case. Letter is the documented default format; A4 and A5 are also available. An explicit format takes priority over width and height. Set a margin on all sides when the document needs a readable border or room for header and footer content.

Option Use Detail
format Choose a named paper size such as A4, A5, or Letter. When set, it takes priority over width and height.
width, height Specify paper dimensions when named formats do not fit. Do not expect them to override format.
preferCSSPageSize Honor the page’s CSS @page size. When true, CSS page size takes priority; otherwise content is scaled to fit the PDF paper size.
landscape Use landscape orientation for wide content. Check the resulting page breaks and footer placement.
margin Reserve space around the printable area. Defaults to zero; provide top, right, bottom, and left values as needed.
printBackground Include background colors and images. Defaults to false. Set true when those visuals are part of the document.
scale Adjust the scale of the rendered content. Use sparingly: shrinking content can affect readability and page breaks.
pageRanges Export a subset of pages. Useful when the document is large and only selected pages are needed.

For exact print colors, the PDF API notes that PDF generation modifies colors for printing by default. Use the CSS -webkit-print-color-adjust property where exact colors are required. Check the output in a PDF viewer, since page backgrounds and color handling can affect legibility and ink usage.

Example using a CSS-defined page size

await page.goto('https://example.com');
await page.pdf({
  path: 'css-sized.pdf',
  preferCSSPageSize: true,
  printBackground: true,
  displayHeaderFooter: true,
  margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' },
  footerTemplate: '<div style="width:100%;text-align:center;font-size:9px"><span class="pageNumber"></span> of <span class="totalPages"></span></div>',
});

Use preferCSSPageSize when the site’s own @page rules define the intended paper dimensions. Otherwise specify a PDF paper format and allow the content to scale to it.

4. Wait for page content before exporting

A PDF captures the page’s current rendered state. Navigation completing does not guarantee that every image, font, or client-rendered section has finished loading. Pick a wait condition that matches the page instead of assuming one condition is reliable for every site.

  • Use an appropriate page.goto() waitUntil condition for navigation. networkidle can be useful for quiet pages, but pages with long polling or ongoing requests may never become idle.
  • For a known key element, wait for it explicitly with page.waitForSelector().
  • If the site populates content after navigation, wait for a meaningful page-specific condition before calling page.pdf().
  • For lazy-loaded images, scroll through the page before exporting if the site only loads them near the viewport, then allow them to settle.

Waiting longer can improve completeness but increases latency. Prefer a clear readiness signal and a bounded timeout over an arbitrary long delay.

5. Or skip the browser setup

If you need a PDF without managing a local browser, ScreenshotNeo accepts a URL and can return a PDF. See the ScreenshotNeo API documentation for the PDF parameters and response behavior.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o page.pdf

Or use Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("page.pdf", "wb").write(r.content)

Or use 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}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
await require('node:fs/promises').writeFile('page.pdf', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its 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 1,000 free screenshots a month.

6. Troubleshoot common PDF problems

Symptom Likely cause Fix
No page numbers appear displayHeaderFooter was omitted or is false, or the template lacks the documented placeholder classes. Enable the option and add pageNumber and, if desired, totalPages spans to the header or footer template.
Footer overlaps text or is clipped The bottom margin is too small, or template content exceeds the available footer area. Increase the bottom margin and simplify the template; keep template styles inline.
Footer styling does not match the page Page styles are not visible to header and footer templates. Set typography, spacing, and alignment directly in the template’s inline styles.
Script-generated footer content is missing Scripts in header and footer templates are not evaluated. Use the supported placeholder classes and static template markup.
PDF looks different from the browser PDF uses print media by default, or print CSS hides or rearranges content. Inspect print styles; call page.emulateMedia({ media: 'screen' }) before PDF generation if screen styling is intended.
Backgrounds are missing printBackground defaults to false. Set printBackground: true.
Content is unexpectedly scaled Paper format and CSS @page sizing differ, or format takes precedence over dimensions. Choose either the PDF format or CSS sizing intentionally; set preferCSSPageSize: true to prioritize CSS page size.
Output changes after a Playwright update or browser install The installed browser binary does not match the Playwright version. Install and run the browser binaries associated with the installed Playwright version, and consult documentation for that version.
Some content or images are absent The page was exported before client rendering, lazy loading, or image loading finished. Wait for a meaningful selector or page condition; scroll to trigger lazy loading where needed.

7. Performance, reliability, and cost considerations

PDF generation requires launching or reusing a browser, navigating to the page, rendering it, and writing the output. For repeated conversions, reuse a browser process where appropriate and create a fresh page or context for each job. Always close the browser when the worker is done; the example uses finally so cleanup runs if navigation or PDF generation fails.

Set navigation and readiness timeouts appropriate to the pages you process, and handle failures at each stage. A slow or unreachable page, a selector that never appears, and a PDF rendering error should be reported separately in your application logs. Do not retry every failure immediately: transient network errors may recover, while a bad URL or missing selector will not. Bound retries and avoid launching multiple copies of the same expensive capture at once.

The research documentation provides no universal runtime, throughput, or cost benchmark for Playwright PDF conversion. Your cost depends on where the browser runs, resource use, and job volume. Measure representative pages in your own environment, including long pages and pages with substantial images. For a hosted alternative, ScreenshotNeo’s stated plans are Free: 1,000 shots/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. See ScreenshotNeo for the product details.

8. Frequently asked questions

Can page numbers start at a custom value?

The documented template placeholder supplies the current page number and total page count. For custom numbering rules, the cited API material does not describe a starting-offset option; verify support in the documentation for your installed Playwright version before relying on one.

Can I put the page number in the header instead?

Yes. Enable displayHeaderFooter and place the same documented placeholder classes in headerTemplate.

No. Page styles are not visible inside header and footer templates, so include the needed styling inline.

Does the PDF include the page’s background graphics by default?

No. Set printBackground: true when those graphics should appear.

References