Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use html2canvas to render a DOM element, turn the resulting canvas into PNG data, and place that image in a jsPDF document. For a normal HTML-to-PDF job, jsPDF’s html() method is simpler because it coordinates the HTML rendering pipeline. Choose the explicit canvas route when you need the raster image itself or precise control over image placement.

Choose the rendering path

Approach Best for Important trade-off
pdf.html(element, options) Turning an element into a PDF with minimal code Uses jsPDF’s HTML module and html2canvas; layout is controlled through the module’s options
html2canvas() followed by addImage() Obtaining image data or controlling image coordinates and dimensions You must decide scaling and pagination; a single raster image is not automatically flowing, selectable PDF text

Both methods reproduce a visual rendering, not every browser feature. html2canvas documents CSS support property by property, so unsupported CSS can change the result. Test the exact page, browser and styles you intend to publish.

Install the JavaScript dependencies

In a browser project, install the packages with npm:

npm install jspdf html2canvas dompurify

Import the first two packages in your application:

import { jsPDF } from "jspdf";
import html2canvas from "html2canvas";

DOMPurify is relevant when you pass an HTML string to jsPDF’s HTML pipeline. If you pass an existing DOM element, keep the element’s content under your application’s normal sanitization rules and never inject untrusted markup into the page.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fastest route: jsPDF’s html() method

Give jsPDF a prepared element, then save the document when the asynchronous render completes:

import { jsPDF } from "jspdf";

const element = document.querySelector("#invoice");
if (!element) throw new Error("#invoice was not found");

const pdf = new jsPDF({
  orientation: "portrait",
  unit: "mm",
  format: "a4"
});

await pdf.html(element, {
  margin: [12, 12, 12, 12],
  autoPaging: "text",
  html2canvas: {
    scale: 2,
    useCORS: true
  },
  callback: (doc) => doc.save("invoice.pdf")
});

Run this after the element and its fonts, images and data are ready. The HTML module prepares a rendering container and uses html2canvas; page size, margins, dimensions and html2canvas options affect the output. autoPaging can help with flowing content, but inspect page breaks in the generated file rather than assuming a complex layout will paginate perfectly.

Rendering an HTML string

When your input is a string rather than an element, sanitize it first and follow the jsPDF version’s html() API. The HTML module’s documentation identifies DOMPurify as a dependency for string HTML. Do not pass attacker-controlled markup, URLs or options directly to a PDF generator.

Image-first route: html2canvas plus addImage()

This route gives you a canvas and therefore image data that you can place yourself:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { jsPDF } from "jspdf";
import html2canvas from "html2canvas";

export async function saveElementAsPdf(element) {
  if (!(element instanceof HTMLElement)) {
    throw new TypeError("saveElementAsPdf expects a DOM element");
  }

  const canvas = await html2canvas(element, {
    scale: 2,
    backgroundColor: "#ffffff",
    useCORS: true
  });

  const imageData = canvas.toDataURL("image/png");
  const pdf = new jsPDF({ unit: "mm", format: "a4" });
  const pageWidth = pdf.internal.pageSize.getWidth();
  const pageHeight = pdf.internal.pageSize.getHeight();
  const imageHeight = (canvas.height * pageWidth) / canvas.width;

  pdf.addImage(
    imageData,
    "PNG",
    0,
    0,
    pageWidth,
    Math.min(imageHeight, pageHeight)
  );
  pdf.save("capture.pdf");
}

This deliberately fits one image to the page width and limits the drawn height to one page. If the source is taller, content below the page boundary is clipped by this minimal example. Use a pagination strategy instead of silently shrinking a long document to unreadable text.

Paginate a tall canvas

A practical image-based approach slices the source canvas into page-sized regions and adds one region per PDF page. The following function keeps the image’s aspect ratio and uses a temporary canvas for each slice:

import { jsPDF } from "jspdf";
import html2canvas from "html2canvas";

export async function saveLongElement(element) {
  const source = await html2canvas(element, { scale: 2, backgroundColor: "#fff" });
  const pdf = new jsPDF({ unit: "mm", format: "a4" });
  const pageWidth = pdf.internal.pageSize.getWidth();
  const pageHeight = pdf.internal.pageSize.getHeight();
  const pagePixelHeight = Math.floor(source.width * pageHeight / pageWidth);

  for (let top = 0, page = 0; top < source.height; top += pagePixelHeight, page++) {
    const sliceHeight = Math.min(pagePixelHeight, source.height - top);
    const slice = document.createElement("canvas");
    slice.width = source.width;
    slice.height = sliceHeight;
    const context = slice.getContext("2d");
    if (!context) throw new Error("Canvas 2D context unavailable");
    context.drawImage(
      source, 0, top, source.width, sliceHeight,
      0, 0, source.width, sliceHeight
    );

    if (page > 0) pdf.addPage();
    const heightMm = sliceHeight * pageWidth / source.width;
    pdf.addImage(slice.toDataURL("image/png"), "PNG", 0, 0, pageWidth, heightMm);
  }

  pdf.save("long-capture.pdf");
}

