Convert HTML to PDF with JavaScript: Top libraries compared
Table of contents
- Nutrient — Document platform with SDKs and APIs. Full CSS, viewing, signing, redaction.
- html2pdf.js — Simple client-side exports. Limited CSS support.
- pdfmake — Structured documents from JSON data. Manual configuration.
- Playwright — Full CSS support, server-side. Requires infrastructure.
How to convert HTML to PDF in JavaScript
The simplest way to convert HTML to PDF in JavaScript is with html2pdf.js. Add the library via CDN, select your HTML element, and call html2pdf().from(element).save():
// Include via CDN: https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.jsconst element = document.getElementById('content');html2pdf().from(element).save('document.pdf');This works for simple documents. For better CSS support, use Playwright (server-side) or Nutrient (managed API). For structured data like invoices, use pdfmake.
Four approaches to HTML-to-PDF conversion
JavaScript developers have four main approaches, each with different tradeoffs:
- Client-side libraries (html2pdf.js, pdfmake) — Quick setup but browser limitations
- Server-side automation (Playwright) — High fidelity but requires infrastructure
- Document platforms (Nutrient) — SDKs and APIs with viewing, signing, and more
- Custom solutions — Full control but significant development overhead
This guide focuses on the first three options, comparing their capabilities, performance, and ideal use cases.
Open source vs. commercial: Tradeoffs
Open source libraries (html2pdf.js, pdfmake, Playwright) are free to use and work well for many use cases. Commercial solutions like Nutrient add features that matter for larger deployments:
| Consideration | Open source | Nutrient |
|---|---|---|
| Cost | Free | Subscription after free tier |
| Support | Community forums | Dedicated support, SLAs |
| CSS support | Varies by library | Full CSS3 |
| Infrastructure | Self-managed | Managed or self-hosted |
| Compliance | DIY | SOC 2, encryption included |
Choose based on your scale, support needs, and feature requirements.
Nutrient
Nutrient is a document processing platform with SDKs for Web, iOS, Android, .NET, Java, and Node.js. Beyond HTML-to-PDF conversion, it handles viewing, annotations, eSignatures, redaction, OCR, and AI-powered extraction.
For HTML-to-PDF conversion, Nutrient has a number of options.
- Document Engine — Self-hosted server API with full CSS support. Converts HTML forms into fillable PDF fields. Chain operations like merging, watermarking, and page manipulation in one call.
curl -X POST http://localhost:5000/api/build \ -H "Authorization: Token token=<API token>" \ -F page.html=@/path/to/page.html \ -F instructions='{ "parts": [{ "html": "page.html" }] }' \ -o result.pdf- Processor API — Cloud-hosted API for serverless workflows. Supports PDF generation, conversion, OCR, and batch processing.
// Node.js example// This code requires Node.js. Do not run this code directly in a web browser.
const axios = require('axios')const FormData = require('form-data')const fs = require('fs')
const formData = new FormData()formData.append('html', fs.createReadStream('index.html'))
(async () => { try { const response = await axios.post('https://api.nutrient.io/processor/generate_pdf', formData, { headers: formData.getHeaders({ 'Authorization': 'Bearer your_api_key_here' }), responseType: "stream" })
response.data.pipe(fs.createWriteStream("result.pdf")) } catch (e) { const errorString = await streamToString(e.response.data) console.log(errorString) }})()
function streamToString(stream) { const chunks = [] return new Promise((resolve, reject) => { stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))) stream.on("error", (err) => reject(err)) stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))) })}- .NET SDK — Chrome-based rendering for local files and live URLs.
- Mobile SDKs (Android and iOS) — Generate PDFs from HTML on-device.
- No-code (Zapier and Power Automate) — Convert templates and form submissions without code.
Features not available in open source libraries
- Form field conversion — HTML form elements become fillable PDF fields automatically
- Chained operations — Merge, watermark, and manipulate pages in a single API call
- Cross-platform consistency — Same rendering engine across Web, iOS, Android, and server
- Enterprise support — SLAs, dedicated support, long-term maintenance
- Compliance — SOC 2 Type 2 audited, encryption, audit trails
html2pdf.js
html2pdf.js is a client-side library suitable for prototypes and simple applications. It has limitations: image-based output, browser memory limits, and no text selection in the resulting PDF.
The html2pdf(opens in a new tab) library converts HTML pages to PDFs in the browser. It uses html2canvas(opens in a new tab) and jsPDF(opens in a new tab) under the hood. html2canvas renders an HTML page into a canvas element and turns it into a static image. jsPDF then takes the image and converts it to a PDF file.
Check out our blog on how to convert HTML to PDF using React for a step-by-step guide on how to use jsPDF in a React app.
Installation options
You can install html2pdf.js in three ways:
- CDN — add a
scripttag to your HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>- npm —
npm install html2pdf.js - Manual download — grab the bundle from GitHub(opens in a new tab) and include it with a script tag
<script src="html2pdf.bundle.min.js"></script>Convert HTML to PDF using html2pdf
Define a generatePDF() function to get the HTML element and convert it to PDF. Call this function from a download button:
<!DOCTYPE html><html>10 collapsed lines
<head> <!-- html2pdf CDN link --> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg==" crossorigin="anonymous" referrerpolicy="no-referrer" ></script> </head> <body> <button id="download-button">Download as PDF</button> <div id="invoice"> <h1>Our Invoice</h1> </div>
<script> const button = document.getElementById("download-button");
function generatePDF() { // Choose the element that your content will be rendered to. const element = document.getElementById("invoice"); // Choose the element and save the PDF for your user. html2pdf().from(element).save(); }
button.addEventListener("click", generatePDF); </script> </body></html>This example renders only the h1 title, but you can render any HTML element including images and tables.
To test this example locally, run a quick static server with:
npx serve -l 4111 .Then open http://localhost:4111(opens in a new tab) in your browser to interact with the PDF download button.

Advanced example: Downloading an invoice as a PDF
You can download an invoice template as a PDF by clicking this link. You can also generate the HTML for your own invoice on your backend if you prefer.
Similar to the example above, you’ll use the generatePDF() function to download the invoice as a PDF. However, this time, you’ll render the invoice template to the div element.
The end result will look like what’s shown below.

Common issues with html2pdf.js in production
Typical problems: Blurry output, memory crashes with large documents, and inconsistent mobile rendering. Nutrient Web SDK addresses these issues while still supporting client-side operation.
pdfmake
pdfmake is suitable for data-driven documents and reports. However, it has limitations, including manual configuration complexity and no HTML input support — you define layouts in JavaScript objects instead.
The pdfmake(opens in a new tab) library generates PDF documents directly in the browser. It uses a layout and style configuration defined in a JSON-like structure within JavaScript. It’s built on pdfkit(opens in a new tab).
Unlike html2pdf.js, which converts HTML to an image first, pdfmake defines document structure in JavaScript, so text stays selectable.
Installation
Add via CDN:
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.72/pdfmake.min.js" integrity="sha384-qwfyMhpwB4dOhtxItT9bxl/sd7WQ95MvXSyLUgVz3oNRlvK/Ea7uT4O3rf1ZriyH" crossorigin="anonymous" referrerpolicy="no-referrer"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.72/vfs_fonts.js" integrity="sha384-vD7JPR0c5DJMoqHXO7lJKsK9k6XPlL4LMoGK1jHsGBWoDzW9BGfaP7UWKIXHdz7L" crossorigin="anonymous" referrerpolicy="no-referrer"></script>Or, install via npm:
npm install pdfmakeGenerate PDFs from HTML using pdfmake
Define a generatePDF() function with your PDF content as a JavaScript object. Use pdfmake to create and download the file. The example shows text and a table, but pdfmake also supports lists, images, and advanced styling.
<!DOCTYPE html><html>12 collapsed lines
<head> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.72/pdfmake.min.js" crossorigin="anonymous" referrerpolicy="no-referrer" ></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.72/vfs_fonts.js" crossorigin="anonymous" referrerpolicy="no-referrer" ></script> </head> <body> <button onclick="generatePDF()">Download Invoice</button>
<!-- Move script here so pdfMake is loaded --> <script> function generatePDF() { const docDefinition = { content: [ { text: "Invoice", style: "header" }, { table: { body: [ ["Item", "Qty", "Price"],3 collapsed lines
["Product 1", "2", "$10"], ["Product 2", "1", "$20"], ["Total", "", "$40"], ], }, }, ], styles: { header: { fontSize: 18, bold: true }, }, };
pdfMake.createPdf(docDefinition).download("invoice.pdf"); } </script> </body></html>You can test the example by running:
npx serve -l 4111 .
Benefits of using pdfmake
- Selectable text — Unlike
html2pdf.js,pdfmakepreserves text as selectable and copyable. - Custom styling — Define font sizes, colors, and positioning with structured JavaScript.
- Advanced layouts — Supports tables, lists, and multicolumn designs.
Playwright
Playwright is suitable for high-fidelity rendering with full CSS support. However, it has limitations and requires server infrastructure and ongoing maintenance.
Playwright(opens in a new tab) controls headless Chrome, Firefox, and WebKit browsers to convert webpages into PDFs. Unlike html2pdf, Playwright runs on your server and generates PDFs server-side.
Installation
Create a new project and install Playwright(opens in a new tab):
mkdir playwright-pdf-generation && cd playwright-pdf-generationnpm init --yesnpm install playwrightBefore using Playwright, make sure the required browser binaries are installed by running:
npx playwright installBasic PDF example
You’ll now create an index.js file where you’ll require Playwright, launch a new browser session, go to your invoice page, and save the PDF file:
// Require Playwright.const { chromium } = require("playwright");
(async function () { try { // Launch a new browser session. const browser = await chromium.launch(); // Open a new page. const page = await browser.newPage();
// Set the page content. await page.setContent("<h1>Our Invoice</h1>");
// Generate a PDF and store it in a file named `invoice.pdf`. await page.pdf({ path: "invoice.pdf", format: "A4" });
await browser.close(); } catch (e) { console.error(e); }})();Running node index.js generates invoice.pdf locally. To let users download PDFs, set up a Node server with the http module. The server listens on /generate-pdf, renders the page with Playwright, and streams the PDF to the browser.
To do this, omit the path option in page.pdf() to get a buffer, set the response header to application/pdf, and send the buffer as the response. For all other routes, return a simple HTML page with a link to trigger the PDF download:
const { chromium } = require("playwright");const http = require("http");
// Create an instance of the HTTP server to handle the request.http .createServer(async (req, res) => { if (req.url === "/generate-pdf") { // Making sure to handle a specific endpoint. const browser = await chromium.launch(); const page = await browser.newPage();
// Set the content directly or navigate to an existing page. await page.setContent(` <!DOCTYPE html> <html> <head> <title>Invoice</title> </head> <body> <h1>Our Invoice</h1> <p>Details about the invoice...</p> </body> </html> `);
// By removing the `path` option, you'll receive a `Buffer` from `page.pdf`. const buffer = await page.pdf({ format: "A4" });
await browser.close();
// Set the content type so the browser knows how to handle the response. res.writeHead(200, { "Content-Type": "application/pdf" }); res.end(buffer); } else { // Respond with a simple instruction page. res.writeHead(200, { "Content-Type": "text/html" }); res.end( "<h1>Welcome</h1><p>To generate a PDF, go to [/generate-pdf](/generate-pdf).</p>", ); } }) .listen(3000, () => { console.log("Server is running on http://localhost:3000"); });Open localhost:3000 in your browser, and you’ll see the instruction page. You can then navigate to /generate-pdf to open the PDF with the invoice.

Common issues and troubleshooting
- Blurry output — Use high-resolution images and scale properly.
- Limited CSS — Limited styling; avoid modern layout features like flexbox or grid.
- Large files — Optimize HTML and assets
- Layout complexity — Manual configuration required; break documents into smaller blocks when needed.
- Font issues — Ensure custom fonts are embedded correctly.
- Setup is more involved than client-side tools.
- Minimize dynamic scripts or unnecessary content before rendering.
- License validation — Confirm valid key and domain configuration
- Large documents — Use streaming API for >100MB
- Custom fonts — Upload to Document Engine for consistency
HTML-to-PDF conversion feature comparison
| Feature | html2pdf.js | pdfmake | Playwright | Nutrient |
|---|---|---|---|---|
| Ease of use | Very easy | Moderate | Moderate | Easy |
| Installation | Simple (CDN, npm) | Simple (CDN, npm) | npm | SDK/API |
| Runs in browser | ||||
| Text selection | ||||
| CSS/HTML support | Limited | None | Full | Full |
| Customization | Low | High | High | High |
| PDF quality | Medium (image-based) | High (structured content) | Very high (browser rendering) | Very high (Chrome-based) |
| Performance | Fast for small documents | Good with structured layouts | High for large/complex layouts | Enterprise scale |
| Use case | Simple exports, receipts | Invoices, reports, forms | High-fidelity PDFs, automation | Production applications, compliance |
| Support/SLA | Community | Community | Community | Enterprise |
| Pros | No backend needed, easy setup | Selectable text, layout control | Pixel-perfect output, full CSS | Managed infrastructure, advanced features |
| Cons | Blurry output, no selection | Manual configuration, no HTML input | Server setup, heavier runtime | Commercial license required |
Nutrient SDK includes annotations, form filling, and custom output options.
Start your free trial to test Nutrient’s HTML-to-PDF conversion.
PDF-to-HTML conversion is coming soon to Document Engine and DWS API.
FAQ
It depends on your needs. For simple client-side conversion, use html2pdf.js. For structured documents from data, use pdfmake. For full CSS support on a server, use Playwright. For managed infrastructure with additional features, use Nutrient.
Yes. html2pdf.js and pdfmake both run entirely in the browser. html2pdf.js converts HTML elements directly, while pdfmake generates PDFs from JavaScript objects. Neither requires a backend server.
html2pdf.js creates image-based PDFs, which can look blurry at certain zoom levels. For sharper output, use Playwright or Nutrient, which render actual text and vector graphics instead of images.
html2pdf.js has limited CSS support. For full CSS3 compatibility, including flexbox and grid, use Playwright (server-side) or Nutrient. Both use real browser engines to render your styles accurately.
Client-side (html2pdf.js, pdfmake) runs in the browser; no server is needed, but it’s limited by browser memory and capabilities. Server-side (Playwright, Nutrient) runs on a server, meaning it handles larger documents and complex CSS, but requires infrastructure.
Most open source libraries don’t support this. Nutrient Document Engine converts HTML form elements into fillable PDF fields, including text inputs, checkboxes, and dropdowns.