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

Direct fix: jsPDF’s html() method requires html2canvas. Install or import html2canvas in the browser bundle, or load its script before calling pdf.html(). If you pass an HTML string rather than a DOM element, also provide DOMPurify. The conversion must run in a real browser, not Node.js.

What the error means

When the console says html2canvas is not loaded, is undefined, or pdf.html() fails immediately, jsPDF has reached a missing dependency. The html() method delegates page rendering to html2canvas, which turns the DOM into a canvas before jsPDF writes the PDF.

This is different from a blank PDF after successful loading. A missing global is a load or bundling problem; a blank or partial PDF can result from cross-origin assets, an oversized canvas, or content that has not finished rendering.

Fix it in a module or npm build

Install the browser dependencies

Install jsPDF and html2canvas in the application that executes in the browser. The html2canvas project documents this package name:

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

Import the dependency before the code that invokes html():

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

// This reference makes the dependency explicit to the bundler.
void html2canvas;

export function exportElement() {
  const element = document.getElementById("content");
  if (!element) throw new Error("#content was not found");

  const pdf = new jsPDF({ unit: "mm", format: "a4" });
  pdf.html(element, {
    callback: (doc) => doc.save("output.pdf")
  });
}

Some bundlers include html2canvas automatically when you import the full jsPDF distribution; an explicit import still makes dependency ownership and versioning clear. Keep the function on a browser-only path. Do not execute it during server-side rendering, at module evaluation time in a server process, or in a Node.js script.

Run after the DOM exists

Call the function from a click handler, a browser lifecycle hook, or another point after the target element has been mounted. A module can load correctly while still failing because document.getElementById("content") returns null or because images and fonts are still loading.

Fix it with script tags

Use the required order

For an unbundled html2pdf.js setup, load jsPDF first, html2canvas second, and html2pdf third:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<script src="jspdf.min.js"></script>
<script src="html2canvas.min.js"></script>
<script src="html2pdf.min.js"></script>

Bundled html2pdf.js builds include their dependencies. With an unbundled wrapper, however, the order matters because each layer expects the previous one to be available. Pin one compatible version of each script rather than mixing copies from different releases.

Call jsPDF after all scripts finish

function makePdf() {
  if (typeof window.html2canvas !== "function") {
    throw new Error("html2canvas did not load");
  }

  const content = document.getElementById("content");
  if (!content) throw new Error("#content was not found");

  const pdf = new jspdf.jsPDF();
  pdf.html(content, {
    callback: function (doc) {
      doc.save("output.pdf");
    }
  });
}

window.addEventListener("DOMContentLoaded", makePdf);

The typeof check is a quick diagnostic for script-tag builds. If it fails, inspect the browser’s Network and Console panels for a 404, blocked CDN request, failed integrity check, or Content Security Policy violation.

Bundled versus unbundled html2pdf.js

Setup Dependency handling What you must verify
Bundled build jsPDF, html2canvas and html2pdf are packaged together. Use the documented entry point and avoid loading a second copy globally.
Unbundled scripts Each library is downloaded independently. Load jsPDF → html2canvas → html2pdf, keep versions consistent, and wait for every script.
ES module build The bundler resolves imported packages. Import html2canvas in the browser bundle and do not run the conversion during SSR or in Node.js.

Why it still fails after html2canvas loads

The code is running in Node.js

html2canvas uses window, document, computed styles and other browser APIs. It is not suitable for Node.js. Move the conversion into client-side code, or use a browser automation or hosted screenshot service when the job must run on a server.

A script is blocked or loaded at the wrong path

Open DevTools and reload the page. A failed Network request, an integrity mismatch, or a CSP error leaves the global undefined even though the HTML contains a script tag. Correct the URL or CSP policy, remove duplicate versions, and confirm that the response is JavaScript rather than an error page.

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

The target is not ready

Wait until the element is mounted and any application data, web fonts, and images have settled. A click handler is safer than invoking conversion while a framework is rendering. Confirm the selector and temporarily export a small, static element to separate timing problems from rendering problems.

