ScreenshotNeo

BlogHow-to

How to Convert a Puppeteer Response to a Fetch Response

Convert Puppeteer's HTTPResponse to a Fetch API Response with asFetchResponse(), or build one manually when your installed version lacks the helper.

By the ScreenshotNeo team4 October 20268 min read

Direct answer: If the Puppeteer version installed in your project provides HTTPResponse.asFetchResponse(), use await puppeteerResponse.asFetchResponse(). Puppeteer’s Next API documents this helper, but the versioned 25.12.0 HTTPResponse reference does not list it, so check your installed API. If it is unavailable, construct a Fetch Response from Puppeteer’s body, status, status text, and headers.

Use Puppeteer’s conversion helper

In a runtime with the Fetch API’s global Response and a Puppeteer version exposing the helper, conversion is one call:

const puppeteerResponse = await page.goto('https://example.com');

if (!puppeteerResponse) {
  throw new Error('Navigation did not produce a main-resource response');
}

const fetchResponse = await puppeteerResponse.asFetchResponse();
console.log(fetchResponse.status, fetchResponse.headers.get('content-type'));
const body = await fetchResponse.arrayBuffer();

page.goto() can return null in cases such as navigation to about:blank or a same-page hash change. Handle that possibility before calling a method on the response.

The helper returns a Promise of a Fetch API Response. Its documented behavior includes copying response headers and parsing newline-separated Set-Cookie values into separate entries. It omits the body for status codes 101, 204, 205, and 304. Puppeteer Next HTTPResponse reference

Check whether your installed Puppeteer supports it

The Next API documents asFetchResponse(), while Puppeteer’s versioned 25.12.0 reference does not list it. The documentation reviewed here does not establish the first stable release that includes the helper, so do not infer a minimum version from the Next page.

  1. Check the installed package version with npm ls puppeteer or inspect the lockfile. If you use puppeteer-core, check that package instead.
  2. Check the API documentation for that release and its HTTPResponse type declarations.
  3. Use the helper if the installed type exposes it. Otherwise, use the manual fallback below.

Do not instantiate or subclass Puppeteer’s HTTPResponse class. Puppeteer marks its constructor as internal and advises third-party code not to call it or create subclasses. Versioned HTTPResponse reference

Fallback: construct a Fetch Response manually

Puppeteer’s versioned API exposes content(), headers(), status(), and statusText(). These values can be passed to the Fetch Response constructor:

const puppeteerResponse = await page.goto('https://example.com');

if (!puppeteerResponse) {
  throw new Error('Navigation did not produce a main-resource response');
}

const fetchResponse = new Response(await puppeteerResponse.content(), {
  status: puppeteerResponse.status(),
  statusText: puppeteerResponse.statusText(),
  headers: puppeteerResponse.headers(),
});

console.log(fetchResponse.status);
console.log(await fetchResponse.text());

content() resolves to a Uint8Array. The manual mapping follows from the documented Puppeteer fields, but it is not documented as behaviorally identical to asFetchResponse(). Check unusual status codes, duplicate headers, and cookies in the runtime where you need to use the result. Puppeteer content() reference

Important differences and edge cases

  • Null-body statuses: The documented helper omits bodies for 101, 204, 205, and 304. A direct manual constructor call with content may not reproduce that handling and may reject combinations that Fetch disallows. If these statuses are possible, branch explicitly and verify the resulting response.
  • Set-Cookie: Puppeteer represents multiple Set-Cookie values as newline-separated values in its header map. The helper documents parsing them into separate entries. A plain object passed to new Response() may not preserve the special cookie semantics you expect. Avoid splitting on commas because cookie attributes can contain commas.
  • Header names and duplicates: Puppeteer’s versioned API reports lowercase header names and combines duplicate values into comma-separated values except for Set-Cookie. Combining values is not valid for every header’s semantics; inspect headers individually if duplicates matter.
  • Body fidelity: Puppeteer warns that response content may be re-encoded based on headers or browser heuristics. Neither conversion route guarantees byte-for-byte access to the original network payload. Puppeteer content() reference
  • Fetch availability: Response is a Web API. Ensure the Node.js version or other runtime you use provides it, or use a compatible Fetch implementation. This is a runtime requirement, separate from Puppeteer’s response API.
  • One-shot body consumption: Like other Fetch responses, a response body can normally be consumed once. Read it as text, JSON, or bytes according to your use case; clone the Fetch response before consuming it if you need two readers.

Complete Node.js example

This example navigates to a page, uses the helper when available, falls back to manual construction, and writes the response body to a file. It requires a Node.js runtime with global Response and an installed Puppeteer package.

import puppeteer from 'puppeteer';
import { writeFile } from 'node:fs/promises';

const browser = await puppeteer.launch();

