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

Most CORS errors in a Firebase function that uses Puppeteer happen before Puppeteer runs: the browser client is calling a Firebase endpoint with the wrong protocol, the function’s CORS policy does not allow the web app’s exact origin, or the function is an onRequest HTTP endpoint rather than an onCall callable. Use Firebase’s httpsCallable client for onCall, configure CORS on onRequest, and debug the browser-to-function request separately from Puppeteer’s work on the server.

First identify which request is failing

“CORS error” is a browser message about a cross-origin request. In this setup, there may be two different requests to investigate:

  • Browser to Firebase: your web app calls the Firebase function. The browser enforces CORS on this request, including its preflight when needed.
  • Function to a website: Puppeteer runs in the function runtime and opens a page. This is not the browser client calling your Firebase endpoint. Puppeteer cannot change the CORS headers Firebase returns to your web app or make a target website allow a browser-side request.

Start in the browser’s developer tools and inspect the request to the Firebase function. If an OPTIONS request fails, fix the function’s protocol or CORS configuration first. If the callable returns successfully but the page fails to load or a script on that page reports an error, investigate Puppeteer and the target site separately.

Choose the Firebase trigger that matches your client

Firebase’s callable and ordinary HTTP functions have different request protocols and CORS defaults. A callable is designed to be called using Firebase client SDKs. An HTTP function is a normal HTTP endpoint and needs an explicit CORS policy if a browser on another origin will call it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Question onCall onRequest
How the browser calls it Firebase client SDK, such as httpsCallable Ordinary HTTP, such as fetch
Default CORS policy in the Firebase v2 HTTPS API Allows requests from all origins by default No CORS policy by default
Request protocol Callable protocol, including its expected data envelope and token handling HTTP request and response that you define
Best fit A Firebase app calling a Firebase callable function A regular HTTP API, including one intended for clients that do not use Firebase SDKs

For an SDK-based Firebase web app, use onCall and httpsCallable. Use onRequest when you need a conventional HTTP API. Do not infer the trigger type from the URL: check the deployed function export and the code that defines it.

Call an onCall function with the Firebase SDK

A callable request is not just an arbitrary JSON request to a function URL. Firebase documents a callable protocol with a data field and token handling. A browser can preflight a callable request: application/json is not a CORS-safelisted content type, and Authorization is not a CORS-safelisted header. Using httpsCallable avoids many mismatches caused by hand-building that request.

Configure the callable and run Puppeteer on the server

This v2 example restricts calls to one web-app origin, checks that the caller is signed in, opens the page on the server, and closes Chromium even if navigation or page evaluation throws an error. Replace the example origin and target URL with your own values.

const { onCall, HttpsError } = require('firebase-functions/v2/https');
const puppeteer = require('puppeteer');

exports.scrape = onCall(
  { cors: ['https://app.example.com'] },
  async (request) => {
    if (!request.auth) {
      throw new HttpsError('unauthenticated', 'Sign-in required');
    }

    const browser = await puppeteer.launch({ headless: true });
    try {
      const page = await browser.newPage();
      await page.goto('https://example.com', { waitUntil: 'networkidle2' });
      return { title: await page.title() };
    } finally {
      await browser.close();
    }
  },
);

The cors option accepts a boolean, string, regular expression, or array. For a browser app in production, prefer an allowlist that names the origins you intend to support instead of allowing every origin. An origin includes its scheme and, when present, port: http://localhost:3000 and https://app.example.com are different origins.

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

Call it from your Firebase web app

With the Firebase app initialized, obtain a Functions client for the same region in which the function is deployed, then call the exported function by name. For example:

import { getFunctions, httpsCallable } from 'firebase/functions';

const functions = getFunctions(app, 'us-central1');
const scrape = httpsCallable(functions, 'scrape');

const result = await scrape();
console.log(result.data.title);

Replace app with your initialized Firebase app and us-central1 with the deployed function’s region. If the callable expects inputs, pass them to scrape and read them from the callable request’s data on the server. Keep the browser’s function name and region aligned with the deployment.

Configure CORS explicitly for an onRequest function

If you intentionally built a regular HTTP endpoint, configure its CORS policy on the HTTP trigger. In v2, an array can allow multiple exact origins; the API also supports regular expressions. The origin to allow is the origin of the browser app, not the function URL.

const { onRequest } = require('firebase-functions/v2/https');

exports.renderPageHttp = onRequest(
  { cors: ['https://app.example.com'] },
  async (req, res) => {
    // Validate input, run Puppeteer, and send a JSON response.
    res.json({ ok: true });
  },
);

Do not assume that an onRequest function has the same defaults as onCall: HTTP functions have no CORS policy by default, while callable functions allow all origins by default. Setting cors: true makes the endpoint available to requests from all origins. For an authenticated production browser client, use that broad policy only if it is deliberate. CORS is not a substitute for authentication or input validation.

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

What Puppeteer can—and cannot—fix

Puppeteer belongs in the function’s server-side work: launch a browser, create a page, navigate, extract information or capture output, then close the browser. It does not make the Firebase endpoint CORS-permissive. Nor can it add an Access-Control-Allow-Origin response header to a website it visits; that header must come from the server responding to the relevant browser request.