Images come from another origin

html2canvas can taint the canvas when it reads images or other resources from a different origin. The remote server must send suitable CORS headers. Its useCORS option requests CORS-enabled loading; it cannot grant permission to a server that sends no permission.

const pdf = new jsPDF();
pdf.html(document.getElementById("content"), {
  html2canvas: {
    useCORS: true
  },
  callback: (doc) => doc.save("output.pdf")
});

Alternatively, proxy the asset through your own origin, host it locally, or omit the problematic image. Check the Console for canvas-taint messages and verify the image response headers.

The canvas is too large

Very tall or wide documents can exceed browser canvas limits. The result may be blank or only partly rendered without a useful JavaScript exception. Reduce the capture width, lower the scale, shorten the document, or split it into sections and generate multiple pages. Test with a smaller viewport before increasing dimensions again.

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

Duplicate or incompatible libraries are present

A page that loads one html2canvas globally and bundles another copy can pass a superficial existence check while jsPDF uses a different object. Remove duplicate tags, let the bundler own the dependency, and inspect the generated bundle or import graph so the symbol used by the calling code is the one actually loaded.

A reliable diagnostic checklist

  1. Confirm the conversion runs in a browser context with window and document.
  2. Confirm the target element exists and is visible when html() starts.
  3. For modules, install and import @html2canvas/html2canvas before the export path.
  4. For script tags, verify jsPDF → html2canvas → html2pdf order.
  5. Run typeof window.html2canvas and expect "function" in a script-tag build.
  6. Use DevTools Network and Console to find blocked, missing, or disallowed scripts.
  7. Remove duplicate versions and retest with a minimal same-origin element.
  8. If loading succeeds but output is blank, investigate CORS and canvas dimensions rather than reinstalling the package.

When to use a server-side capture instead

Client-side html2canvas is useful when the user is already viewing the page and the content is same-origin and moderate in size. A hosted capture is simpler for scheduled jobs, backend workflows, pages requiring a consistent browser environment, or URLs you do not control. It also avoids shipping a rendering stack to every visitor.

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

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server for developers. One GET request returns PNG, JPEG, WebP or PDF, so your server does not need to load jsPDF and html2canvas in a browser. Cookie and consent banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages, failed loads and timeouts are not billed, and the response identifies the page verdict and billing status in headers.

One-call example

See the parameter reference in the ScreenshotNeo documentation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

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(`${res.status} ${res.statusText}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));

ScreenshotNeo also supports full-page captures with lazy images, CSS-selector element shots, dark mode, device presets, custom viewports, retina scale, PDF paper and margin settings, custom CSS and JavaScript, clicks, waits, request blocking, cookies, headers, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info and capture_pdf to 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 shots; every feature is included on every plan. Create a free ScreenshotNeo account to try the hosted path.

FAQ

Does importing html2canvas alone fix a blank PDF?

No. Importing fixes a missing dependency. A blank result after successful loading requires checking cross-origin resources, document dimensions, rendering timing and browser canvas limits.

Can I call pdf.html() from a Web Worker?

The documented dependency requires browser DOM APIs, so code that has no window or document cannot provide the environment html2canvas expects.

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

Should I load both html2pdf.js and html2canvas manually?

Only for an unbundled setup. A bundled html2pdf.js build is intended to include its dependencies; manually adding another copy can create version conflicts.

Frequently Asked Questions

Does importing html2canvas alone fix a blank PDF?

No. Importing fixes a missing dependency. A blank result after successful loading requires checking cross-origin resources, document dimensions, rendering timing and browser canvas limits.

Can I call pdf.html() from a Web Worker?

The documented dependency requires browser DOM APIs, so code that has no window or document cannot provide the environment html2canvas expects.

Should I load both html2pdf.js and html2canvas manually?

Only for an unbundled setup. A bundled html2pdf.js build is intended to include its dependencies; manually adding another copy can create version conflicts.

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

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.