try {
  const page = await browser.newPage();
  const puppeteerResponse = await page.goto('https://example.com', {
    waitUntil: 'domcontentloaded',
  });

  if (!puppeteerResponse) {
    throw new Error('Navigation did not produce a main-resource response');
  }

  let fetchResponse;
  if (typeof puppeteerResponse.asFetchResponse === 'function') {
    fetchResponse = await puppeteerResponse.asFetchResponse();
  } else {
    const status = puppeteerResponse.status();
    const bodyless = [101, 204, 205, 304].includes(status);
    fetchResponse = new Response(
      bodyless ? null : await puppeteerResponse.content(),
      {
        status,
        statusText: puppeteerResponse.statusText(),
        headers: puppeteerResponse.headers(),
      },
    );
  }

  console.log('Status:', fetchResponse.status);
  console.log('Content-Type:', fetchResponse.headers.get('content-type'));
  await writeFile('response-body.bin', new Uint8Array(await fetchResponse.arrayBuffer()));
} finally {
  await browser.close();
}

The bodyless fallback branch handles the documented null-body statuses for this example, but manual construction still may differ from Puppeteer’s helper for cookies or unusual headers. Check those cases if they are relevant to your application.

cURL and Python: where conversion does and does not apply

The conversion itself is a JavaScript operation on Puppeteer’s HTTPResponse object. cURL and Python do not receive that in-memory object, so neither can call asFetchResponse(). They can make their own HTTP request and read its status, headers, and body, but that is a separate response captured by their own client.

For example, this cURL command saves a response body and prints response headers and status:

curl -sS -D response-headers.txt -o response-body.bin -w 'HTTP %{http_code}\n' https://example.com

Python’s requests exposes its own response object, not a Fetch API response:

import requests

response = requests.get('https://example.com', timeout=30)
response.raise_for_status()
print(response.status_code, response.headers.get('content-type'))
with open('response-body.bin', 'wb') as output:
    output.write(response.content)

Use the Puppeteer conversion when you specifically need a Fetch Response from a browser response, for example to pass it to code that expects Web Fetch APIs. Use cURL or Python when you only need to make an HTTP request outside the browser.

Or skip the browser setup

If your goal is to get a website screenshot rather than convert a Puppeteer response, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF, without managing a browser in your application. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', new Uint8Array(await res.arrayBuffer())));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

Troubleshooting

Symptom Likely cause Fix
asFetchResponse is not a function or a TypeScript missing-property error The installed Puppeteer API or its type declarations do not expose the helper. Check documentation for the installed release and use the manual fallback if unavailable.
Cannot read properties of null page.goto() returned null, so there is no main-resource response object. Check for a null result before accessing response methods; consider whether the navigation was a same-document change.
Response is not defined The runtime does not provide the Fetch API global. Use a runtime with global Response or provide a compatible Fetch implementation.
Constructing the response throws for a status code Fetch restricts response status/body combinations, especially for null-body statuses. Use a null body for 101, 204, 205, or 304 as appropriate, and verify the status is accepted by the target runtime.
Cookies or duplicate headers differ The manual header map does not reproduce the helper’s documented special handling. Use asFetchResponse() where available; inspect Puppeteer’s header representation and handle Set-Cookie separately when using the fallback.
Saved bytes differ from what you expected Puppeteer may expose re-encoded content, and text decoding can also change byte interpretation. Use content() and keep the body as bytes where possible, but account for Puppeteer’s documented re-encoding caveat.
The response body is empty or unreadable on a second read The body was consumed already, or the HTTP status has no body. Consume it once, clone the Fetch response before separate reads, and check the status.

Performance, reliability, and cost

Conversion reads Puppeteer’s response content into memory before constructing a Fetch response. That is convenient for ordinary page responses, but large bodies require memory proportional to the content held by the process. Avoid converting large downloads just to inspect headers or status; those metadata are available directly through Puppeteer’s methods.

The conversion does not make the browser request more reliable or change its network behavior. Navigation timing, redirects, browser cache, and page lifecycle determine which response Puppeteer returns. Check for a missing response and inspect status before treating the body as successful application data. Puppeteer’s documentation also cautions that body re-encoding can affect byte fidelity.

There is no separate fee for converting a response in Puppeteer; the relevant operational costs are your browser runtime, memory, and network transfer. If the task is capturing a visual page, ScreenshotNeo offers a hosted screenshot API with a free allowance and published paid tiers, including 3,000 shots for $5 on Starter. Its available features include full-page and element captures, device presets, custom wait conditions, cookies and headers, caching, asynchronous jobs, and bulk capture.

FAQ

Does conversion preserve the original HTTP response exactly?

No byte-for-byte guarantee is documented. Puppeteer says content can be re-encoded based on response headers or browser heuristics.

Can I pass a Puppeteer HTTPResponse directly to code expecting Fetch Response?

Not as a Fetch Response object. Call asFetchResponse() when available or construct a new Response from its fields.

Should I construct Puppeteer’s HTTPResponse myself?

No. Its constructor is internal; use the response obtained from Puppeteer’s browser APIs.

Will the manual fallback handle every special case?

It maps documented body and metadata fields, but equivalence for cookie parsing, null-body statuses, and unusual headers should not be assumed. Validate the cases your application relies on.