Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

The most reliable way to convert an HTML file to PDF with JavaScript is to render it in a real browser engine, then call page.pdf(). Puppeteer and Playwright preserve CSS layout, web fonts, images and JavaScript-driven content far better than manually drawing text into a PDF. Use an absolute file:// URL for a local document, wait until its assets and data are ready, set paper and margin options explicitly, and add print CSS for predictable page breaks.

Choose a browser-based converter

HTML-to-PDF conversion is a rendering problem, not merely a text-export problem. A browser evaluates CSS, loads fonts and images, runs scripts and computes page layout before producing the PDF. Puppeteer drives Chromium; Playwright can drive Chromium and also supports Firefox and WebKit for broader browser testing. For a PDF job, choose based on the browser engines, installation footprint, API style and the amount of control you need over authentication and readiness.

Library Best fit PDF behavior to know
Puppeteer Chromium-based server jobs and teams already using the Puppeteer API page.pdf() uses print CSS by default, waits for fonts by default, and supports paper formats, margins, backgrounds and an in-memory result.
Playwright Projects that also need cross-browser automation or Playwright conventions page.pdf() supports standard formats such as A4 and Letter, width and height, CSS units and margin options; print media is the default.

Convert a local HTML file with Puppeteer

Install Puppeteer in a Node.js project, then pass an absolute path as a file:// URL. The following complete program writes an A4 PDF with backgrounds enabled and explicit margins.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('file:///absolute/path/report.html', {
    waitUntil: 'networkidle2'
  });
  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true,
    margin: { top: '16mm', right: '14mm', bottom: '16mm', left: '14mm' }
  });
} finally {
  await browser.close();
}

Replace /absolute/path/report.html with the real path. A relative path, a Windows path without the file:/// prefix, or a path containing characters that are not URL-encoded can produce a navigation error. Resolve the path in your application and construct a correctly formed file URL when paths are supplied by users.

Return PDF bytes instead of writing a file

Omit path when the PDF must be uploaded, returned from an HTTP endpoint or stored in object storage. The call returns PDF bytes as a buffer-like value.

const pdfBytes = await page.pdf({
  format: 'A4',
  printBackground: true,
  margin: { top: '16mm', right: '14mm', bottom: '16mm', left: '14mm' }
});
// Example: res.type('application/pdf').send(Buffer.from(pdfBytes));

Convert a URL or dynamic page

The same flow works for an HTTPS page. Navigate to the URL, wait for a condition that represents completed rendering, then create the PDF.

await page.goto('https://example.com/invoice/123', {
  waitUntil: 'networkidle2'
});
await page.pdf({
  path: 'invoice.pdf',
  format: 'A4',
  printBackground: true
});

networkidle2 is a useful baseline for pages that fetch data, but it is not a universal “ready” signal. Long-polling, analytics, advertisements or delayed charts can keep a page busy or finish after network idle. Prefer an application-specific readiness marker when possible:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.goto('https://example.com/report', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-pdf-ready="true"]');
await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });

If your page exposes a promise, you can wait for it with page.evaluate before printing. Keep the wait bounded with a timeout so a failed data request does not leave a worker hanging indefinitely.

Playwright implementation

Playwright uses the same browser-rendering approach. Install it, launch Chromium, navigate to the file, and call page.pdf().

import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.goto('file:///absolute/path/report.html', {
    waitUntil: 'networkidle'
  });
  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true
  });
} finally {
  await browser.close();
}

Use width and height when a custom page size is required, or use CSS units such as mm, cm, in and px. Standard formats such as A4 and Letter are usually easier to share across systems.

Control print CSS and page geometry

Both documented APIs generate PDFs with the print CSS media type by default. A screen layout can therefore look different in the PDF. Put print-only rules in a stylesheet and define the paper size and margins in one place.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@media print {
  .no-print { display: none !important; }
  h1, h2, h3 { break-after: avoid; }
  table, figure { break-inside: avoid; }
  @page { size: A4; margin: 16mm 14mm; }
}

Use break-before, break-after and break-inside to keep headings with the content they introduce and prevent tables or figures from splitting where a split would be confusing. Older page-break-* properties may still be needed for legacy layouts, but the modern break-* properties are clearer.

Use screen styles intentionally

If the screen design is deliberately the source of truth, switch media before printing. In Puppeteer:

await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-layout.pdf', printBackground: true });

In Playwright:

await page.emulateMedia({ media: 'screen' });
await page.pdf({ path: 'screen-layout.pdf', printBackground: true });

Do this only when you have checked that navigation bars, animations and responsive breakpoints make sense on paper. Otherwise, keep the default print media and maintain a dedicated print stylesheet.

Preserve colors and backgrounds

