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 right JavaScript HTML-to-PDF library depends on where your code runs and what “PDF” means for your application. Use Puppeteer or Playwright when a server or automation job must print a fully rendered page. Use html2pdf.js when a user clicks “Export” in a browser and you need to turn one DOM element into a file. Use jsPDF when you are constructing a document from data, text, and drawing primitives rather than printing existing HTML.

This guide shows working patterns, explains print CSS and page settings, documents the important failure modes, and gives a decision framework for production projects.

Choose the rendering model first

Need Best starting point Why
Print a URL or server-rendered page Puppeteer or Playwright A real browser lays out the page, runs JavaScript, loads fonts and applies print CSS.
Export a selected element in a browser html2pdf.js A short client-side chain converts a DOM element through html2canvas and jsPDF.
Generate invoices, reports or forms from structured data jsPDF You control PDF text and drawing primitives directly instead of reproducing browser layout.

These approaches are not interchangeable. Browser printing generally preserves selectable text and browser layout semantics. html2pdf.js rasterizes the rendered element into a canvas before putting it in a PDF, so its output behaves more like an image of the page. Direct jsPDF generation gives precise document control but requires you to implement layout yourself.

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

Server-side HTML to PDF with Puppeteer

Puppeteer’s documented route is to launch Chromium, navigate to a page, call page.pdf(), then close the browser. The API uses print CSS media by default, and Puppeteer’s PDF guide says font loading is awaited by default.

Install and run

npm install puppeteer
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });
    await page.pdf({
      path: 'page.pdf',
      format: 'A4',
      printBackground: true,
      margin: { top: '16mm', right: '16mm', bottom: '16mm', left: '16mm' }
    });
  } finally {
    await browser.close();
  }
})();

waitUntil: 'networkidle2' is only a navigation condition. Applications with late API calls, charts or user-generated images should wait for an application-specific signal as well, such as a selector that appears after rendering. Do not use an arbitrary delay as your only readiness check when you can expose a deterministic one.

Print CSS and page controls

Because PDF generation uses print media, a stylesheet can deliberately change the document:

@media print {
  .screen-only { display: none !important; }
  .invoice { break-inside: avoid; }
  a { color: #000; text-decoration: none; }
}

Set format or explicit width and height, margins, printBackground: true when colored backgrounds matter, a scale, page ranges, and header/footer templates as supported by the Puppeteer version you pin. Screen colors can differ from printed colors; CSS print-color behavior and browser settings are part of the output, not an afterthought.

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

Populate HTML without a public URL

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setContent(`
      <!doctype html>
      <html><head>
        <style>body{font-family:Arial} h1{color:#174ea6}</style>
      </head><body>
        <h1>Monthly report</h1><p>Generated from application data.</p>
      </body></html>`, { waitUntil: 'networkidle0' });
    await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });
  } finally {
    await browser.close();
  }
})();

For remote fonts, wait for the page’s font state when your application loads them dynamically. Also make sure the browser process has access to every image, stylesheet and font URL; an HTML file that looks correct in your desktop browser can produce missing assets in a restricted server environment.

Server-side HTML to PDF with Playwright

Playwright follows the same lifecycle and supports Chromium, Firefox and WebKit automation. Its API documentation states that page.pdf() generates a PDF with print CSS media.

npm install playwright
import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', { waitUntil: 'networkidle' });
  await page.pdf({
    path: 'page.pdf',
    format: 'A4',
    printBackground: true,
    margin: { top: '16mm', right: '16mm', bottom: '16mm', left: '16mm' }
  });
} finally {
  await browser.close();
}

Use screen styles deliberately

If the PDF should resemble the screen rather than the print stylesheet, emulate screen media before calling pdf():

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

Playwright also exposes options for paper format, dimensions, margins, backgrounds, scaling, page ranges and header/footer content. Names and availability are version-sensitive, so pin the package and verify the API for that release before deploying.

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

Browser-side export with html2pdf.js

html2pdf.js is intended for a browser. Its documented pipeline takes an element, clones or prepares it, renders it with html2canvas, creates a jsPDF document and saves it. The project README explicitly says it does not run in Node.js.

Minimal element export

const element = document.getElementById('element-to-print');
html2pdf().from(element).save();

With npm, bundle the library in your browser build. If you use separate script files, the documented dependency order is jsPDF first, html2canvas second and html2pdf.js third.

Set page size, margins and image quality

const element = document.querySelector('#invoice');
const options = {
  margin: 10,
  filename: 'invoice.pdf',
  image: { type: 'jpeg', quality: 0.95 },
  html2canvas: { scale: 2, useCORS: true },
  jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
};

html2pdf().set(options).from(element).save();

These settings affect the raster image placed in the PDF. A higher canvas scale can improve apparent sharpness but increases memory use and file size. Cross-origin images need appropriate server headers; otherwise the canvas may be tainted or assets may be omitted.

What html2pdf.js cannot guarantee

  • Text is rasterized, so it is not selectable or searchable in the resulting PDF. The project also warns that this can create large files.
  • html2canvas may fail to reproduce some CSS, filters, embedded content or complex browser features.
  • The cloned DOM can expose CSS issues that are not visible in the live page.
  • Resizing the root element can trigger reflow, changing line breaks and pagination.
  • Very large documents can exceed the browser’s maximum canvas dimensions and render blank.
  • Custom Promise implementations may conflict with the library’s expectations.

