Enable request interception, match the image request, decode the Base64 text into bytes, and fulfill the request with request.respond(). Set an accurate image MIME type, continue every request you do not replace, and guard against another handler resolving the same request.
Working implementation
This complete example replaces one HTTP(S) image while allowing the rest of the page to load normally. The Base64 value must contain the encoded image bytes, not a data: URL prefix.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const imageBase64 = '...'; // Base64-encoded PNG bytes
const targetImageUrl = 'https://example.test/assets/hero.png';
const imageBytes = Buffer.from(imageBase64, 'base64');
await page.setRequestInterception(true);
page.on('request', request => {
if (request.isInterceptResolutionHandled()) return;
if (request.url() === targetImageUrl) {
request.respond({
status: 200,
contentType: 'image/png',
body: imageBytes
});
return;
}
request.continue();
});
await page.goto('https://example.test', { waitUntil: 'networkidle2' });
// ...inspect the page or capture a screenshot...
await browser.close();
The sample uses a PNG payload, so it returns image/png. For JPEG bytes use image/jpeg; for WebP use image/webp. The response body can be a string or a byte array; Node’s Buffer is suitable for the byte-array form.
What each part does
Turn interception on before responding
page.setRequestInterception(true) activates Puppeteer’s request-resolution methods, including continue(), abort(), and respond(). Calling respond() without interception enabled does not provide the mocked network response.
Recommended Free Tools
#1 Best Overall
Match the request you intend to replace
Exact URL matching is the safest choice when one known asset should change. Query strings, signed URLs, cache-busting parameters, redirects, and alternate hosts can make an exact string comparison too strict. In those cases, inspect the URL with new URL(request.url()) and compare its origin, pathname, or selected query parameters.
const requested = new URL(request.url());
const target = new URL('https://example.test/assets/hero.png');
if (requested.origin === target.origin &&
requested.pathname === target.pathname &&
request.resourceType() === 'image') {
request.respond({
status: 200,
contentType: 'image/png',
body: imageBytes
});
return;
}
request.resourceType() reports how the rendering engine classifies the request. It is useful as an additional check when a URL pattern might also match a stylesheet, script, or API call. URL matching alone is preferable when the asset’s address is uniquely stable.
Decode Base64 before sending it
Buffer.from(value, 'base64') converts the encoded text to the original binary bytes. Do not pass the Base64 characters as though they were already an image file. If your input is a data URL such as data:image/png;base64,iVBOR..., remove everything through the comma first:
const dataUrl = 'data:image/png;base64,iVBOR...';
const comma = dataUrl.indexOf(',');
if (comma === -1) throw new Error('Expected a data URL');
const imageBytes = Buffer.from(dataUrl.slice(comma + 1), 'base64');
Use the media type from the data URL, or otherwise determine it from the actual bytes, and send the matching contentType.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #2
Resolve all other requests
Once interception is enabled, requests pause until they are continued, fulfilled, aborted, or completed from browser cache. The unconditional request.continue() branch is therefore required. Omitting it can leave scripts, stylesheets, fonts, and the document itself stalled, causing navigation timeouts or a partially rendered page.
Handling multiple or asynchronous request handlers
A single synchronous listener is easiest to reason about. Applications often have several listeners, middleware, or asynchronous work that decides which response to send. Puppeteer exposes request.isInterceptResolutionHandled() so a handler can avoid resolving a request twice.
page.on('request', async request => {
if (request.isInterceptResolutionHandled()) return;
const shouldReplace = request.url() === targetImageUrl;
if (!shouldReplace) {
request.continue();
return;
}
// Any await creates time for another handler to resolve the request.
const bytes = await loadBytesSomehow();
// Check immediately before the resolution call.
if (request.isInterceptResolutionHandled()) return;
request.respond({
status: 200,
contentType: 'image/png',
body: bytes
});
});
The final check and the respond() (or continue()/abort()) call should remain together synchronously. Checking once at the top is not enough if an await occurs afterward.
If several listeners can act, establish one owner for each request or centralize interception in one listener. A double resolution can produce an interception error and leave the page in an unexpected state.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Choosing a matching strategy
| Strategy | Use it when | Watch for |
|---|---|---|
| Exact URL | The image address is stable and unique | Query parameters, redirects, and cache-busting can change the string |
| Origin and pathname | Only tracking or version parameters vary | Different assets may share a pathname across environments |
URL plus resourceType() |
A broad URL pattern could match non-image resources | Classification is the browser’s perception, not a server-side MIME guarantee |
Whichever strategy you choose, return the bytes and MIME type that belong together. A PNG body labeled as JPEG can fail to decode or render inconsistently.
Data URLs versus intercepted HTTP requests
request.respond() is for an intercepted network request. Puppeteer documents mocking responses for data: URL requests as unsupported; calling respond() for one is a no-op. If the page already uses a data URL, replace the element’s source in page content instead of trying to intercept that data URL:
await page.evaluate((dataUrl) => {
const image = document.querySelector('#hero');
if (!image) throw new Error('Image element not found');
image.src = dataUrl;
}, 'data:image/png;base64,...');
This changes the DOM rather than mocking a network response. For an HTTP(S) image that the page requests normally, interception with respond() is the appropriate technique.
Navigation and capture timing
Install the interception listener before goto(), otherwise the target request may happen before your handler exists. Choose a navigation condition that matches the page: domcontentloaded is useful when you only need the initial document, while networkidle2 waits for a quieter network. A lazy-loaded image may not request its URL until it enters the viewport, so scroll or otherwise trigger the component before inspecting or capturing it.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteawait page.goto('https://example.test', { waitUntil: 'domcontentloaded' });
await page.locator('#hero').scrollIntoViewIfNeeded();
await page.waitForSelector('#hero');
await page.screenshot({ path: 'result.png', fullPage: true });
Do not use an arbitrarily long delay as a substitute for a condition you can observe. Wait for the target selector, a known application state, or the request itself when that is what determines readiness.
Troubleshooting
The page hangs or navigation times out
- Cause: an unmatched request was never resolved.
- Fix: keep the pass-through branch and call
request.continue()for every request you do not replace. Ensure an exception in your handler cannot skip both branches.
The replacement image is broken
- Cause: the Base64 text includes an unremoved data-URL prefix, is malformed, or the declared MIME type does not match the bytes.
- Fix: strip the prefix, decode with
Buffer.from(..., 'base64'), and setcontentTypeto the actual format.
The handler reports that the request was already handled
- Cause: another listener, or another branch of asynchronous code, already called a resolution method.
- Fix: check
isInterceptResolutionHandled()at entry and again immediately after everyawait, then keep the final resolution call next to that second check.
The URL comparison never matches
- Cause: the browser requested a redirected URL, a URL with a query parameter, or a different host.
- Fix: log
request.url(), compare parsed URL components, and addrequest.resourceType() === 'image'when appropriate.
Calling respond() has no effect
- Cause: interception is disabled, or the target is a
data:URL. - Fix: enable interception before navigation and target the original HTTP(S) request. Modify the DOM directly for data URLs.
The page shows the old image
- Cause: the image completed from browser cache, so no network request needed interception.
- Fix: use a cache-busting URL for your test asset or configure the page context so the request is made again. Do not assume an interceptor can replace a request that never reaches the network.
Operational considerations
Reliability
Keep replacement data in memory or load it before navigation when possible. If bytes must be fetched asynchronously, protect the post-await resolution with the handled-state check. Close the browser in a finally block in production code so failures do not leak Chromium processes.
Security
Only replace URLs you expect. Broad substring matches can accidentally mock scripts or third-party resources. Treat Base64 supplied by users as untrusted input, enforce a reasonable decoded size, and avoid logging the entire payload.
Performance
Base64 is a transport representation; decoding it once before the handler avoids repeated work for multiple navigations. A large inline value consumes memory in your Node process and in the browser response path. If the same bytes are reused, retain the decoded buffer rather than decoding for every request.
Best Value
Or skip the browser setup
If your goal is a clean screenshot rather than testing a specific mocked response, ScreenshotNeo provides a single screenshot API call. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; 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 page verdict and billing status in X-Page-Verdict and X-Billed headers.
Use the API documentation at https://screenshotneo.com/docs/ for all options, including full-page and element captures, device presets, custom CSS and JavaScript, waits, blocked resources, cookies, headers, geolocation, PDFs, signed links, asynchronous jobs, bulk capture, and caching.
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}`);
ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots each month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Can I return a Base64 string directly as the response body?
Decode it first. The intercepted response represents image bytes, so pass the decoded Buffer (or another byte array) and the matching MIME type.
Does interception replace images loaded from CSS?
Yes, when the browser makes an HTTP(S) image request for the CSS resource. Match the requested URL and resolve it as you would an image element request.
Why is my replacement applied more than once?
The page may request the same URL multiple times, or multiple frames may load it. Track requests by URL, frame, or another identifier if your test requires a single replacement event.
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.




