Free tools Windows power users keep installed
One-click scans. No signup required.
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.
Recommended Free Tools
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.
#1 Best Overall
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:
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →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().
Rank #2
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.
@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.
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.
Rank #4
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
finallyblocks 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/pdffor HTTP responses.
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.
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.
Best Value
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.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchFor 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.
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.
Quick Recap
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.