Use this route for user-initiated exports where an image-like PDF is acceptable. If users must search, copy or select text, prefer browser printing or direct PDF text generation.

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

Generate a PDF directly with jsPDF

jsPDF is a JavaScript PDF-generation library with npm, browser, ES-module and UMD distributions. It is a different workflow from “print this HTML”: your code places text, lines, images and other primitives at chosen coordinates.

npm install jspdf
import { jsPDF } from 'jspdf';

const doc = new jsPDF({ format: 'a4', unit: 'mm' });
doc.setFontSize(18);
doc.text('Monthly report', 20, 25);
doc.setFontSize(11);
doc.text('Revenue: $12,400', 20, 38);
doc.save('report.pdf');

Direct generation is useful for predictable forms, invoices and compact reports. You must handle wrapping, page breaks, fonts, tables and overflow yourself (or add compatible plugins). It should not be presented as a drop-in HTML renderer.

Fonts, images and CSS that decide fidelity

Fonts

Wait until web fonts are available before printing. A fallback font changes glyph widths and therefore line breaks and page count. Package or host fonts where your deployment can reliably reach them, and check licensing for embedded fonts.

Images and charts

Use absolute or reachable URLs, wait for lazy images and chart rendering, and verify cross-origin headers for browser-side canvas workflows. A screenshot of a chart is not equivalent to a vector chart in a PDF.

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

Pagination

Use print-specific rules such as break-before, break-after and break-inside where supported. Keep headings with their following content and avoid forcing a fixed-height container around text that must flow across pages.

Performance, reliability and deployment

  • Browser automation cost: launching a browser is heavier than creating PDF primitives. Reuse a controlled browser process for batches, but isolate pages and close them after each job.
  • Readiness: combine navigation waits with an application signal such as data-pdf-ready="true". Network-idle events can remain open because analytics, WebSockets or polling never stop.
  • Security: treat target URLs and HTML as untrusted. Restrict navigation, credentials, file access and outbound network access; never expose an endpoint that lets arbitrary users drive your internal browser.
  • Determinism: pin browser and package versions, set timezone and locale when dates matter, and use stable test fixtures. Browser updates can alter pagination.
  • Memory: large DOM trees, high canvas scales and full-page images increase memory pressure. Split long reports or lower raster scale where appropriate.
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

Check that navigation completed, the page did not hit a bot check, and the canvas is not beyond browser dimensions. For html2pdf.js, reduce the document or canvas scale. For Puppeteer or Playwright, log the final URL and page errors before printing.

Styles look wrong

Remember that print media is the default. Add or revise @media print, or emulate screen media in Playwright when that is the intended design. Confirm that stylesheets and fonts are reachable from the runtime.

Images are missing

Wait for lazy-loaded images, use reachable URLs, and configure CORS for browser canvas capture. A private image URL that works only with your desktop cookies will not load in a clean automation context.

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

Pages break in the wrong places

Remove fixed heights that force overflow, add break rules to logical sections, and wait for all content before measuring or printing. Small font or width changes can move an entire paragraph to the next page.

Text cannot be searched

That is expected for html2pdf.js’s canvas-based pipeline. Switch to Puppeteer or Playwright printing, or build the document with jsPDF text primitives.

The Node process hangs

Always close the browser in a finally block, set job timeouts, and investigate pages that keep connections open. Do not rely solely on a network-idle condition for applications with continuous traffic.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server that can return a screenshot or PDF from one request. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info and capture_pdf—let Claude, Cursor and other MCP clients capture pages without you maintaining a browser.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

For request options and PDF output details, see the ScreenshotNeo documentation. The same service supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper size and margins, page ranges, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage reporting and an OpenAPI specification. Parameter names used by other screenshot APIs also work to ease migration.

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan, and annual billing provides two months free. Create a free ScreenshotNeo account to try it without a card.

Which library should you choose?

  1. Need a faithful print of a URL or application route? Start with Puppeteer or Playwright and design a print stylesheet.
  2. Need an “Export this panel” button in a browser? Choose html2pdf.js only if rasterized, non-searchable text is acceptable.
  3. Need a data-driven document with exact coordinates? Use jsPDF and implement the layout deliberately.
  4. Need remote captures without operating Chromium? Use ScreenshotNeo’s API or MCP server, especially when consent overlays and failed-page billing matter.

Frequently Asked Questions

Can html2pdf.js run in Node.js?

No. Its project documentation describes it as a browser-side library; use Puppeteer, Playwright or direct jsPDF generation for Node.js workloads.

Why does my PDF use different colors from the screen?

Puppeteer and Playwright print with print media by default, and print color handling can differ from screen rendering. Define print styles and enable background printing where supported.

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

How can I keep PDF text searchable?

Use browser PDF printing with Puppeteer or Playwright, or generate text with jsPDF. html2pdf.js rasterizes the element, so its text is not selectable or searchable.

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.