Get started

How to convert a webpage to PDF (free, no install)

Convert any webpage to PDF using a free browser tool or automate with Playwright. Covers JavaScript pages, lazy loading, and bot-detection workarounds.

benoitdedApril 10, 20268 min read

Converting a webpage to PDF loads the full URL in a headless Chromium browser, renders JavaScript and CSS, and exports the result as a PDF. The free PDF4.dev tool at pdf4.dev/tools/webpage-to-pdf handles this in seconds, no software needed. For automation and authenticated pages, Playwright gives full control.

How webpage-to-PDF works

A webpage-to-PDF conversion is not a screenshot. It uses Chromium's print engine, the same one behind Chrome's "Save as PDF" feature, running in headless mode:

  1. A headless Chromium instance opens the URL
  2. The browser loads HTML, CSS, JavaScript, images, and web fonts
  3. Scripts execute: React hydration, data fetching, dynamic rendering
  4. The browser waits for the page to reach a stable state
  5. page.pdf() renders the full document using Chromium's paged media engine

The result matches what you would see if you opened the URL in Chrome and printed to PDF.

URL-to-PDF vs HTML-to-PDF

FeatureURL-to-PDFHTML-to-PDF
InputA live URLA raw HTML string
AuthenticationRequires public URL (or custom cookie handling)Full control — pass any HTML
Dynamic contentLoaded by the browser at runtimeStatic; you control the markup
Network dependencyYesNo
Best forCapturing existing pagesGenerating documents from templates

For generating invoices, reports, and certificates from data, HTML-to-PDF is more reliable. URL-to-PDF is the right choice when the page already exists and renders itself.

Method 1: Free browser tool (no install)

PDF4.dev's webpage-to-PDF tool converts any public URL to PDF without installing anything.

  1. Go to pdf4.dev/tools/webpage-to-pdf
  2. Paste the URL (must be publicly accessible)
  3. Choose paper format (A4 or Letter) and margins
  4. Click Convert to PDF and download

The server fetches the page with Playwright, auto-scrolls to trigger lazy loading, injects CSS to disable animations, and returns a downloadable PDF. Files are not stored after delivery.

Works well for:

  • JavaScript-rendered pages (React, Vue, Angular, Next.js)
  • Pages with web fonts
  • Full-page capture including content below the fold

Limitations:

  • Pages behind authentication cannot be accessed
  • Infinite scroll content only loads to the depth the auto-scroll reaches
  • Pages that actively block headless browsers will fail

Method 2: Playwright in Node.js or Python

For automation, CI/CD pipelines, or pages that require authentication, Playwright gives direct control over every step.

Install Playwright

npm install playwright
npx playwright install chromium

Basic URL-to-PDF

import { chromium } from "playwright";
import fs from "fs";
 
async function urlToPdf(url: string, outputPath: string): Promise<void> {
  const browser = await chromium.launch();
  const page = await browser.newPage();
 
  await page.goto(url, { waitUntil: "networkidle" });
 
  const pdf = await page.pdf({
    format: "A4",
    margin: { top: "20mm", bottom: "20mm", left: "15mm", right: "15mm" },
    printBackground: true,
  });
 
  fs.writeFileSync(outputPath, pdf);
  await browser.close();
}
 
urlToPdf("https://example.com/report", "report.pdf");

waitUntil: "networkidle" waits until there are no network requests for 500ms. This ensures client-side rendered content (React, Next.js, SWR data fetching) has finished loading before the PDF is captured.

For Python, install with:

pip install playwright
playwright install chromium

Handling lazy-loaded images

Pages using loading="lazy" or IntersectionObserver defer image loading until the element is near the viewport. Headless Chromium has a limited default viewport height, so images below the fold may not load.

Scroll through the full page height before capturing to trigger them:

// Call this after page.goto(), before page.pdf()
await page.evaluate(async () => {
  await new Promise<void>((resolve) => {
    let scrolled = 0;
    const step = 300;
    const timer = setInterval(() => {
      window.scrollBy(0, step);
      scrolled += step;
      if (scrolled >= document.body.scrollHeight) {
        clearInterval(timer);
        window.scrollTo(0, 0); // scroll back to top before PDF capture
        resolve();
      }
    }, 100);
  });
});
 
// Wait for any newly triggered network requests to settle
await page.waitForLoadState("networkidle");

Disabling animations

Animations freeze at their current frame in a PDF. To get a clean, static render, inject CSS that stops all transitions and animations:

await page.addStyleTag({
  content: `
    *, *::before, *::after {
      animation-duration: 0s !important;
      animation-delay: 0s !important;
      transition-duration: 0s !important;
      transition-delay: 0s !important;
    }
  `,
});

This disables CSS animations and transitions, including those driven by Framer Motion and GSAP (which use CSS under the hood). Do this before calling page.pdf().

Avoiding bot detection

Many sites block headless Chromium by checking for signals that identify automated browsers. The most common signals are a missing user agent, the navigator.webdriver property set to true, and headless-specific Chromium flags.

const browser = await chromium.launch({
  args: ["--disable-blink-features=AutomationControlled"],
});
 
const context = await browser.newContext({
  userAgent:
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
    "(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
  viewport: { width: 1280, height: 800 },
});
 
const page = await context.newPage();
 
// Remove the webdriver flag
await page.addInitScript(() => {
  Object.defineProperty(navigator, "webdriver", { get: () => undefined });
});
 
await page.goto("https://example.com");

These adjustments cover most basic detection. For sites with advanced anti-bot systems (Cloudflare Turnstile, Akamai Bot Manager), a stealth browser service provides pre-configured evasion and is more reliable than manual flag tuning.

Only convert pages you are authorized to access and archive. Bypassing detection on sites that explicitly block automated access may violate their terms of service.

Consent dialogs block the content behind them and appear in the PDF. Dismiss them before capturing:

await page.goto(url, { waitUntil: "networkidle" });
 
// Try to click the accept button (common class/id patterns)
try {
  await page.click(
    '[id*="accept"], [class*="accept-all"], button:has-text("Accept")',
    { timeout: 3000 }
  );
  await page.waitForLoadState("networkidle");
} catch {
  // No banner found, continue
}
 
const pdf = await page.pdf({ format: "A4", printBackground: true });

Controlling screen vs print rendering

By default, Playwright captures the page in screen mode, which matches the visual appearance in a browser. For pages with explicit @media print CSS rules (hiding navigation, resetting backgrounds), switch to print media before capture:

await page.emulateMedia({ media: "print" });

For most URL-to-PDF conversions, screen rendering is the right choice. Print media makes sense when the target site has dedicated print styles. The CSS print styles guide covers how to write print CSS that produces clean PDFs from HTML.

Reusing a browser instance for batch conversion

Launching a new browser process for each URL is slow. For batch jobs, launch one browser and reuse it across pages:

const browser = await chromium.launch();
const urls = ["https://example.com/page1", "https://example.com/page2"];
 
for (const url of urls) {
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: "networkidle" });
  const pdf = await page.pdf({ format: "A4", printBackground: true });
  fs.writeFileSync(`output-${Date.now()}.pdf`, pdf);
  await page.close(); // close the page, keep the browser
}
 
await browser.close();

Each page takes roughly 1-3 seconds with a warm browser, depending on the target page's complexity.

Common problems and fixes

ProblemCauseFix
Blank PDFJS rendering incompleteUse waitUntil: "networkidle" or add page.waitForSelector()
Missing imagesLazy loading not triggeredScroll the full page before page.pdf()
Fonts not renderingWeb font not loadedWait for networkidle before capture
Layout brokenPrint CSS overriding screen stylesUse page.emulateMedia({ media: "screen" })
Page cut off mid-contentFixed-height container in CSSInject html, body { height: auto !important; overflow: visible !important; }
403 / access deniedBot detectionSet realistic user agent, remove navigator.webdriver
Consent banner in PDFModal not dismissedClick accept button before capture
Images from CDN missingAuthentication-gated assetsRun the browser with the correct cookies for that domain

Generating PDFs from templates (for structured documents)

URL-to-PDF is best for capturing existing pages. For generating structured documents (invoices, reports, certificates) from data, a template-based HTML-to-PDF approach gives more control over layout and produces leaner files. The PDF4.dev API handles this with a single POST request: pass a template ID and data, receive a PDF.

For deep-dive comparisons of Playwright vs. Puppeteer, see Playwright vs Puppeteer for PDF generation.

Related tools: convert webpage to PDF · HTML to PDF · compress PDF

Free tools mentioned:

Webpage To PdfTry it freeHtml To PdfTry it freeCompress PdfTry it free

Start generating PDFs

Build PDF templates with a visual editor. Render them via API from any language in ~300ms.