How to Add Div Content to a PDF With jsPDF
Learn how to export a selected div to a downloadable PDF with jsPDF, including CSS limits, images, page breaks, fonts, troubleshooting, and a hosted API option.

To add the contents of a div to a PDF in the browser, pass the element to jsPDF’s html() method. jsPDF uses html2canvas to reconstruct the element and place the result into the PDF.
import { jsPDF } from "jspdf";
const element = document.querySelector("#content-to-export");
if (!element) throw new Error("PDF content element was not found");
const doc = new jsPDF({ format: "a4", unit: "mm" });
doc.html(element, {
x: 10,
y: 10,
width: 190,
callback: (pdf) => pdf.save("document.pdf"),
});
This approach is browser based. It is useful when the user has already rendered the content and needs a download button. It is not a literal browser screenshot: html2canvas reads the DOM and supported styles, paints a canvas, and jsPDF places the result on PDF pages. Complex CSS, cross-origin assets, iframes, fonts, and very long documents therefore need deliberate configuration and inspection.
1. Install jsPDF and prepare the content
Install jsPDF in the application that owns the page:
npm install jspdf
Use a stable export container with an explicit background and a width that matches the PDF’s printable area. Keep controls outside that container, or mark them to be ignored.
<button id="download-pdf" type="button">Download PDF</button>
<article id="content-to-export" class="report">
<h1>Quarterly report</h1>
<p>This content will be exported.</p>
<img src="/images/chart.png" alt="Revenue chart">
<button data-html2canvas-ignore>Edit report</button>
</article>
.report {
width: 760px;
padding: 32px;
color: #111827;
background: white;
box-sizing: border-box;
}
@media print {
.report { width: 190mm; }
}
The jsPDF html method depends on html2canvas. If you pass an HTML string instead of an element, the documented path also uses DOMPurify. Bundlers can load these optional dependencies as separate chunks, so check your build output and installed versions if the method is undefined or a dynamic import fails. See the jsPDF documentation and repository for the API supported by your version.
2. A complete export function
Wrap the operation in a promise so callers can disable the button, show progress, and handle failures. The callback is invoked after jsPDF has rendered the HTML.

