Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
World desk10 min

How to Convert HTML to an Image and Add It to a PDF with jsPDF

A practical guide to rendering HTML with html2canvas, placing the image in jsPDF, handling long pages and CSS limitations, and choosing the integrated html() method or ScreenshotNeo.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The reliable image-first workflow is: render a DOM element with html2canvas, await its canvas, convert the canvas to PNG data, place that image in a jsPDF document with explicit dimensions, and save the PDF. For a mostly automatic HTML-to-PDF conversion, use jsPDF’s html() method instead. The image route gives you the rendered pixels and precise image placement; the integrated route handles more of the PDF layout for you.

Choose the right jsPDF workflow

There are two practical implementations. Both render the page visually, so the result depends on browser rendering and html2canvas CSS support rather than on a full browser print engine.

Need Recommended method What you control Main limitation
Turn an existing element into a PDF quickly pdf.html(element, options) Page size, margins, dimensions, html2canvas options and PDF output Rendering still follows html2canvas’s supported CSS subset
Obtain image data or position a raster capture yourself html2canvas() followed by addImage() Image format, coordinates, scaling and page composition You must handle tall content and pagination

Use html() when your goal is simply an HTML element in a PDF. Use the explicit canvas path when you also need a PNG, want to crop or resize the result, or need to compose several images on custom pages. An image added with addImage() is raster content: text in that portion is not ordinary selectable PDF text.

Install the dependencies

In a browser application, install jsPDF and html2canvas:

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

Import both modules in your JavaScript entry point. If you pass an HTML string to jsPDF’s HTML module instead of a DOM element, the module also relies on DOMPurify. Prefer a real, sanitized DOM element whenever possible, especially when any markup or data originated outside your application.

Image-first: render a DOM element, then add it to a PDF

This complete example captures the element with id invoice, converts the canvas to PNG data, scales it to the PDF’s usable width, and saves a one-page file.

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

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

  const canvas = await html2canvas(element, {
    backgroundColor: "#ffffff",
    scale: Math.min(window.devicePixelRatio || 1, 2),
    useCORS: true
  });

  const imageData = canvas.toDataURL("image/png");
  const pdf = new jsPDF({
    orientation: "portrait",
    unit: "mm",
    format: "a4"
  });

  const pageWidth = pdf.internal.pageSize.getWidth();
  const pageHeight = pdf.internal.pageSize.getHeight();
  const margin = 10;
  const usableWidth = pageWidth - margin * 2;
  const imageHeight = (canvas.height * usableWidth) / canvas.width;

  pdf.addImage(
    imageData,
    "PNG",
    margin,
    margin,
    usableWidth,
    Math.min(imageHeight, pageHeight - margin * 2),
    undefined,
    "FAST"
  );

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

document.querySelector("#save-pdf").addEventListener("click", async () => {
  const button = document.querySelector("#save-pdf");
  button.disabled = true;
  try {
    await saveElementAsPdf(document.querySelector("#invoice"));
  } catch (error) {
    console.error("PDF capture failed", error);
    alert("The PDF could not be created. Check the console for details.");
  } finally {
    button.disabled = false;
  }
});

The Math.min() in this deliberately minimal version prevents the image from being drawn below the page. It also means a capture taller than one page is clipped. Do not use this one-page logic for a report, invoice or article that can grow beyond the page; choose one of the pagination approaches below.

Keep the capture stable before rendering

  • Wait for fonts and important images before calling html2canvas.
  • Give the target element a fixed or predictable width so line wrapping does not change between screens.
  • Set a solid backgroundColor when transparent page backgrounds would make text hard to read.
  • Use scale for sharper output, but watch memory use for large elements.
  • Use useCORS: true only when remote image servers send suitable CORS headers; it cannot bypass browser security rules.
await document.fonts.ready;
await Promise.all(
  [...document.querySelectorAll("#invoice img")].map((img) =>
    img.complete
      ? Promise.resolve()
      : new Promise(resolve => {
          img.addEventListener("load", resolve, { once: true });
          img.addEventListener("error", resolve, { once: true });
        })
  )
);

Paginate a tall canvas

A canvas is one bitmap. To make a multi-page image PDF, slice that bitmap into page-height sections and add each section at the same displayed width. The following function uses an offscreen canvas for each slice and adds pages until the source is exhausted.

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