page.setExtraHTTPHeaders() adds headers to requests initiated by the page. Request interception can continue, respond to, or abort those requests. Neither changes the target server’s CORS response. Adding Access-Control-Allow-Origin as a request header is not a fix: it is a response header, and adding custom request headers can cause a preflight of its own.

A page navigation and a script running inside that page are also different cases. A navigation can succeed while JavaScript on the page is unable to read a cross-origin API response because that API does not allow the page’s origin. If Puppeteer is evaluating a page script that makes such an API request, investigate the target API’s response policy and the browser console; changing the CORS policy on your Firebase callable does not change the target API’s policy.

Debug a failed call in a reliable order

  1. Confirm the trigger. Check whether the deployed export is onCall or onRequest. Match the client to it.
  2. Match the client protocol. For onCall, use httpsCallable. Do not substitute an arbitrary JSON fetch unless you are intentionally implementing the callable protocol, including its data envelope and permitted headers.
  3. Inspect the preflight. In browser developer tools, find the OPTIONS request and inspect its status and the response headers Access-Control-Allow-Origin, Access-Control-Allow-Headers, and Access-Control-Allow-Methods. Use the response to distinguish a CORS policy problem from a later function or Puppeteer error.
  4. Compare the origin exactly. An allowlisted value must match the requesting web app’s origin, including scheme and port. For example, localhost during development is not the same origin as a deployed HTTPS Hosting domain.
  5. Remove unnecessary custom headers. Custom headers, an explicit Authorization header, and non-safelisted content types can trigger preflight. When using a callable, let the Firebase SDK manage callable authentication headers.
  6. Verify the function region and URL. Confirm that the Functions client uses the region where the function is deployed and that the client calls the intended function. A mismatch can look like a CORS failure.
  7. Read the actual status and response. A 401 or 403, a missing App Check token, or a callable HttpsError is not automatically a CORS defect. Check the actual response status and body rather than treating every browser console message as evidence that the CORS allowlist is wrong.
  8. Isolate the browser work. Temporarily make the callable return a constant value. Once that works, launch Puppeteer; next add page.goto; then add extraction or interaction. This separates Firebase transport failures from browser-launch, navigation, or page-work failures.
  9. Close Chromium on every path. Put browser cleanup in a finally block. That ensures an exception does not skip browser.close() and leave browser processes behind across invocations.

Common symptoms and fixes

Symptom Likely cause What to do
The browser reports that no Access-Control-Allow-Origin header is present. An onRequest function has no CORS policy, or the requested origin is not allowed. Configure the v2 cors option on the HTTP trigger and allow the exact web-app origin.
A callable preflight fails even though the endpoint uses onCall. The client may be making a generic HTTP request, using extra headers, or calling the wrong function or region. Use httpsCallable, remove unnecessary custom headers, and verify the region, deployed export, and preflight response.
Local development works differently from the deployed app. The development and deployed origins differ by scheme, host, or port. Allow the intended development origin if needed, and separately allow the deployed web-app origin. Do not treat the function URL as the requesting origin.
The function returns an authentication or App Check error. The request reached a security check and was rejected; this is not necessarily a CORS policy failure. Check the response status and body, then resolve the relevant sign-in or App Check requirement.
The callable works until Puppeteer is added. The Firebase request may be fine; browser launch, navigation, or page work may be failing. Return a constant first, then add launch and navigation in sequence. Inspect the function error and ensure the browser closes in finally.
A target page opens, but its script cannot read a cross-origin API. The target API’s response does not permit that page origin; Firebase’s CORS settings do not govern the target API. Inspect the page console and target response. Do not try to solve it by adding Access-Control-Allow-Origin as a request header.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Browser compatibility and reliability details

Puppeteer’s documented launch options include headless, executablePath, args, and a startup timeout. Puppeteer is guaranteed to work with its bundled browser, so setting executablePath to a different Chromium build is a compatibility decision, not a CORS fix. If launch fails, check the function’s browser setup and executable compatibility before changing CORS settings.

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

Navigation waits also affect function execution. The example uses waitUntil: 'networkidle2'; if the page keeps network connections open, that wait may not finish as expected. Choose a wait condition that matches the page and the data you need, and handle navigation errors separately from callable transport errors. Regardless of the wait condition, retain cleanup in finally.

Or skip the browser setup

If your goal is simply to get a screenshot of a public page—not to run custom Puppeteer logic inside your Firebase function—you can use ScreenshotNeo instead. It does not repair a misconfigured Firebase endpoint or replace your application’s custom function; it provides a screenshot API and MCP server so you can avoid managing the screenshot browser yourself.

One GET request returns an image or PDF. The example saves a WebP screenshot; see the ScreenshotNeo API documentation for request options and setup.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
  • Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in X-Page-Verdict and X-Billed headers.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents, including Claude, Cursor, and other MCP clients.
  • The Free plan includes 1,000 screenshots per month with no card required. Paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Frequently Asked Questions

Does enabling CORS on a callable require the caller to be signed in?

No. CORS controls which browser origins may make a cross-origin request; it does not itself authenticate a user. Enforce sign-in or other access requirements in the function.

Will changing the callable’s CORS setting let Puppeteer access every API used by a page?

No. The callable policy governs the browser client’s request to Firebase. A target API’s own response policy is separate.

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.