Slicing can cut a paragraph, table row or card between pages. If page semantics matter, use the HTML pipeline with deliberate page-break CSS or split the source into sections before rendering. Rasterized pages also do not preserve ordinary selectable text.

Control the capture before conversion

Wait for content

Call html2canvas only after asynchronous data, web fonts and images have loaded. A useful pattern is to await your data fetch, then await document.fonts.ready when available. For images hosted on another origin, configure CORS on the image server and use html2canvas’s useCORS option where appropriate.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set a predictable viewport

The canvas reflects the element’s computed size. Give the capture container an explicit width, background color and responsive state. Avoid capturing an element while it is hidden with display:none; render it in a visible, stable container first.

Account for CSS support

html2canvas states that each CSS property needs explicit implementation and that some properties are unsupported. Effects such as unusual filters, advanced blend modes, cross-origin assets or browser-native controls may differ from a normal screenshot. Provide simpler fallback styles for the capture container and verify the output in your target evergreen browsers, including Chromium-based browsers, Firefox and Safari.

Choose scale and image format

A higher scale improves detail but increases memory use, encoding time and PDF size. PNG preserves sharp text and transparency; JPEG is smaller for photographic content but introduces compression artifacts. If you need a transparent result, do not paint a white background and use a format and PDF workflow that preserves the appearance you require.

Security and untrusted input

Use a current jsPDF release. A jsPDF advisory published March 17, 2026 reports HTML injection in certain output() overloads when user-controlled options reach those methods. Versions through 4.2.0 are listed as affected, and 4.2.1 is identified as the fix. Upgrade to 4.2.1 or newer and keep attacker-controlled filenames, viewer URLs and output options out of those overloads. This advisory concerns specific output() options; it should not be confused with html2canvas’s CSS-rendering limitations or treated as a claim that ordinary html() rendering is the affected feature.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and fixes

  • The result is blank: confirm the selector resolves to a visible element, run the capture after data and fonts load, and check that an ancestor is not hidden or zero-sized.
  • Images are missing or the canvas is tainted: serve images with suitable cross-origin headers, use same-origin assets, or replace inaccessible assets. A client-side renderer cannot bypass browser origin policy.
  • Styles do not match the page: simplify unsupported CSS, set explicit dimensions and backgrounds, and test the exact browser. html2canvas is not a pixel-perfect browser compositor.
  • Text is tiny: increase the canvas scale or choose a larger PDF page, while watching memory and file size. Do not enlarge a low-resolution canvas after capture.
  • Long content is cut off: use the slicing example or the HTML pipeline’s paging options. A single addImage() call only draws the rectangle you specify.
  • Fonts are wrong: wait for document.fonts.ready, ensure the font files are reachable, and verify their CORS headers.
  • PDF generation is slow or crashes: reduce capture width or scale, split very large pages into sections, and release temporary canvases after use.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One request renders a URL as PNG, JPEG, WebP or PDF, so you do not need to install a browser or maintain an html2canvas capture page.

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)
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}`);

See the parameter list and PDF options in the ScreenshotNeo documentation. It removes cookie-consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages and failed loads are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Which method should you use?

  • Use html() when your deliverable is a PDF and you want jsPDF to manage HTML rendering and page layout.
  • Use html2canvas plus addImage() when another part of your application needs the canvas or when you need exact image coordinates, custom slicing or compositing.
  • Use an image-capable API when the source is a remote URL and maintaining a browser capture environment is unnecessary.

Frequently Asked Questions

Does an image-based jsPDF PDF contain selectable text?

No. The canvas is raster image data. Text selection and accessibility require a PDF path that places text as PDF content rather than one bitmap.

Can html2canvas capture any website URL?

It captures DOM elements in the browser page where your code runs. Cross-origin assets still require appropriate browser permissions and response headers; it is not a server-side URL fetcher.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use PNG or JPEG?

PNG is usually clearer for interfaces and text. JPEG can reduce size for photographic pages but may show compression artifacts.

What is the safest jsPDF version for this workflow?

Use jsPDF 4.2.1 or newer, the patched line identified in the March 17, 2026 advisory, and avoid sending untrusted values into the affected output options.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.