function canvasToMultiPagePdf(canvas, filename = "capture.pdf") {
  const pdf = new jsPDF({ unit: "mm", format: "a4", orientation: "portrait" });
  const margin = 10;
  const pageWidth = pdf.internal.pageSize.getWidth();
  const pageHeight = pdf.internal.pageSize.getHeight();
  const outputWidth = pageWidth - margin * 2;
  const outputHeight = pageHeight - margin * 2;
  const pixelsPerPage = Math.floor(canvas.width * outputHeight / outputWidth);

  let sourceY = 0;
  let page = 0;
  while (sourceY < canvas.height) {
    const sliceHeight = Math.min(pixelsPerPage, canvas.height - sourceY);
    const slice = document.createElement("canvas");
    slice.width = canvas.width;
    slice.height = sliceHeight;
    const context = slice.getContext("2d");
    context.drawImage(
      canvas,
      0, sourceY, canvas.width, sliceHeight,
      0, 0, canvas.width, sliceHeight
    );

    if (page > 0) pdf.addPage();
    const displayedHeight = sliceHeight * outputWidth / canvas.width;
    pdf.addImage(
      slice.toDataURL("image/png"),
      "PNG",
      margin,
      margin,
      outputWidth,
      displayedHeight,
      undefined,
      "FAST"
    );

    sourceY += sliceHeight;
    page += 1;
  }

  pdf.save(filename);
}

Bitmap slicing can split a heading, table row or paragraph at a page boundary. If readable page breaks matter, divide the source DOM into page-sized sections before rendering, or use jsPDF’s HTML pipeline with page-break options and inspect the output. Scaling an entire long page onto one sheet preserves all pixels but usually makes the text impractical to read.

Use jsPDF’s integrated html() method

For a normal element-to-PDF operation, this shorter route lets jsPDF coordinate the HTML rendering pipeline:

import { jsPDF } from "jspdf";

async function saveWithHtmlModule() {
  const element = document.querySelector("#invoice");
  const pdf = new jsPDF({ unit: "mm", format: "a4", orientation: "portrait" });

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

saveWithHtmlModule().catch(console.error);

Option names and behavior can vary with the jsPDF version installed, so check the API documentation for that version. The HTML module can render through a prepared container and html2canvas, or use jsPDF’s PDF context. Margins, page dimensions, and html2canvas settings all affect the result. Test the exact CSS, browser and package versions used in production.

CSS, images and browser constraints

html2canvas resolves asynchronously and returns a canvas, but it does not reproduce every CSS property. Its documentation notes that each property must be implemented individually, so unsupported properties can be missing or visually different. Modern evergreen browsers such as Chrome/Chromium, Firefox and Safari are the practical target, yet browser-specific rendering differences remain.

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.
  • Cross-origin images: images without permissive CORS headers can be omitted or make the canvas unusable. Proxy them through your own origin or configure the asset server.
  • Web fonts: wait for document.fonts.ready; otherwise fallback fonts alter line breaks and height.
  • Animations and videos: pause animations and use a poster frame for video if a deterministic capture is required.
  • Fixed positioning: test sticky and fixed elements; their visual position may differ from a browser screenshot or print layout.
  • Large canvases: high scale values multiply width, height and memory. Lower the scale or capture sections when mobile devices run out of memory.
  • Transparency: PNG can preserve transparent pixels, but a PDF page often needs a deliberate background color for consistent printing.

Security and dependency maintenance

A jsPDF security advisory published March 17, 2026 reports HTML injection in certain output() overloads when attacker-controlled options reach those methods. Versions through 4.2.0 are listed as affected; version 4.2.1 is identified as the fix. Upgrade to a patched release and do not pass untrusted filenames, viewer URLs or other options into affected overloads. This advisory concerns particular output() options; it is separate from html2canvas’s CSS-support limitations and is not a claim that ordinary html() rendering is itself vulnerable.

Sanitize untrusted HTML before inserting it into the DOM. Avoid treating a PDF filename, URL or viewer option supplied by a user as trusted configuration. Lock and regularly review your jsPDF and html2canvas versions, then test PDF generation after upgrades.

Performance, reliability and output choices

PNG versus JPEG

PNG keeps text, lines and flat colors crisp and supports transparency, but files are larger. JPEG can be smaller for photographic content, but compression artifacts appear around text and it does not preserve transparency. Choose the format passed to toDataURL() and the matching type passed to addImage().

Reduce work before capture

  • Capture only the required subtree instead of the entire document.
  • Resize oversized source images before they enter the DOM.
  • Use a moderate scale and measure memory on your lowest-powered supported device.
  • Move repeated captures to a queued job rather than starting many high-resolution canvases at once.
  • For deterministic server-side captures, use a browser-based screenshot service instead of depending on a user’s viewport, fonts and network.

Validate the resulting file

Open the PDF in more than one viewer, check every page boundary, confirm remote images and fonts loaded, and verify that the saved dimensions match your intended paper size. Compare a representative long document, not only a short card that fits on one page.

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

Troubleshooting

The PDF is blank

Make sure the element exists and is visible when capture starts. Await the html2canvas promise, wait for fonts and images, and inspect browser console errors. A zero-width or zero-height element produces no useful image.

Images are missing or the canvas is tainted

Check the image response’s CORS headers. Host the assets on the same origin, configure the asset server for the requesting origin, or proxy the files. useCORS: true helps only when the server permits the request.

Text or CSS looks different

Identify unsupported CSS, wait for web fonts, and use stable dimensions. Replace effects that do not render reliably with simpler backgrounds, borders and layouts. Inspect the output in the browser you support rather than assuming pixel-perfect parity.

Only the top of a long document appears

The one-page image example intentionally limits its height. Use canvas slicing, render separate page sections, or configure the HTML module’s paging behavior. Do not merely increase the PDF image height beyond the page.

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

The browser becomes unresponsive

Lower the capture scale, reduce the element’s pixel dimensions, split the document, and avoid several simultaneous high-resolution captures. Large canvases consume memory proportional to their pixel count.

The download never starts

Call the save operation after the asynchronous render completes and from a user-initiated action where browser download policies require it. Catch and log rejected promises so a rendering failure is not mistaken for a download problem.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you need a clean screenshot or PDF from a URL rather than a DOM element already in your application, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

One request returns PNG, JPEG, WebP or a PDF. The API supports full-page captures with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper size/margins/orientation/page ranges, HTML/CSS to image, custom JavaScript and CSS, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Common parameter names used by other screenshot APIs also work.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

For AI workflows, its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for options and response handling. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Create a free ScreenshotNeo account.

Equivalent calls from Python and Node.js

Python

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)

