What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
To render a local image in a Puppeteer PDF, make the image reachable to the browser, wait until it has loaded, and then call page.pdf(). With page.setContent(), do not assume a relative image path resolves from your HTML file’s directory: the API sets markup but does not document a filesystem base URL. For a reliable workflow, use an absolute file:// URL only after verifying access in your runtime, or serve the image over HTTP or embed it as a data URL.
Why local images disappear from Puppeteer PDFs
Puppeteer’s Page.pdf() prints the current page to PDF. If you build the page with page.setContent(html), Puppeteer assigns that markup to the page; its documented API does not establish that relative URLs should resolve against the directory containing your source HTML. So an <img src="./logo.png"> may not point to the file you expect.
There are two separate conditions to satisfy: the browser must be able to access the intended image resource, and the image must finish loading before PDF generation. A successful call to setContent() proves only that markup was assigned. It does not prove that Chrome can read a local file or that an image loaded successfully.
Puppeteer’s PDF guide and API references reviewed on September 29, 2026, display versions 25.12.0 for PDF generation and PDF options, 25.11.0 for setContent(), and “Next” for the lifecycle-options page. Check the documentation for the version installed in your project because the reviewed pages do not establish universal local-file rules across operating systems, Chrome builds, or containers. Puppeteer’s setContent() reference
#1 Best Overall
Choose how the browser will reach the image
| Method | Useful when | Trade-off |
|---|---|---|
Absolute file:// URL |
The image is on the same machine and your controlled browser runtime permits access to that exact path. | Access behavior depends on the browser and runtime configuration. Verify it in the environment that creates the PDF; there is no universally established launch flag or permission rule in the reviewed documentation. |
| Local HTTP route | Your application already has a local server, or can serve files through a controlled route. | The browser must be able to reach the route, and the server must expose the intended file. |
| Data URL | You have a small image and want to include its bytes directly in the HTML. | Encoding increases the HTML size and is less convenient for multiple or large images. |
These are implementation choices, not guarantees of Puppeteer behavior. Pick the one that fits your deployment, then test resource access under the same operating-system user and browser setup used in production. The Puppeteer Files guide covers file upload and notes that Puppeteer does not provide programmatic file downloads; it does not define local images as page subresources.
Complete Node.js example using a local HTTP image
Serving the asset over HTTP avoids relying on an assumed relative base path. This example starts a small local server for one image, loads HTML that references that route, checks every image element, and writes a PDF. Install Puppeteer with npm install puppeteer, save the code as render-pdf.js, and run it with node render-pdf.js /absolute/path/to/logo.png.
const http = require('node:http');
const fs = require('node:fs');
const path = require('node:path');
const puppeteer = require('puppeteer');
async function main() {
const imagePath = path.resolve(process.argv[2] || 'logo.png');
if (!fs.existsSync(imagePath)) {
throw new Error(`Image not found: ${imagePath}`);
}
const server = http.createServer((req, res) => {
if (req.url !== '/logo') {
res.writeHead(404).end('Not found');
return;
}
res.writeHead(200, { 'Content-Type': 'image/png' });
fs.createReadStream(imagePath).pipe(res);
});
await new Promise((resolve, reject) => {
server.once('error', reject);
server.listen(0, '127.0.0.1', resolve);
});
let browser;
try {
const address = server.address();
const imageUrl = `http://127.0.0.1:${address.port}/logo`;
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(`
<!doctype html>
<html><head><meta charset="utf-8">
<style>body { font: 16px sans-serif; } img { max-width: 300px; }</style>
</head><body>
<h1>PDF with a local image</h1>
<img src="${imageUrl}" alt="Logo">
</body></html>`,
{ waitUntil: 'load' }
);
const imageResults = await page.evaluate(async () => {
const images = [...document.images];
await Promise.all(images.map(image => {
if (image.complete) return Promise.resolve();
return new Promise(resolve => {
image.addEventListener('load', resolve, { once: true });
image.addEventListener('error', resolve, { once: true });
});
}));
return images.map(image => ({
src: image.currentSrc || image.src,
loaded: image.complete && image.naturalWidth > 0,
}));
});
const failures = imageResults.filter(image => !image.loaded);
if (failures.length) {
throw new Error(`Image failed to load: ${JSON.stringify(failures)}`);
}
await page.pdf({ path: 'output.pdf', printBackground: true });
console.log('Wrote output.pdf');
} finally {
if (browser) await browser.close();
await new Promise(resolve => server.close(resolve));
}
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});
The sample serves the supplied file with a PNG content type; change that header if the asset is another format. For multiple images, serve each from a controlled route or use another access method, then check all required image elements. If your page inserts images asynchronously, perform the check after that code has run. The readiness check waits until each image either loads or errors, then rejects the PDF step if any image has no positive naturalWidth.
Rank #2
Using a data URL for a small image
If you already have the image bytes in Node, a data URL removes the need for a filesystem URL or local server. This example reads a PNG and embeds it in the HTML; the same image-readiness check is still appropriate before printing.
Free tools Windows power users keep installed
One-click scans. No signup required.
const fs = require('node:fs');
const puppeteer = require('puppeteer');
(async () => {
const bytes = fs.readFileSync('/absolute/path/to/logo.png');
const imageData = `data:image/png;base64,${bytes.toString('base64')}`;
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setContent(`<html><body><img src="${imageData}" alt="Logo"></body></html>`);
const result = await page.evaluate(() => [...document.images].map(image => ({
src: image.currentSrc || image.src,
loaded: image.complete && image.naturalWidth > 0,
})));
if (result.some(image => !image.loaded)) throw new Error('Image did not load');
await page.pdf({ path: 'output.pdf', printBackground: true });
} finally {
await browser.close();
}
})().catch(error => { console.error(error); process.exitCode = 1; });
For a larger image set, embedding every asset can make the HTML cumbersome. A local server is often easier to operate in that situation, provided it is reachable from the browser process.
Using a file URL or an existing page
File URLs
A file:// URL can be used in a controlled environment if the browser is permitted to read the exact file. Resolve the path in Node rather than guessing the working directory, and construct a file URL with Node’s URL utilities. Then inspect the image’s actual currentSrc and loaded state before printing. Because file access depends on the specific browser launch and deployment environment, test this approach in the same OS, container, and process identity used in production. Do not add a launch flag merely because an example on another system uses one.
Pages loaded with goto()
If you navigate to an HTML file with page.goto(), relative references may resolve according to that page’s URL context, but the browser still needs access to the image. This differs from passing only markup to setContent(); do not conflate assigning HTML with navigating to a file. Confirm the actual resolved URL and access behavior in your own runtime.
Wait for page-specific work
setContent() supports lifecycle waiting options, but an event such as load should not be treated as proof that every required image succeeded. Puppeteer documents waiting for fonts in PDF options; it does not promise a general wait for every image or arbitrary asynchronous page task. Check the resources your PDF depends on explicitly. SetContentWaitForOptions reference
Generate the PDF with the right print settings
Call page.pdf() only after resource checks pass. Puppeteer uses print media by default, so print CSS can make the PDF differ from a screen preview. It also omits background graphics unless printBackground is enabled. That option affects CSS backgrounds; it does not make an inaccessible or unloaded <img> appear.
Rank #4
await page.pdf({
path: 'output.pdf',
printBackground: true,
format: 'A4',
waitForFonts: true,
timeout: 30000,
});
Relevant documented options include:
printBackgrounddefaults tofalse; set it totruewhen the design needs CSS background graphics.waitForFontsdefaults totrueand waits fordocument.fonts.ready. Puppeteer notes that bringing a background page to the front may be necessary.formatdefaults toletterand takes priority overwidthandheightwhen set.preferCSSPageSizedefaults tofalse; when enabled, CSS@pagesizing takes priority over PDF width, height, or format.scaledefaults to1and accepts a documented range from0.1to2.timeoutdefaults to 30,000 milliseconds; setting it to zero disables the timeout.- Without a
path,page.pdf()returns aUint8Array. A relative output path is resolved from the current working directory.
PDF generation modifies colors for printing by default. If exact color rendering matters, Puppeteer documents CSS -webkit-print-color-adjust as a way to request it. Use page.emulateMediaType('screen') only when screen media, rather than print media, is explicitly what the PDF should reflect. See the PDF generation guide, PDFOptions reference, and Page reference.
Troubleshoot missing or changed images
- Broken icon or blank image: log
currentSrcandsrc, check the browser can read the file as the Puppeteer process identity, and inspect failed requests or the page before callingpdf(). A relative path in markup assigned withsetContent()is not proof of a valid filesystem path. - The readiness check says the image failed: confirm the path exists, the local route returns the intended bytes, or the data URL has the right MIME type and complete base64 data. Fix the resource error before generating the PDF.
- Image works in a development screenshot but not the PDF: inspect print CSS and media behavior. Puppeteer prints using print media by default; use screen media only if that is the desired output.
- Background illustration is absent: enable
printBackground: true. This does not repair a failed image element. - Colors differ: PDF printing alters colors by default; request exact CSS color rendering with
-webkit-print-color-adjustwhere appropriate. - Fonts or layout timing differs:
waitForFontsconcerns document fonts, not image readiness or all asynchronous page logic. Await application work and verify image completion separately. - Works locally, fails in a container or production: test with the same OS, Chrome/Puppeteer setup, filesystem layout, permissions, and process identity. The reviewed API references do not settle file URL permissions across environments, and they do not identify a universal launch option.
Or skip the browser setup
If your goal is a screenshot or PDF of a publicly reachable webpage rather than a PDF containing a local filesystem image, ScreenshotNeo provides a website screenshot API and MCP server. It is not a way to attach an image that exists only on your machine; make that asset available to the page first if the rendered page needs it.
One GET request can capture a URL. This cURL example saves a WebP screenshot of Stripe:
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Best Value
- Used Book in Good Condition
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 documentation for options, including PDF output. Cookie banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks/CAPTCHAs, blank pages, failed loads, timeouts, and cache hits are not billed, with response headers indicating the page verdict and billing status. Its MCP server lets AI agents use screenshot and PDF tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.
Sign up free for ScreenshotNeo to get 1,000 screenshots a month with no card.
References
- Puppeteer PDFOptions interface
- Puppeteer PDF generation guide
- Puppeteer Page class
- Puppeteer Page.setContent() method
- Puppeteer Files guide
- Puppeteer SetContentWaitForOptions interface
Frequently Asked Questions
Does waitForFonts wait for local images too?
No. It waits for document.fonts.ready; check required image elements separately before calling page.pdf().
Can I use this method when the image is on another machine?
The browser needs a reachable resource. A local file path on the Node host is not automatically available to a browser on a different machine; serve the image through a route reachable by that browser.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsQuick 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.

