jsPDF throws Invalid Image when addImage() cannot validate the value you supplied as an image. The quickest fix is to pass complete, unmodified image data in a supported representation: a data:image/...;base64,... URL, a loaded HTMLImageElement, a canvas, a Uint8Array, or RGBA data. Make the declared format agree with the actual bytes, wait for remote images to finish loading, and re-rasterize troublesome PNGs through a canvas.
What Invalid Image means in jsPDF
addImage(imageData, format, x, y, width, height) validates the first argument before embedding it. The API accepts a base64 data URL, an HTMLImageElement, an HTMLCanvasElement, a Uint8Array, or an RGBAData object. Supported format values include JPEG, PNG, and WEBP. If the value is malformed, incomplete, the wrong type, or does not match the declared format, jsPDF raises an error instead of writing the PDF.
A URL such as https://example.com/photo.jpg is not the same thing as image bytes or a base64 data URL. Normalize a remote resource by loading it into an image element or converting its response to bytes before calling addImage.
Use a valid input representation
Canvas data URL
This is a dependable browser pattern when you already draw or capture content on a canvas:
Recommended Free Tools
#1 Best Overall
import { jsPDF } from "jspdf";
const canvas = document.querySelector("canvas");
const dataUrl = canvas.toDataURL("image/png");
const pdf = new jsPDF();
pdf.addImage(dataUrl, "PNG", 10, 10, 100, 70);
pdf.save("output.pdf");
Keep the complete prefix, including data:image/png;base64,. It identifies the media type and tells jsPDF how to extract the payload. Do not remove that prefix unless you are deliberately passing raw bytes in a typed array.
A loaded HTML image element
Wait for onload (or the image’s decode() promise) before adding the element:
import { jsPDF } from "jspdf";
const image = new Image();
image.onload = () => {
const pdf = new jsPDF();
pdf.addImage(image, "JPEG", 10, 10, 100, 70);
pdf.save("output.pdf");
};
image.onerror = () => console.error("Image could not be loaded");
image.src = "/images/photo.jpg";
For a cross-origin image, the server must permit the browser’s request if you intend to draw it to a canvas. Otherwise the canvas can become unusable for export; loading the element itself may still work, but converting it to a data URL will not.
Rank #2
Raw bytes in a typed array
When you fetch an image as an ArrayBuffer, retain the bytes and pass a Uint8Array. State the format when it is not unambiguous:
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteconst response = await fetch("/images/logo.png");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const bytes = new Uint8Array(await response.arrayBuffer());
const pdf = new jsPDF();
pdf.addImage(bytes, "PNG", 10, 10, 100, 70);
pdf.save("output.pdf");
Do not convert arbitrary binary data to a JavaScript string. String conversions can alter bytes and produce an apparently valid but corrupt base64 value.
RGBA data
If your renderer provides an RGBAData object (pixel data, width, and height), pass that object in the form expected by your installed jsPDF release. This avoids base64 transport entirely, but the dimensions and array length must describe the same image.
A diagnostic sequence that isolates the cause
- Inspect the value. Log
typeof imageData, its constructor, and its length. It should be a string, loaded image element, canvas,Uint8Array, or RGBAData object—not a URL string masquerading as base64. - Check a string’s prefix. A PNG should begin with
data:image/png;base64,; JPEG commonly begins withdata:image/jpeg;base64,. Verify that the text after the comma is non-empty. - Check completeness. Compare the value before and after storage, JSON serialization, database transport, or HTTP transfer. Truncation, inserted whitespace, URL encoding, and altered padding can corrupt the payload.
- Match the format. A PNG must not be declared as
JPEG. A remote URL must not be passed as if it were a base64 string. When recognition is uncertain, inspect the original bytes and use the matching format. - Wait for loading. Call
addImageonly from the image’s load handler or afterawait image.decode(). A newly assignedsrcis not proof that pixels are available. - Normalize remote images. Fetch the resource into bytes, or draw a successfully loaded image to a canvas and call
toDataURL(). This also avoids relying on direct-URL behavior that has varied across releases. - Try a canvas for difficult PNGs. Some PNG filter combinations have been reported to fail when added directly; a canvas-produced PNG data URL worked in the reported case. Treat this as a compatibility workaround, not a guarantee for every file.
- Reproduce on your exact jsPDF version. One report found a canvas PNG working in 2.3.1 and failing in 2.4.0 and 2.5.0, with differences involving JPEG backgrounds and WEBP conversion. The issue reports are version- and file-specific, so test the same bytes with a minimal example.
PNG, JPEG, or WEBP?
| Format | Use it when | Important caveat |
|---|---|---|
| PNG | You need lossless graphics or transparency. | Decoder and filter edge cases can affect particular files. Re-rasterize through a canvas if direct insertion fails. |
| JPEG | The image is photographic and opaque. | JPEG has no alpha channel. Converting a transparent PNG can produce a solid (including black) background. |
| WEBP | Your installed jsPDF version handles the source reliably and you want WEBP input. | A project report observed an 8-bit-looking conversion in its tested versions. Verify output in your target release. |
Changing the format argument does not convert the bytes. A PNG payload remains PNG bytes until you actually decode and re-encode it, for example by drawing it to a canvas and exporting JPEG.
Reliable remote-image conversion in the browser
import { jsPDF } from "jspdf";
function loadImage(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = src;
});
}
const image = await loadImage("https://example.com/photo.jpg");
const pdf = new jsPDF();
pdf.addImage(image, "JPEG", 10, 10, 100, 70);
pdf.save("output.pdf");
If you need a canvas data URL instead, draw after loading:
Free tools Windows power users keep installed
One-click scans. No signup required.
const canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
canvas.getContext("2d").drawImage(image, 0, 0);
const normalized = canvas.toDataURL("image/png");
pdf.addImage(normalized, "PNG", 10, 10, 100, 70);
Cross-origin policy still applies. Configure the image server and the element’s request appropriately before drawing; otherwise the browser may refuse the export.
Rank #4
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
Supplied Data is not a valid base64-String |
A URL, stripped prefix, or malformed payload was supplied. | Pass a complete data URL, a loaded image element, or a Uint8Array. Keep the data:image/...;base64, prefix. |
Incomplete or corrupt PNG file |
Bytes were truncated or changed during transport. | Obtain the original response again, compare lengths, and avoid string-based binary conversions. |
| PNG fails but JPEG works | A decoder/filter edge case or a release-specific PNG regression. | Re-rasterize through a canvas, test the same file on your jsPDF version, or use JPEG only when losing transparency is acceptable. |
| Black background after conversion | Transparent PNG alpha was discarded by JPEG. | Keep PNG, flatten onto an intentional background before JPEG export, or preserve transparency with a supported PNG path. |
| Image is blank or partially drawn | addImage ran before loading finished, or the source response was incomplete. |
Use onload/decode(), check the HTTP response, and validate dimensions before insertion. |
| WEBP output looks reduced in color depth | Conversion behavior differs by jsPDF release and input. | Test your exact version and compare PNG or JPEG output for the required fidelity. |
Performance, reliability, and cost considerations
- Memory: data URLs add base64 text overhead, while a typed array keeps binary bytes. Large full-page images can consume substantial browser memory either way.
- Dimensions: scale the canvas to the PDF size when possible; decoding a huge source only to shrink it increases work.
- Repeatability: keep a minimal fixture image and record the jsPDF version whenever upgrading. A file that worked in one release is not proof that every decoder path is stable.
- Validation: check HTTP status, byte length, image dimensions, and the first bytes before handing data to
addImage. - Security: do not place secrets in client-visible image URLs or custom headers. Remote images can also fail because of authentication, redirects, or content negotiation.
Or skip the browser setup
If your goal is a clean screenshot to place in a PDF, ScreenshotNeo returns PNG, JPEG, WebP, or PDF from one GET request. It accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Bot checks, 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. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
cURL:
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)
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}`);
See the ScreenshotNeo documentation for the request options. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.FAQ
Can I pass a plain image URL to addImage?
Do not assume it will work across releases. Load the URL into an image element or fetch it as bytes first, then pass the resulting supported representation.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Should I always convert PNG to JPEG?
No. JPEG removes transparency and can introduce a background. Convert only when the image is opaque and JPEG’s compatibility or size is more important than alpha and lossless detail.
Best Value
Why does the same code work on one jsPDF release but not another?
Image decoder and conversion behavior can change between releases. Re-run a minimal reproduction with the exact image bytes and installed version before changing application logic.
Frequently Asked Questions
Can I pass a plain image URL to addImage?
Do not assume it will work across releases. Load the URL into an image element or fetch it as bytes first, then pass the resulting supported representation.
Should I always convert PNG to JPEG?
No. JPEG removes transparency and can introduce a background. Convert only when the image is opaque and JPEG’s compatibility or size is more important than alpha and lossless detail.
Why does the same code work on one jsPDF release but not another?
Image decoder and conversion behavior can change between releases. Re-run a minimal reproduction with the exact image bytes and installed version before changing application logic.
Quick Recap
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.