import { jsPDF } from "jspdf";
export function exportDivToPdf(selector, filename = "document.pdf") {
const element = document.querySelector(selector);
if (!(element instanceof HTMLElement)) {
throw new Error(`No element found for ${selector}`);
}
return new Promise((resolve, reject) => {
try {
const doc = new jsPDF({
orientation: "portrait",
unit: "mm",
format: "a4",
compress: true,
});
doc.html(element, {
x: 10,
y: 10,
width: 190,
windowWidth: element.scrollWidth,
autoPaging: "text",
margin: [10, 10, 10, 10],
callback: (pdf) => {
pdf.save(filename);
resolve();
},
html2canvas: {
useCORS: true,
backgroundColor: "#ffffff",
scale: Math.min(window.devicePixelRatio || 1, 2),
logging: false,
},
});
} catch (error) {
reject(error);
}
});
}
document.querySelector("#download-pdf").addEventListener("click", async (event) => {
const button = event.currentTarget;
button.disabled = true;
try {
await exportDivToPdf("#content-to-export", "quarterly-report.pdf");
} finally {
button.disabled = false;
}
});
Option names can vary between jsPDF releases. Check the typings and the versioned documentation before relying on a worker-chain option. Some builds support a promise-style worker with .save(); the callback form above is easier to audit across bundlers.
3. Configure page size, position, and pagination
| Option | Purpose | Guidance |
|---|---|---|
format |
Page format such as a4 or a custom size |
Choose this before tuning the content width. |
orientation |
portrait or landscape |
Landscape helps wide tables and dashboards. |
unit |
Coordinates such as mm, pt, or px |
Use one unit consistently. |
x, y |
Top-left placement of the rendered content | Leave margins for printers and readability. |
width |
Rendered HTML width in the PDF’s coordinate system | For A4 with 10 mm margins, 190 mm is a practical starting point. |
margin |
Margins used by the HTML renderer | Keep it consistent with x/y. |
autoPaging |
How long content flows onto pages | Inspect headings, tables, and cards at page boundaries. |
A long div is not automatically a well-designed report. Add deliberate break points where a card or heading should stay together:
.keep-together {
break-inside: avoid;
page-break-inside: avoid;
}
.page-break-before {
break-before: page;
page-break-before: always;
}
These properties are hints to a reconstructed renderer, not a guarantee. Generate representative PDFs containing short, medium, and very long content, then inspect every page boundary. Tables with a very wide cell, absolutely positioned elements, sticky headers, and nested overflow containers are common sources of surprising breaks.
4. Control what html2canvas renders
html2canvas creates a clone of the document and draws the properties it understands. It does not capture the browser’s final pixels. The html2canvas documentation describes this distinction and lists supported configuration.
Exclude buttons, menus, and decoration
Add data-html2canvas-ignore to an element, or use ignoreElements for a rule:
doc.html(element, {
html2canvas: {
ignoreElements: (node) => node.matches?.(".no-pdf, nav, button"),
onclone: (clonedDocument) => {
clonedDocument.querySelectorAll(".only-on-screen").forEach((node) => node.remove());
},
},
callback: (pdf) => pdf.save("document.pdf"),
});
Make rendering conditions explicit
Responsive CSS can select a different layout during export. Set windowWidth and windowHeight to known values. Set scrollX and scrollY when a fixed or sticky element depends on scroll position. Use scale to trade output sharpness against memory and file size.
html2canvas: {
windowWidth: 1280,
windowHeight: 900,
scrollX: 0,
scrollY: 0,
scale: 2,
}
5. Images, fonts, and cross-origin content
Images normally need to be same origin or served with CORS headers that permit the page to load them. useCORS: true asks the browser to use CORS for eligible images; it cannot bypass the browser’s security policy. A proxy option can help when you control a suitable proxy, but it is not a way to defeat access controls.
html2canvas: {
useCORS: true,
imageTimeout: 15000,
allowTaint: false,
}
Wait for images before starting the export if your application inserts them asynchronously:
await Promise.all(
[...document.querySelectorAll("#content-to-export img")].map((img) =>
img.complete
? Promise.resolve()
: new Promise((resolve) => {
img.addEventListener("load", resolve, { once: true });
img.addEventListener("error", resolve, { once: true });
})
)
);
Cross-origin iframes are restricted. Same-origin frames can be traversed, but a cross-origin or sandboxed frame without allow-same-origin cannot be read by the renderer. Replace such content with an export-friendly summary or provide the asset from the same origin.
Standard PDF fonts have limited Unicode coverage. If accents, symbols, Arabic, CJK characters, or emoji are missing, embed a TTF font containing those glyphs, register it with jsPDF, and use that family in the export stylesheet. A font that exists only as a browser webfont may not be available to jsPDF’s PDF text layer.
6. Text, selectable output, and fidelity trade-offs
The html2canvas route reconstructs the DOM through a canvas-oriented rendering path. It is convenient for mixed HTML and CSS, but fidelity depends on supported properties. Filters, blend modes, complex gradients, pseudo-elements, SVG effects, and advanced layout can differ from the browser. A canvas-heavy result may also make text less searchable or selectable than text written directly with jsPDF APIs.
For documents that must be searchable, accessible, or extremely typographic, create PDF text and layout with jsPDF primitives or a server-side HTML-to-PDF engine. For a visual report where matching the on-screen card matters more than semantic PDF structure, html() is usually the shortest implementation.
7. Browser-only execution and framework timing
html2canvas depends on browser APIs and is documented for modern Chrome/Chromium, Firefox, and Safari. It is not a Node.js renderer. In React, Vue, or similar frameworks, call the export after the component has mounted and after data, images, and webfonts have loaded. Do not call it during server-side rendering.
await document.fonts?.ready;
const report = document.querySelector("#content-to-export");
if (!report) throw new Error("Report is not mounted");
await exportDivToPdf("#content-to-export");
8. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
doc.html is not a function |
Old jsPDF build, wrong import, or missing optional module. | Upgrade jsPDF, verify the import, and inspect the installed version’s docs and bundle. |
| Blank PDF | Selector matched nothing, content was hidden, or rendering failed before the callback. | Validate the element, export only after mount, remove display:none, and catch errors. |
| Images missing | Cross-origin response lacks permitted CORS headers or images were not loaded. | Serve assets with CORS, use useCORS, wait for images, and inspect network responses. |
| Iframe is blank | Frame is cross-origin or sandboxed. | Use same-origin content or provide an export representation outside the frame. |
| Layout is narrower or wider | width, viewport, units, or responsive breakpoints differ. |
Set windowWidth, choose a PDF width that matches margins, and remove conflicting transforms. |
| Content is cut off | Overflow container, fixed height, or page-break behavior. | Temporarily use overflow: visible, remove fixed heights, and test long content. |
| Fonts or symbols are garbled | Selected PDF font lacks glyphs. | Embed and register a TTF font with the required Unicode range. |
| Tab, modal, or sticky header appears | Those nodes are part of the cloned DOM. | Use data-html2canvas-ignore, ignoreElements, or an onclone callback. |
| Browser freezes on a huge report | Large canvas dimensions consume substantial memory. | Reduce scale, split the report, lower image dimensions, and avoid exporting unnecessary off-screen content. |
9. Performance, reliability, and cost considerations
Rendering cost grows with DOM size, image pixels, scale, and the number of pages. A practical workflow is to render only the report element, cap scale at a value your devices can handle, compress images before insertion, and avoid a 4K canvas when a printed page does not need it. Disable the download button during rendering so users cannot start several expensive captures at once.
For reliability, show a clear loading state, handle image errors, wait for fonts, and keep a small export fixture in your UI review process. Compare PDFs after CSS changes because a harmless-looking layout rule can alter page breaks. Do not promise identical output across browsers: font rasterization, CORS behavior, and supported CSS can vary.
jsPDF itself is a client-side library, so there is no per-document service charge from jsPDF. Your operational costs are browser CPU, memory, bandwidth for assets, and any infrastructure used to host images or a proxy. If you need a repeatable server-side capture pipeline, a hosted screenshot or PDF API can move browser setup and maintenance out of the application.
10. Or skip the browser setup
ScreenshotNeo provides a one-call website screenshot API and an MCP server. It can return PNG, JPEG, WebP, or PDF output, capture a selected element, load lazy images, apply custom CSS and JavaScript, set paper size and margins, and wait for a selector, delay, or network idle. Before capture it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and whether the shot was billed.

