ScreenshotNeo

BlogHow-to

How to Get a Web Worker’s URL in Puppeteer

Use Puppeteer’s `worker.url()` to read a WebWorker’s URL. Learn how to find current workers, track their lifecycle, and distinguish them from ServiceWorkers.

By the ScreenshotNeo team4 October 20266 min read

Call worker.url() on Puppeteer’s WebWorker object. To get the URLs of dedicated WebWorkers currently associated with a page, iterate over page.workers(). That method does not include ServiceWorkers.

1. Read a worker URL

The WebWorker.url() method returns a string containing the worker’s URL. Use it when you already have a worker object, such as the object passed to the workercreated event.

page.on('workercreated', worker => {
  console.log('Worker URL:', worker.url());
});

This listener reports workers created after it is registered. Register it before navigating if you need to observe workers created during page load.

2. List workers already associated with a page

Call page.workers() to get the page’s current dedicated WebWorkers, then call url() on each one:

for (const worker of page.workers()) {
  console.log(worker.url());
}

If no dedicated workers are associated with the page at that moment, the array is empty. A worker created later will not appear in an earlier snapshot; use the workercreated event to observe later creation.

3. Complete runnable example

Install Puppeteer in a Node.js project with npm install puppeteer. Save this as worker-urls.js and run it with node worker-urls.js. The example registers lifecycle listeners before navigation, then prints workers that exist after the page loads.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();

    page.on('workercreated', worker => {
      console.log('Worker created:', worker.url());
    });

    page.on('workerdestroyed', worker => {
      console.log('Worker destroyed:', worker.url());
    });

    await page.goto('https://example.com', {
      waitUntil: 'domcontentloaded',
    });

    console.log('Workers currently associated with the page:');
    for (const worker of page.workers()) {
      console.log(worker.url());
    }
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Replace the example URL with a page that creates a WebWorker if you want to see worker URLs. The sample site may not create one. The Puppeteer methods and events used here are documented in the WebWorker API reference, Page.workers() reference, and Page events reference.

4. Choose the right API for the worker type

What you need Use What it returns or observes
URL for a worker object you already have worker.url() The URL of that WebWorker as a string.
Dedicated WebWorkers associated with a page now page.workers(), then worker.url() The current dedicated workers for that page. ServiceWorkers are excluded.
Dedicated worker creation and teardown workercreated and workerdestroyed page events A worker object at creation or destruction time; call url() on it.
A Chrome extension Manifest V3 ServiceWorker Browser target APIs The extension guide’s example waits for a target whose type is service_worker, checks target.url(), then obtains the worker with target.worker().

Dedicated WebWorkers are not ServiceWorkers

page.workers() covers dedicated WebWorkers associated with the page. Puppeteer’s API reference explicitly excludes ServiceWorkers. For the Manifest V3 extension ServiceWorker example, Puppeteer’s extension guide uses a browser target instead:

const target = await browser.waitForTarget(target =>
  target.type() === 'service_worker' &&
  target.url().endsWith('background.js')
);

const serviceWorker = await target.worker();
if (serviceWorker) {
  console.log('ServiceWorker URL:', target.url());
}

The filename check is only appropriate when you know the extension’s worker filename. It is not a universal recipe for discovering every ServiceWorker. See Puppeteer’s Chrome extensions guide for the extension-specific pattern.

Do not use the page URL

page.url() is the page’s main-frame URL, equivalent to page.mainFrame().url(). It does not return a worker’s URL. Use worker.url() for a WebWorker URL.

URL retrieval is different from worker evaluation

worker.evaluate() executes a function in the worker context. It is useful for reading data available to worker code, but it is not needed to retrieve the worker URL. Call worker.url() directly.

5. Timing, lifecycle, and edge cases

  • Listener timing: attach workercreated before navigation or before the action that creates the worker. Events that happened before registration are not replayed.
  • Current versus future workers: page.workers() is a snapshot of workers associated with the page when called. Combine it with lifecycle events if you need both the current set and later changes.
  • Short-lived workers: a worker may be destroyed soon after creation. Log or store its URL in the creation handler if you need it after teardown.
  • Multiple pages: page.workers() is scoped to that page. To inspect another page, call it on that page’s object too.
  • ServiceWorkers: do not infer that an empty page.workers() array means no ServiceWorker exists. That API excludes ServiceWorkers.
  • Version differences: check the API reference for the Puppeteer version installed in your project. The official references reviewed for this guide span different published versions, and event documentation may also be on a next-version path.

6. Troubleshooting

Symptom Likely cause Fix
page.workers() returns an empty array The page has not created a dedicated WebWorker yet, or the worker is a ServiceWorker. Confirm the page creates a dedicated worker. Register workercreated before the relevant navigation or action. For an extension ServiceWorker, use the browser target approach in the extension guide.
The logged URL is the site URL The code called page.url() instead of the worker method. Call worker.url() on the worker object.
The creation handler never runs The listener was added after the worker had already been created, or the page did not create a worker. Register the listener before navigation or the triggering action, and verify the page actually starts a dedicated WebWorker.
target.worker() returns no worker The target may have stopped or may not expose an active worker when queried. Check the target type and URL, and handle a missing worker result. Follow the extension guide’s flow for the target and extension version in use.
Code examples do not match installed APIs The project’s Puppeteer version differs from the documentation version used by the example. Check the matching version of the official reference and adapt the code to the installed release.

7. Performance, reliability, and cost

Reading a URL from an existing worker is a direct API call; the important operational cost is usually the browser work around it, such as launching Chromium and loading the page. Reuse a browser and page where appropriate in a longer-running process, and close browser resources in a finally block so failures do not leave a process running.

Worker presence and lifetime depend on what the page does and when it does it. Register lifecycle listeners before the event can occur, and treat the current worker list as time-sensitive. Puppeteer’s cited API references do not publish performance benchmarks or a monetary cost for worker.url(); browser hosting and page loading costs depend on your own environment.

Or skip the browser setup

If your goal is to capture a page rather than inspect its worker internals, ScreenshotNeo provides a website screenshot API and MCP server. This is separate from Puppeteer’s worker inspection APIs.

One GET request returns a screenshot. See the ScreenshotNeo documentation for API 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
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.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(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Yearly billing gives two months free, and every feature is on every plan.

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

FAQ

What does worker.url() return?

It returns a string containing the WebWorker’s URL.

Can I get a worker URL from page.workers()?

Yes. Iterate over the returned dedicated WebWorker objects and call url() on each.

Does page.workers() include ServiceWorkers?

No. Puppeteer documents that ServiceWorkers are not included in that page method.

Should I call worker.evaluate() to read the URL?

No. Use the dedicated worker.url() method; evaluation is for running a function in the worker context.