HTML to PDF in JavaScript: save a page or div as PDF

HTML to PDF in JavaScript: save a page or div as PDF

HTML to PDF in JavaScript: window.print() with print CSS, html2pdf.js and jsPDF in the browser, or a server-side API for real text PDFs.

"HTML to PDF in JavaScript" can mean two things. You might want visitors to save a page from the browser, or you might want your code to generate a PDF file. The browser options are free and need no server, but each one has a catch, so this guide covers them first and then the server-side route.

Quick answer. To get a real PDF file with selectable text, call the API from your backend (Node 18+) and keep the key on the server:

const r = await fetch("https://html-pdf.com/v1/pdf", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.HTML_PDF_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ html: "<h1>Hello</h1>" }),
});
const pdf = Buffer.from(await r.arrayBuffer());

Option 1: window.print() and print CSS (no library)

This option lets visitors save any page as a PDF with zero dependencies. Every browser can already save to PDF, so a button that opens the print dialog takes one line.

<button onclick="window.print()">Save as PDF</button>

The user picks Save as PDF as the destination. Use print CSS to control what ends up on paper.

@media print {
  nav, footer, .no-print, button { display: none !important; }
  body { margin: 0; }
  .card, tr { break-inside: avoid; }
  h2 { break-before: page; }
}
@page { size: A4; margin: 15mm; }
html { print-color-adjust: exact; -webkit-print-color-adjust: exact; }

To print only one div, hide everything else while printing.

@media print {
  body * { visibility: hidden; }
  #invoice, #invoice * { visibility: visible; }
  #invoice { position: absolute; left: 0; top: 0; width: 100%; }
}

Option 2: html2pdf.js (one-click download in the browser)

This option gives visitors a direct download button without a print dialog. html2pdf.js combines html2canvas (which draws the element onto a canvas) and jsPDF (which writes the PDF).

<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<div id="invoice"><h1>Invoice #042</h1><p>Total: $120.00</p></div>
<button id="dl">Download PDF</button>
<script>
  document.getElementById("dl").addEventListener("click", () => {
    html2pdf()
      .set({
        margin: 10,
        filename: "invoice.pdf",
        image: { type: "jpeg", quality: 0.95 },
        html2canvas: { scale: 2 },
        jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
        pagebreak: { mode: ["css", "legacy"] },
      })
      .from(document.getElementById("invoice"))
      .save();
  });
</script>

The catch is that the PDF contains a picture of your HTML. Text isn't selectable or searchable, files get large, and things can blur when zoomed. Content can also be sliced in half at page edges.

html2canvas re-implements CSS, so some properties render differently from the browser. Cross-origin images also need CORS headers.

Option 3: jsPDF directly

This option produces real text in the browser for simple documents. jsPDF builds a PDF from drawing commands.

import { jsPDF } from "jspdf";

const doc = new jsPDF({ unit: "mm", format: "a4" });
doc.setFontSize(18);
doc.text("Invoice #042", 15, 20);
doc.setFontSize(11);
doc.text("Total: $120.00", 15, 30);
doc.save("invoice.pdf");

That works for simple documents, but you position everything by hand because it doesn't understand your HTML layout. Its doc.html() method uses html2canvas under the hood, with the same trade-offs as html2pdf.js.

Option 4: generate the PDF on the server

This option gives you a real file with selectable text and Chrome-accurate layout. Use it when you need to email, store or attach the PDF. With Node.js that usually means Puppeteer, which means running and maintaining Chrome yourself.

Or call an API that runs Chromium for you. Put the call in your backend so your key never reaches the browser.

// server.mjs (Node 18+, Express)
import express from "express";

const app = express();
app.use(express.json({ limit: "5mb" }));

app.post("/api/pdf", async (req, res) => {
  const r = await fetch("https://html-pdf.com/v1/pdf", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.HTML_PDF_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      html: req.body.html,   // in production, render your own template instead of trusting the client
      format: "A4",
      margin: "15mm",
      max_pages: 10,
    }),
  });
  if (!r.ok) return res.status(r.status).json(await r.json());
  res.setHeader("Content-Type", "application/pdf");
  res.setHeader("Content-Disposition", 'attachment; filename="invoice.pdf"');
  res.send(Buffer.from(await r.arrayBuffer()));
});

app.listen(3000);

Then add the one-click download in the browser.

async function downloadPdf() {
  const html = document.documentElement.outerHTML; // or a specific element's outerHTML
  const res = await fetch("/api/pdf", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ html }),
  });
  if (!res.ok) return alert("PDF failed");
  const url = URL.createObjectURL(await res.blob());
  const a = Object.assign(document.createElement("a"), { href: url, download: "invoice.pdf" });
  a.click();
  URL.revokeObjectURL(url);
}

Make sure stylesheets and images in that HTML use absolute https:// URLs, since the renderer can't reach localhost. Sending the whole page from the client is fine for a demo. In production, build the HTML on the server from your own template so users can't inject arbitrary content.

Which one should you use?

This table matches each need to the simplest option that covers it.

Need Use
Let visitors save a page, no code window.print() + print CSS
Quick client-side download, appearance over quality html2pdf.js
A few lines of text, fully in the browser jsPDF
Real text, accurate CSS, file in your backend Headless Chrome on the server, or the API

Get an API key

One request creates an account and returns your key.

curl -X POST https://html-pdf.com/v1/signup -H "Content-Type: application/json" -d '{"email": "you@example.com"}'

Open the link we email you to confirm your address. That adds 100 free credits with no card. 1 credit = 1 PDF page, and failed renders cost nothing.

More: HTML to PDF in Node.js (Express and Next.js) · page breaks · API docs

FAQ

How do I convert HTML to PDF in JavaScript?

In the browser, call window.print() and let the user pick "Save as PDF", or use html2pdf.js for a one-click download. For a real file with selectable text, send the HTML from your backend to POST https://html-pdf.com/v1/pdf.

Why is the text in my html2pdf.js PDF not selectable?

html2pdf.js renders your element to a canvas image and puts that image in the PDF. To get real, searchable text, use print CSS with window.print() or render with headless Chrome on the server.

Can I call the PDF API directly from the browser?

You shouldn't, because that would expose your API key to every visitor. Put the call in a small backend route and have the browser download the PDF from there.

Is there a free way to try the API?

Yes. New accounts get 100 free credits once, with no card, and 1 credit is 1 PDF page. Paid packs start at $10 for 1,000 credits, and purchased credits never expire.

This page as markdown · text