Set printBackground: true when colored panels, background images or charts are part of the document. Browsers may otherwise modify colors for printing. If exact color reproduction matters, add:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
html {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

Exact color output can consume substantial ink and may reduce readability on office printers, so inspect the result on the target printer or viewer.

Fonts, images and other assets

A PDF can contain missing glyphs, blank images or unstyled markup when its dependencies cannot be resolved. For local files, use absolute asset paths or a predictable directory layout. For web fonts, ensure the font files are reachable and wait for the document to finish loading; Puppeteer’s PDF API waits for fonts by default.

  • Use URL-encoded absolute file:// paths for local stylesheets, scripts and images.
  • Give images intrinsic dimensions or stable CSS dimensions to reduce layout shifts.
  • Wait for application data and charts, not just the initial HTML response.
  • Check the generated PDF for fallback fonts, clipped text and missing SVG or canvas content.

Treat untrusted HTML as executable browser input. Scripts in the document can access whatever the browser context can access, and local-file permissions can expose sensitive data if configured carelessly. Sanitize input or isolate the conversion process according to your threat model; do not load arbitrary user files in a privileged environment.

Authentication and browser context

Private pages require the same setup as any browser automation task. Create a context with the needed cookies, headers or authentication state, then navigate and wait for the page’s real ready condition. Avoid placing secrets in the HTML itself. For multi-tenant systems, create an isolated browser context per job and clear it after the PDF is produced.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Production reliability, performance and cost

Launching Chromium for every document is simple but adds startup time and memory overhead. A service that handles many jobs can keep a browser process warm while creating a fresh page or context for each conversion. Limit concurrent pages to the memory available on the host, and recycle the browser after repeated crashes or suspected leaks.

  • Lifecycle: close pages, contexts and browsers in finally blocks so failures do not leave processes running.
  • Timeouts: set navigation and readiness timeouts; report whether the failure was navigation, missing data or PDF creation.
  • Sandboxing: use the browser sandbox where your deployment permits it. If a container requires a different configuration, review the security impact instead of copying unsafe flags blindly.
  • Determinism: pin browser and library versions, fonts and timezone settings when PDFs are compared byte-for-byte or used in regulated workflows.
  • Concurrency: queue jobs and apply back-pressure rather than allowing unbounded parallel Chromium pages.
  • Output: stream or upload the byte result when files are temporary, and set Content-Type: application/pdf for HTTP responses.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

The PDF is blank or missing data

Cause: printing happened before client-side rendering completed. Fix: wait for a specific selector, a page-defined readiness promise or a bounded delay after the data request. Do not rely on a fixed delay when a deterministic signal is available.

CSS looks different from the browser

Cause: PDF generation uses print media by default, or a print stylesheet hides or changes elements. Fix: inspect @media print rules, then either correct them or call emulateMediaType('screen') / emulateMedia({ media: 'screen' }) deliberately.

Images or web fonts are missing

Cause: inaccessible relative paths, blocked requests or a conversion that ran before assets loaded. Fix: use absolute, reachable URLs; verify file permissions and network access; wait for the relevant resources; and check browser console and request errors.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Headings or tables split badly

Cause: no page-break rules or oversized elements. Fix: apply break-after: avoid to headings, break-inside: avoid to tables and figures, and redesign elements that cannot fit on one page.

Navigation times out

Cause: the page uses long-lived connections, a blocked host or a resource that never completes. Fix: use a suitable navigation event such as domcontentloaded, wait for your own readiness selector, block nonessential resources, and retain a hard overall timeout.

Chromium fails in a server or container

Cause: missing browser binaries or system dependencies, insufficient memory, process limits or an incompatible sandbox configuration. Fix: install the browser and dependencies required by your chosen library, monitor memory and process counts, reduce concurrency, and use a supported sandbox arrangement.

Or skip the browser setup

ScreenshotNeo is a hosted screenshot and PDF API when you do not want to manage Chromium. One GET request can return a PDF, and its cleanup steps accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups and chat widgets before capture. Each response identifies the page verdict and whether it was billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a PDF capture, use the API base and PDF options documented at https://screenshotneo.com/docs/:

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

The same endpoint can be called from Python or Node.js:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also offers PDF paper size, margins, landscape mode and page ranges, plus custom CSS and JavaScript, waits for selectors or network idle, authentication headers and cookies, geolocation, timezone, caching, asynchronous jobs and bulk capture. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools 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 shots. Sign up free for ScreenshotNeo.

FAQ

Can JavaScript convert HTML to PDF in a browser tab?

Client-side JavaScript can open the browser print dialog, but unattended file creation and server-side automation generally require a browser automation library or a hosted rendering API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use A4 or Letter?

Choose the paper standard your recipients use, then set it explicitly rather than relying on the host machine’s defaults. Define matching margins in the PDF options and @page CSS.

Why does a PDF have a different number of pages after a dependency update?

Browser, font, CSS and library changes can alter line wrapping and element heights. Pin versions and include representative documents in regression tests when pagination matters.

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.