Get your API key
How to convert a webpage to PDF (free, no install)

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.

8 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

Frequently asked questions

How do I convert a webpage to PDF for free?
Use PDF4.dev's free tool at pdf4.dev/tools/webpage-to-pdf. Paste any public URL and click Convert. A real Chromium browser fetches the page, renders it fully (JavaScript included), and returns a PDF. No account or software required.
Why does the PDF look different from printing in Chrome?
Chrome's print dialog applies @media print CSS rules: it removes navigation, resets backgrounds, and adjusts margins. A headless tool uses screen rendering by default, which matches what the page looks like in the browser visually. Use page.emulateMedia({ media: 'print' }) in Playwright to apply print CSS instead.
Can I convert a page that requires login?
The free browser tool cannot authenticate. For login-gated pages, use Playwright or Puppeteer in your own code where you can set cookies, complete a login form, or restore a session before capturing the PDF.
What if the page has lazy-loaded images?
PDF4.dev's tool auto-scrolls the full page height before rendering to trigger lazy loading. In your own Playwright script, scroll the page manually and then call page.waitForLoadState('networkidle') before generating the PDF.
Will animations appear correctly in the PDF?
No. PDFs are static documents. Animations are frozen at their current frame. PDF4.dev injects CSS to set animation-duration and transition-duration to 0s before capture, so you get a clean frozen state rather than a mid-animation screenshot.
How is URL-to-PDF different from HTML-to-PDF?
URL-to-PDF fetches live content from a server (including server-rendered state, dynamic data, and authenticated sessions). HTML-to-PDF takes a raw HTML string you provide. URL-to-PDF is easier for existing pages; HTML-to-PDF gives full control when generating documents from templates.
Can I convert a React or Next.js page to PDF?
Yes. Playwright with waitUntil: 'networkidle' waits for React hydration and data fetching to complete before capturing. For pages using React Query or SWR, add an explicit page.waitForSelector() call to confirm the data has rendered before generating the PDF.
Can I convert multiple URLs to PDF at once?
The free tool handles one URL at a time. For batch conversion, use the Playwright Node.js approach in a loop, reusing a single browser instance to avoid launching a new process for each URL.
Does the PDF include web fonts?
Yes. Web fonts loaded via @font-face are fetched and embedded in the PDF as long as they are accessible from the server running the headless browser. Self-hosted or CDN-hosted fonts both work.
What is the best way to avoid bot detection when converting a webpage?
Set a realistic user agent string, configure a standard viewport (1280x800), and remove the navigator.webdriver property that identifies headless Chromium. For sites with advanced detection (Cloudflare, Akamai), a stealth browser service is more reliable than manual flag manipulation.

Start generating PDFs

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