Node.js

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 data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);

These API examples capture the supplied URL directly. If your objective is a PDF assembled from an in-browser, user-specific DOM state, keep the jsPDF workflow; if it is a repeatable URL capture, the API avoids browser setup and exposes a verdict for each response.

Frequently Asked Questions

Does the image-first PDF preserve selectable text?

No. Once the canvas is encoded and inserted with addImage(), that region is a bitmap. Use the HTML module or a text-oriented PDF generation approach when selectable text is required.

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

Can html2canvas capture a page from another domain?

It can capture a DOM element in your page, but cross-origin assets require suitable CORS response headers or a same-origin proxy. It is not a bypass for browser security policy.

Why does my multi-page output split rows awkwardly?

Canvas slicing cuts pixels at fixed boundaries. Render deliberate page sections or use the HTML module’s paging behavior when semantic page breaks are more important than a single continuous screenshot.

The Bottom Line

Use pdf.html() for the shortest HTML-to-PDF path; use html2canvas() plus addImage() when you need the actual image and custom placement. In both cases, plan for unsupported CSS, cross-origin assets, asynchronous fonts and explicit pagination for tall content.

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.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

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

More from the Wire

  1. Shenzhen desk3 min
    HONOR Expands Beyond Smartphones With Humanoid Robot RevealHONOR said it unveiled its first humanoid robot at MWC 2026 and named shopping assistance, workplace inspections, and supportive companionship as intended uses. Later Robotics D1 claims and a reported…
  2. Cupertino desk5 min
    Apple Unveils AirPods Max 2: The Upgrade That Should Have Happened Years AgoAirPods Max 2 adds H2-powered audio features and Apple claims up to 1.5× more effective ANC, but its design, Smart Case, and 20-hour battery rating are unchanged. Wired lossless audio…
  3. Cupertino desk4 min
    Apple’s OLED Touch MacBooks Are Coming—but the Dynamic Island Is the Real GambleApple has not announced an OLED touchscreen MacBook, but reports point to high-end models arriving in late 2026 or early 2027. The reported Mac Dynamic Island could be useful, but…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.