Recommended Free Tools
Choose the library based on what your HTML represents. For a real webpage that needs its JavaScript, CSS, and fonts rendered, use Puppeteer with Chromium and page.pdf(). For an export button running in the visitor’s browser, use html2pdf.js. For a PDF built from application data rather than existing HTML, use PDFKit. If you want Chromium rendering without operating the browser yourself, use a hosted HTML-to-PDF API.
The key distinction is whether you need to render existing HTML or create a document from scratch. That choice determines fidelity, runtime, layout control, and how much infrastructure you must maintain.
Which JavaScript HTML-to-PDF option should you use?
| Option | Best fit | Where it runs | What it renders | Main trade-off |
|---|---|---|---|---|
| Puppeteer | Existing pages whose JavaScript and CSS need browser rendering | Node.js with Chromium, or a controlled browser | HTML rendered by Chromium | You must deploy and operate a browser process |
| html2pdf.js | A client-side “Export to PDF” action for a page or element | Browser only | Canvas-based rendering through html2canvas and jsPDF | Complex layouts, large documents, and text selection need testing |
| PDFKit | Invoices, reports, and other documents whose layout your app controls | Node.js or browser build | Text, images, and drawing primitives placed through its API | You build the layout; it does not convert arbitrary HTML/CSS |
| Hosted Chromium API | HTML-to-PDF conversion without maintaining local Chromium | External service | A public URL or raw HTML rendered in headless Chromium | Network latency, credentials, vendor dependence, and data processing |
For general-purpose conversion of an existing webpage, Puppeteer is the strongest default among these options: it uses a browser engine and exposes print settings. The Puppeteer guide recommends Page.pdf() for printing PDFs. For a smaller client-side export, html2pdf.js is more convenient. Choose PDFKit only when reconstructing the document with a drawing API is acceptable.
Convert a webpage to PDF with Puppeteer
Puppeteer launches Chromium, navigates to a page, and saves the browser’s print rendering as a PDF. Install it in a Node.js project with npm install puppeteer. Puppeteer downloads a compatible browser as part of its normal installation; deployment environments may need additional system libraries or browser configuration.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
import puppeteer from 'puppeteer';
const url = 'https://example.com';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true
});
} finally {
await browser.close();
}
Save this as an ES module (for example, convert.mjs) and run node convert.mjs. The resulting page.pdf is generated with print media styles. This default matters: a page designed primarily for screen may change when printed.
Wait for the right content, not just navigation
waitUntil: 'networkidle2' waits for the network to become mostly idle; it does not guarantee that every application has finished rendering. A single-page app may fetch data after navigation, defer images, or render content in response to a later event. If the PDF is missing a chart, price, or other late-loaded content, wait for a page-specific signal before calling page.pdf():
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready="true"]');
await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });
Replace the selector with one that your page sets only after its content is ready. A fixed delay can help with a known animation or delayed widget, but it is less reliable than waiting for a meaningful selector. Puppeteer waits for fonts to load by default; still check the output in the actual deployment environment, especially if fonts or images come from remote origins.
Choose media, color, and page geometry
By default, page.pdf() uses the page’s CSS print media type. Use this for documents with print-specific styles, such as hidden navigation or a different column layout. If you need the screen styling instead, set the media type first:
await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-layout.pdf', printBackground: true });
The PDF API can set paper format, dimensions, margins, landscape orientation, and whether to include background graphics. Use the page’s print CSS for predictable page breaks and print-only adjustments; use PDF options for the document-level paper settings. Chromium modifies print colors by default. To preserve exact CSS colors, apply -webkit-print-color-adjust: exact in the print styles, then verify the result in the generated PDF.
Rank #2
Example print CSS:
@media print {
.screen-only { display: none; }
.report-section { break-inside: avoid; }
body { -webkit-print-color-adjust: exact; }
}
Long tables, oversized images, and sections taller than a page can split in awkward places. Test the actual content rather than assuming a rule such as break-inside: avoid can keep every element intact; a block that cannot fit on one page may still need to split.
Generate a PDF in the browser with html2pdf.js
Use html2pdf.js when the user is already viewing the page and should export a selected element without sending its content to a server. It combines html2canvas and jsPDF, and its package documentation specifies that it runs in a browser, not Node.js.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Invoice export</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
</head>
<body>
<main id="invoice">
<h1>Invoice</h1>
<p>Invoice content goes here.</p>
</main>
<button id="export" type="button">Save as PDF</button>
<script>
document.querySelector('#export').addEventListener('click', () => {
html2pdf().set({
margin: 0.4,
filename: 'invoice.pdf',
pagebreak: { mode: ['css', 'legacy'] },
jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
}).from(document.querySelector('#invoice')).save();
});
</script>
</body>
</html>
The bundle URL above is the version used in the package’s documented example. For production, decide how your project manages third-party scripts and versions rather than relying on an unpinned or changing asset. Calling html2pdf(document.body) is also supported when the entire page is the intended export.
Canvas-based conversion is not identical to printing a live browser page. Validate whether text remains selectable in your output, whether cross-origin images appear, and how page breaks behave in long tables or multi-page content. Large elements can use substantial browser memory. If fidelity to browser print layout or complex interactive content is essential, test Puppeteer as an alternative.
Build the PDF directly with PDFKit
PDFKit is appropriate when you own the document’s structure and can draw or place its contents explicitly. It supports Node.js streams and can write to a file or HTTP response. Install it with npm install pdfkit. This example writes a simple PDF file:
import PDFDocument from 'pdfkit';
import fs from 'node:fs';
const doc = new PDFDocument({ size: 'A4', margin: 50 });
doc.pipe(fs.createWriteStream('report.pdf'));
doc.fontSize(20).text('Monthly report', { underline: true });
doc.moveDown();
doc.fontSize(12).text('Revenue increased this month.');
doc.end();
For an HTTP response, pipe the document to the response stream instead of a file stream, set a PDF content type, and call doc.end() when the content has been added. Do not buffer a large PDF as a string: PDF is binary data, and the stream is the appropriate way to send it.
PDFKit’s API is chainable and supports text, drawing, and JPEG/PNG assets; its project documentation also lists TrueType, OpenType, WOFF, and WOFF2 font support. That flexibility comes with a cost: reproducing a complicated HTML page means rebuilding its layout and behavior as PDFKit operations. Use it when precise control over a known document model matters more than rendering an arbitrary webpage.
Use a hosted HTML-to-PDF API when you do not want to run Chromium
A hosted service can accept a publicly reachable URL or raw HTML and return PDF bytes, while performing conversion in headless Chromium. This avoids deploying a local browser, but shifts some operational concerns to the service and network.
For example, html2pdf.app documents an authenticated POST API. Before adopting a hosted converter, establish how credentials are stored, what data is sent, and whether the page’s resources are reachable by the service. Check the response status before saving the body, and handle it as binary bytes rather than decoding it as text. CSS media mode, remote fonts and images, and JavaScript timing can all affect the result.
A hosted API is a reasonable fit when avoiding browser installation is more important than controlling the entire conversion path. It is not automatically more reliable or more private: account for network failures, service dependency, latency, and the sensitivity of the HTML or URL you submit.
Rank #4
Or skip the browser setup
If your goal is to capture a webpage rather than build a PDF from application data, ScreenshotNeo is a website screenshot API that can return a screenshot or PDF. It also has an MCP server for AI agents. Its one-call API avoids configuring a local browser; the JavaScript methods above remain the better fit when you need to convert an arbitrary HTML document or control a Chromium PDF layout directly.
Free tools Windows power users keep installed
One-click scans. No signup required.
The following cURL example saves a clean screenshot as WebP. For PDF output and other request options, see the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the page verdict and billing status in headers. Its MCP tools include take_screenshot, get_page_info, and capture_pdf, so an AI agent using Claude, Cursor, or another MCP client can request captures. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.
Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month with no card.
Common conversion problems and fixes
- The PDF is blank or missing dynamic content. Navigation completed before the app finished rendering. Wait for a page-specific readiness selector or an application event, then capture. Confirm the selector actually appears in the target environment.
- Fonts, images, or styles are missing. Check that remote resources are reachable from the browser or hosted converter and are not blocked by authentication or cross-origin restrictions. Wait for the content to load; Puppeteer’s default font wait does not fix inaccessible font files.
- The colors or layout differ from the browser. Puppeteer uses print media by default. Add print CSS, or call
page.emulateMediaType('screen')if screen styles are intended. EnableprintBackgroundand use-webkit-print-color-adjust: exactwhen exact CSS colors are needed. - A table splits badly across pages. Add and test print page-break rules on rows or sections, simplify oversized content, or divide the table into more manageable sections. No CSS rule can keep an element together if it is taller than a page.
- html2pdf.js output is slow, huge, or visually wrong. Reduce the exported region, test the content’s page breaks, and check cross-origin assets. For large documents or layouts that need browser-print behavior, compare a Puppeteer implementation.
- Puppeteer works locally but not in deployment. Confirm the deployed environment has a compatible Chromium binary and required system libraries, and that the process has permission to launch it. Keep browser closure in a
finallyblock so failed navigation or PDF generation does not leave browser processes behind. - A hosted API returns an error or a corrupt file. Inspect the HTTP status and response headers before writing the body. Ensure the URL is publicly reachable or the raw HTML is submitted as required, and preserve the response as bytes rather than converting it to text.
Performance, reliability, and cost considerations
Puppeteer gives control over the rendering environment but adds browser startup, memory use, and deployment weight. For repeated conversions, consider reusing a browser process while creating an isolated page for each job; close pages and the browser at the appropriate lifecycle boundary. Set timeouts and impose limits on concurrent jobs so a slow or resource-heavy page cannot exhaust the service.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Browser-side html2pdf.js avoids a server conversion request, but consumes the visitor’s browser memory and processing time. Large images and long pages can make export slow. PDFKit avoids browser rendering but requires your own layout implementation. A hosted API trades local browser operations for a network request and a dependency on an external provider. In every model, measure against your own page types and target runtime: the documentation does not establish a universal speed or output-size winner.
Best Value
For reliability, treat readiness, resources, page breaks, and output validation as part of conversion—not as afterthoughts. Keep a representative set of pages and documents for regression checks, including long content, custom fonts, images, and print styles. For hosted conversions, include status-code handling and retry behavior appropriate to your application; do not retry indefinitely or send sensitive content without assessing its handling.
Practical choice
- Use Puppeteer for a website or app page where Chromium’s JavaScript and CSS rendering are important.
- Use html2pdf.js for a small client-side export feature when its canvas-based output meets your document needs.
- Use PDFKit for generated documents whose structure and layout your code owns.
- Use a hosted Chromium API when you want URL-or-HTML conversion without maintaining Chromium, and accept the service and data-processing dependency.
Whichever route you choose, test the final PDF—not just the source HTML—in the deployment environment and with the actual content users will export.
Frequently Asked Questions
Can I use html2pdf.js in a Node.js backend?
No. Its package documentation says html2pdf.js must run in a browser. Use Puppeteer or a server-side PDF-generation approach for Node.js.
Does PDFKit convert an existing HTML page automatically?
No. PDFKit creates PDFs through a drawing and document API; it is not an HTML/CSS webpage renderer.
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.

