HTML to PDF Converter JavaScript Library
Compare html2pdf.js, Puppeteer and Playwright, with runnable JavaScript code, deployment guidance, troubleshooting and a simpler API option.
For an HTML to PDF converter JavaScript library, choose based on where rendering runs and whether the PDF must preserve real text and print CSS.
- Use html2pdf.js when conversion can happen in a browser and an image-based PDF is acceptable.
- Use Puppeteer when you need server-side Chrome rendering, selectable text and print-media CSS.
- Evaluate Playwright when you already use its browser automation stack and can manage browser binaries and operating-system dependencies.
This guide gives complete examples, explains the rendering trade-offs, and shows a hosted option when you do not want to deploy a browser.
1. Decide which JavaScript approach fits
| Requirement | Best starting point | Reason |
|---|---|---|
| Run entirely in a user’s browser | html2pdf.js | Client-side library built on html2canvas and jsPDF. |
| Run on a Node.js server | Puppeteer | Automates Chrome and exposes page.pdf(). |
| Already operate Playwright | Playwright | Reuses your existing browser automation and deployment model. |
| Need searchable, selectable text | Puppeteer or another browser print pipeline | html2pdf.js places rendered content into the PDF as an image. |
| Need to avoid browser installation | ScreenshotNeo | A hosted API returns PDFs from one GET request and handles browser execution for you. |
2. Browser-side conversion with html2pdf.js
html2pdf.js converts a webpage or element in the browser. Its documented pipeline is .from() -> .toContainer() -> .toCanvas() -> .toImg() -> .toPdf() -> .save(). The project explicitly states that it does not run in Node.js; it must run in a browser.
Install and load the library
npm install html2pdf.js
With a bundler:
import html2pdf from 'html2pdf.js';
const element = document.querySelector('#invoice');
await html2pdf()
.set({
margin: 12,
filename: 'invoice.pdf',
image: { type: 'jpeg', quality: 0.95 },
html2canvas: {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff'
},
jsPDF: {
unit: 'mm',
format: 'a4',
orientation: 'portrait'
},
pagebreak: {
mode: ['css', 'legacy']
}
})
.from(element)
.save();
Or load the browser bundle from your own asset pipeline and call the same API from a script tag:
<button id="download">Download PDF</button>
<section id="invoice">
<h1>Invoice 1042</h1>
<p>Amount due: $240.00</p>
</section>
<script src="/assets/html2pdf.bundle.min.js"></script>
<script>
document.querySelector('#download').addEventListener('click', async () => {
await html2pdf()
.set({ filename: 'invoice.pdf' })
.from(document.querySelector('#invoice'))
.save();
});
</script>
Useful html2pdf.js options
| Option | What it controls | Practical note |
|---|---|---|
margin |
PDF margins | Use a number or an array for per-side margins. |
filename |
Downloaded file name | Include a stable extension such as .pdf. |
image.type |
Intermediate image format | JPEG is smaller; PNG preserves sharp edges and transparency better. |
image.quality |
JPEG quality | Higher values improve quality and increase file size. |
html2canvas.scale |
Raster resolution | Increase for sharper output, but watch memory use. |
html2canvas.useCORS |
Cross-origin image loading | The image server must send compatible CORS headers. |
jsPDF.format |
Paper size | Common values include a4 and letter. |
jsPDF.orientation |
Portrait or landscape | Use landscape for wide tables. |
pagebreak.mode |
Page-break handling | CSS page-break rules are usually the most maintainable choice. |
CSS for predictable page breaks
.pdf-page-break {
break-before: page;
page-break-before: always;
}
.avoid-split {
break-inside: avoid;
page-break-inside: avoid;
}
html2pdf.js limitations
The output is image-based, so text is not selectable or searchable and files can be large. The project also documents imperfect html2canvas rendering, cloning problems, root-element resizing that can trigger reflow, and browser canvas dimension limits that may produce blank output for very large documents. Test long pages, custom fonts, SVGs, charts and cross-origin images in the browsers you support.
3. Server-side conversion with Puppeteer
Puppeteer automates Chrome and supports PDF generation. Its page.pdf() method uses print CSS media by default. If the page is designed for screen media, call page.emulateMediaType('screen') before generating the file. Print output can also alter colors; use -webkit-print-color-adjust: exact when exact colors are required.
Install
npm install puppeteer
Complete Node.js example
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com/report', {
waitUntil: 'networkidle0',
timeout: 60000
});
await page.emulateMediaType('print');
await page.addStyleTag({
content: `
@page { size: A4; margin: 14mm; }
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.avoid-split { break-inside: avoid; page-break-inside: avoid; }
`
});
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
displayHeaderFooter: false,
margin: { top: '14mm', right: '14mm', bottom: '14mm', left: '14mm' }
});
} finally {
await browser.close();
}
Rendering controls that matter
waitUntil: choosenetworkidle0for pages that finish loading, or wait for a specific selector when analytics and long polling prevent network idle.page.emulateMediaType(): selectprintorscreendeliberately.printBackground: include background colors and images.preferCSSPageSize: let an@pagerule control the paper size.format,width,heightandmargin: define the printable geometry.page.setViewport(): controls responsive breakpoints before layout is calculated.page.goto()timeout: set a limit appropriate for your page and fail clearly when it is exceeded.
Waiting for application data
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready]', { timeout: 30000 });
await page.evaluate(() => document.fonts.ready);
await page.pdf({ path: 'dashboard.pdf', printBackground: true });
4. Playwright as an alternative browser pipeline
Playwright is another browser automation option. Its documentation covers installing browser binaries, operating-system dependencies and browser cache management. Those requirements affect container images, cold starts, upgrades and deployment permissions, so include them in your evaluation.
npm install playwright
import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
await page.goto('https://example.com/report', { waitUntil: 'networkidle' });
await page.emulateMedia({ media: 'print' });
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
preferCSSPageSize: true
});
} finally {
await browser.close();
}
Use the Playwright browser documentation for the installation command that matches your operating system and deployment image.
5. HTML and CSS patterns that survive PDF conversion
- Define a paper size and margins with
@page. - Use web fonts that are available in the target runtime, and wait for
document.fonts.ready. - Give images explicit dimensions to reduce layout shifts.
- Keep tables from splitting with
break-inside: avoidon rows or grouped sections where supported. - Use print-specific rules under
@media printand verify whether your tool uses print or screen media. - Set
printBackground: truewhen colored panels or chart backgrounds are part of the document. - Avoid relying on a huge single canvas for very long documents.
6. Or skip the browser setup
ScreenshotNeo provides a hosted website capture API that can return a PDF. The API base is https://api.screenshotneo.com/v1/shot; see the API documentation for the complete option list.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));
For PDF output, pass the PDF options described in the ScreenshotNeo docs. Cookie banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed, and response headers identify the page verdict and whether the request was billed. An MCP server lets AI agents use take_screenshot, get_page_info and capture_pdf. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| “html2pdf.js will not run in Node.js” | The library requires a browser. | Run it in browser code, or use Puppeteer, Playwright or ScreenshotNeo for server-side work. |
| PDF is blank or partially blank | Canvas size limits, a failed resource, or capture before rendering completes. | Split very large documents, wait for fonts and images, and inspect browser console errors. |
| Text is blurry or not selectable | html2pdf.js rasterizes the page. | Use a browser print pipeline such as Puppeteer when selectable text matters; increase canvas scale only for sharpness. |
| Images are missing | Cross-origin restrictions or images not loaded before capture. | Enable CORS where appropriate, use useCORS, preload images and wait for them. |
| Colors differ from the page | Print color adjustment or print media styles. | Set printBackground: true and -webkit-print-color-adjust: exact; verify print versus screen media. |
| Layout changes between runs | Responsive viewport, late data, fonts or images changing dimensions. | Set a fixed viewport, wait for a ready selector and await document.fonts.ready. |
| Page breaks split cards or rows | Missing break rules or unsupported layout structure. | Apply break-inside: avoid, group content, and test representative page lengths. |
| Puppeteer or Playwright fails in production | Browser binaries or OS libraries are absent. | Install the documented browser and system dependencies in the image, cache them, and pin compatible versions. |
| Navigation times out | Slow resources, blocked requests or pages that never become idle. | Wait for a specific selector instead of network idle, increase the timeout, and log the failing URL. |
8. Performance, reliability and cost
Performance
- Reuse a browser process for batches instead of launching one process per document.
- Reuse pages carefully and clear cookies, storage and request interception between tenants.
- Use a fixed viewport and avoid unnecessary resources such as analytics during rendering.
- For html2pdf.js, lower canvas scale or split long documents when memory usage becomes a problem.
- For hosted capture, use caching and a suitable wait strategy when the same URL is requested repeatedly.
Reliability
- Log the URL, viewport, browser/library version and wait condition for every failed conversion.
- Make output deterministic: pin dependencies, provide fonts, set timezone and wait for application readiness.
- Test pages with long tables, lazy images, SVGs, custom fonts, dark backgrounds and responsive breakpoints.
- Retry only transient navigation or infrastructure failures; do not hide invalid HTML or authentication errors with blind retries.
Cost and deployment
html2pdf.js has no server browser deployment cost, but it consumes the user’s CPU and memory and creates image-based PDFs. Puppeteer and Playwright add browser downloads, operating-system dependencies, memory use and maintenance to your infrastructure. A hosted API exchanges that setup for per-request pricing. ScreenshotNeo includes 1,000 shots per month free with no card; paid plans are $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000 and $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan.
9. Selection checklist
- Does conversion need to run in the browser or on a server?
- Must PDF text be searchable and selectable?
- Do you need print CSS, screen CSS or both?
- How will browser binaries and OS dependencies be installed and cached?
- What happens when fonts, images, API data or third-party scripts are slow?
- How will you handle page breaks, long documents and large tables?
- What is your acceptable memory use, file size and conversion time?
- Would a hosted API reduce operational work for your team?
10. FAQ
Can html2pdf.js run in a Node.js backend?
No. Its project documentation says it must run in a browser. Use Puppeteer, Playwright or a hosted service for Node.js server-side conversion.
Which option creates selectable PDF text?
A browser print pipeline such as Puppeteer generally preserves rendered text, while html2pdf.js documents image-based output with nonselectable, unsearchable text.
Why does Puppeteer use different styles than my page?
page.pdf() uses print media by default. Choose print or screen media explicitly and maintain matching CSS rules.
Is Playwright a drop-in replacement for Puppeteer PDF generation?
It is a candidate browser automation option, but evaluate its API, browser setup and deployment requirements against your application rather than assuming identical behavior.
When should I use ScreenshotNeo?
Use it when you want PDF or screenshot capture without packaging and operating a browser yourself, especially when consent banners, popups, failed loads and AI-agent access matter.


