Playwright PDF Generation: URL & HTML to PDF Guide (2026)

Profile

Written By Hanzala Saleem

Updated At July 07, 2026 | 6 min read

This guide shows how to generate a PDF from a live URL and from a raw HTML string using Playwright's page.pdf() method. It is the same approach behind most invoice and report generators: render markup in a real browser, then export the exact result to PDF. We cover the full option set, the Chromium-only limitation that trips up most first attempts, how to wait for dynamic pages before exporting, and Node, Python, and C# examples you can run today.

Quick answer: Playwright generates a PDF with the page.pdf() method, which renders a URL or an HTML string through Chromium's print engine and writes the result to a file or a buffer. Navigate to a page, then call page.pdf({ path: "out.pdf" }). The method runs only in headless Chromium. Firefox and WebKit do not support it. Options such as format, margin, printBackground, and displayHeaderFooter control how the output looks.

page.pdf() runs only in headless Chromium

Before writing any code, know the one limit that causes most failed attempts: page.pdf() works only in Chromium, and only in headless mode. If you launch Firefox or WebKit and call page.pdf(), Playwright throws an error because those engines do not implement PDF export. Every example below launches Chromium for that reason. If you need a PDF from a workflow that also drives Firefox or WebKit, run a separate headless Chromium step just for the export, or hand the URL to a managed PDF API that owns the browser layer for you.

Installing Playwright

Using yarn

yarn add playwright

Using npm

npm install playwright

Creating a PDF using Playwright

Once you’ve got Playwright installed, we’ll now write a function that accepts a website URL as a parameter and saves the PDF to a specified path locally.

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

const createPDF = async (websiteUrl) => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();
  await page.goto(websiteUrl);
  await page.pdf({ path: "my_pdf.pdf" });
  await browser.close();
};

This code launches a Chromium browser, navigates to the URL passed to the function, exports the page to PDF, and saves it locally as my_pdf.pdf. One change makes this reliable on real sites: pass waitUntil: "networkidle" to page.goto() so Playwright waits for the page to stop loading before it exports. Without it, pages that render content with JavaScript or lazy-load images often produce a half-empty PDF.

Generate the same PDF in Python and C#

The pdf method exists in every official Playwright language binding, so you are not limited to Node. The Chromium-only rule applies to all of them.

Python:

from playwright.sync_api import sync_playwright

def create_pdf(website_url: str, output_path: str = "my_pdf.pdf") -> None:
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.goto(website_url, wait_until="networkidle")
        page.pdf(path=output_path, format="A4", print_background=True)
        browser.close()

C# (.NET):

using Microsoft.Playwright;

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();
await page.GotoAsync("https://example.com", new() { WaitUntil = WaitUntilState.NetworkIdle });
await page.PdfAsync(new PagePdfOptions
{
    Path = "example.pdf",
    Format = "A4",
    PrintBackground = true
});

PDF Options

The pdf method accepts these options to control the output. The four in bold below are the ones you will reach for most often.

OptionWhat it doesDefault
printBackgroundPrints background colors and images. Turn this on or your PDF loses most styling.false
formatPaper size such as A4 or Letter. Takes priority over width and height.Letter
marginSets top, bottom, left, and right margins. Required space for headers and footers to show.0
displayHeaderFooterEnables the header and footer templates below.false
headerTemplate / footerTemplateHTML for the print header and footer. Use the classes date, title, url, pageNumber, totalPages to inject live values.empty
landscapeSwitches orientation to landscape.false
scaleZoom of the rendered page, from 0.1 to 2.0. Use below 1 to fit more per page.1
pageRangesExports only chosen pages, for example 1-3,5.all pages
width / heightExplicit paper size with units, for example 8.5in. Ignored if format is set.none
preferCSSPageSizeLets a CSS @page size in the page override width, height, and format.false
pathFile path to write the PDF to. Omit it to get a Buffer back instead, which is better for API responses.none

Two behaviors catch people out. First, printBackground is off by default, so backgrounds vanish unless you set it to true. Second, if colors still look washed out, add the CSS rule -webkit-print-color-adjust: exact to the page, because Chromium adjusts colors for print. If you want the page to render with its screen styles rather than print styles, call await page.emulateMedia({ media: "screen" }) before page.pdf().

