# 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:
```js
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: "
Hello
" }),
});
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.
```html
```
The user picks **Save as PDF** as the destination. Use print CSS to control what ends up on paper.
```css
@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.
```css
@media print {
body * { visibility: hidden; }
#invoice, #invoice * { visibility: visible; }
#invoice { position: absolute; left: 0; top: 0; width: 100%; }
}
```
- **Pros:** free, real selectable text, and the browser's own layout engine.
- **Cons:** the user must click through a dialog and you can't set the filename. Headers, footers and background graphics depend on the user's dialog settings, and you never get the file in your code.
## 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).
```html
Invoice #042
Total: $120.00
```
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.
```js
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](https://html-pdf.com/puppeteer-html-to-pdf), 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.
```js
// 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.
```js
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.
```bash
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](https://html-pdf.com/html-to-pdf-nodejs) (Express and Next.js) · [page breaks](https://html-pdf.com/html-to-pdf-page-breaks) · [API docs](https://html-pdf.com/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.