See the ScreenshotNeo API documentation for authentication and all options. The basic call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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(`Screenshot failed: ${res.status}`);
const bytes = await res.arrayBuffer();
await Bun.write("shot.webp", bytes);
ScreenshotNeo also supports custom headers, cookies, user agents, authorization, timezone and geolocation, blocking ads or resource types, caching with a chosen TTL, signed links for public images, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and PDF page ranges. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—let Claude, Cursor, and other MCP clients capture pages. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
11. FAQ
Can I pass a div’s HTML string to jsPDF?
Yes, but the string path adds DOMPurify as a dependency and does not automatically include the surrounding page’s computed styles. Passing the actual element is generally simpler when the content is already rendered.
Why does the PDF not match a screenshot?
html2canvas reconstructs supported DOM properties rather than copying browser pixels. Unsupported CSS, cross-origin resources, viewport differences, and font availability can change the result.
Can I run this export in Node.js?
The documented html2canvas workflow requires browser APIs. Use a browser automation or hosted capture service for server-side generation.
How do I export only one element?
Select it and pass that element to doc.html(element, options). Ensure the element has the dimensions and background you want in the PDF.
How do I keep a heading with the paragraph below it?
Wrap the heading and paragraph in a container with break-inside: avoid, then verify the generated pages because break rules are renderer hints.