You can add a custom header and footer by passing headerTemplate and footerTemplate and setting displayHeaderFooter to true. Two rules make this actually work: use the class attribute class (not the React className), and reserve room with margin, because a header or footer with no margin space simply does not render. Playwright fills the special classes date, title, url, pageNumber, and totalPages with live values.

Converting HTML to PDF using Playwright

To generate a PDF from an HTML file or string, load the markup with page.setContent() instead of navigating to a URL, then call page.pdf(). This is how most invoice and report generators work: you keep one HTML template, inject dynamic values such as customer name and line items, set the content, and export. Pass printBackground: true so template colors survive, and use waitUntil inside setContent if your template pulls in remote fonts or images.

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

const htmlToPdf = async (html = "<div>ScreenshotAPI</div>") => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();
  await page.setContent(html, { waitUntil: "networkidle" });

  await page.pdf({
    path: "my_pdf.pdf",
    format: "A4",
    printBackground: true,
  });

  await browser.close();
};

Generating a PDF and uploading it to S3

Make sure to install the AWS sdk before we begin.

Using yarn

yarn add aws-sdk

Using npm

npm install aws-sdk

Next, let's write a function that generates a PDF and uploads it to S3. It accepts websiteUrl, bucketName, and fileName as input and returns the uploaded file object as output.

const { chromium } = require("playwright");
const AWS = require('aws-sdk');

AWS.config.update({
  accessKeyId: "YOUR_ACCESS_KEY",
  secretAccessKey: "YOUR_SECRET_KEY",
  region: "YOUR_REGION",
});

const s3 = new AWS.S3();

const generatePDFAndUpload = async (websiteUrl, bucketName, fileName) => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  
  const page = await context.newPage();
  await page.goto(websiteUrl);
  
  const pdf = await page.pdf();
  const params = {
    Bucket: bucketName,
    Key: fileName,
    Body: pdf,
  };
  const uploadedFile = await s3.upload(params).promise();
  
  await browser.close();
  return uploadedFile;
};

Reliably generating PDFs of websites at scale

Writing a function to create a PDF with Playwright is quick. Running it at scale is not. Each headless Chromium instance uses roughly 300 to 500 MB of RAM, so at real volume you need process pooling and hard memory limits or the host runs out of memory. On top of that you manage proxies, cookie banners, missing server fonts that turn text into empty boxes, and caching. None of that is Playwright's job, and all of it becomes yours.

This is the point where a managed API earns its place. ScreenshotAPI exposes a single endpoint that converts a URL or HTML to PDF and handles the browser fleet, fonts, ad and cookie-banner removal, and caching for you. It processes millions of captures every month and is trusted by companies including crypto.com, dentsu, and e.ventures.

First Image
screenshot api customers

Hope the tutorial was helpful. Don’t forget to give screenshotapi.net a shot, we have a 7-day free trial that allows you to capture up to 100 screenshots or PDFs.

Frequently Asked Questions

Does Playwright support PDF generation in Firefox and WebKit?

No. Playwright's page.pdf() works only in headless Chromium. Firefox and WebKit do not implement it, and calling the method on those browsers throws an error. Launch Chromium specifically for PDF work, or use a managed API that owns the browser engine so the choice never blocks you.

Why is my Playwright PDF missing background colors or images?

Background graphics are off by default. Set printBackground: true in the page.pdf() options to include them. If colors still look faded, add the CSS rule -webkit-print-color-adjust: exact to the page, because Chromium adjusts colors for print unless you force exact rendering.

How do I generate a PDF from an HTML string instead of a URL?

Load the markup with page.setContent(htmlString) instead of page.goto(url), then call page.pdf(). This pattern powers most invoice and report tools: keep an HTML template, inject dynamic values, set the content, and export. Set printBackground: true if the template uses background colors.

How do I add page numbers to a Playwright PDF?

Set displayHeaderFooter: true and give a footerTemplate containing spans with the classes pageNumber and totalPages. You must also reserve space with the margin option, for example 80px top and bottom, or the footer will not appear in the exported file.

Can Playwright generate PDFs in Python and C#?

Yes. The pdf method exists in every official Playwright binding. In Python, call page.pdf(path="out.pdf") inside a sync_playwright() block. In C# call page.PdfAsync(new PagePdfOptions { Path = "out.pdf" }). The Chromium-only limitation applies in every language.