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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To load an unpacked Chrome extension in Pyppeteer, launch Chromium in headed mode, remove Pyppeteer’s default --disable-extensions flag, and pass the extension directory with --disable-extensions-except and --load-extension. Then inspect browser targets to find the extension’s background page or Manifest V3 service worker, obtain its extension ID, and navigate to a resource such as its popup.

This works only when the Chromium build and extension are compatible. Pyppeteer is unmaintained, and its project recommends considering Playwright Python instead; the instructions below are for cases where you need Pyppeteer specifically.

What you need before loading an extension

  • Pyppeteer installed in the Python environment that will run the script.
  • An unpacked extension directory containing the extension’s manifest and files. The command-line flags take a directory path, not a Chrome Web Store listing or a packaged extension archive.
  • A browser that supports extension loading. Pyppeteer works best with its bundled Chromium and does not guarantee compatibility with arbitrary Chrome versions.
  • A dedicated user-data directory. This keeps the automation profile separate from your everyday Chrome profile and avoids profile-lock conflicts.

Use an absolute extension path to avoid ambiguity about the script’s current working directory. The example below resolves both paths before starting Chromium.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Load an unpacked extension with Pyppeteer

Pyppeteer’s launch() accepts Chromium command-line flags through args. Its launcher also includes --disable-extensions by default, so passing the load flags alone may not be enough. Remove that specific default argument with ignoreDefaultArgs, then enable and load the extension.

import asyncio
from pathlib import Path
from pyppeteer import launch

EXTENSION_PATH = str(Path("./my-extension").resolve())
USER_DATA_DIR = str(Path("./.pyppeteer-profile").resolve())

async def main():
    browser = await launch(
        headless=False,
        userDataDir=USER_DATA_DIR,
        # Remove Pyppeteer's default extension-disabling flag.
        ignoreDefaultArgs=["--disable-extensions"],
        args=[
            f"--disable-extensions-except={EXTENSION_PATH}",
            f"--load-extension={EXTENSION_PATH}",
        ],
    )

    try:
        # Extension targets may appear after launch; inspect them for diagnosis.
        for target in browser.targets():
            print(target.type, target.url)

        page = await browser.newPage()
        await page.goto("https://example.com")

        # Replace EXTENSION_ID after discovering it from an extension target URL.
        # await page.goto("chrome-extension://EXTENSION_ID/popup.html")
    finally:
        await browser.close()

asyncio.get_event_loop().run_until_complete(main())

Save this as a Python file, change ./my-extension to the unpacked extension directory, and run it in the environment where Pyppeteer is installed. The first launch may create the dedicated profile directory. Keep headless=False while setting up and debugging so you can see whether Chromium starts and whether the extension is present.

Why both extension flags are used

  • --load-extension=/absolute/path tells Chromium which unpacked extension to load.
  • --disable-extensions-except=/absolute/path limits enabled extensions to the specified extension directory.
  • ignoreDefaultArgs=["--disable-extensions"] removes Pyppeteer’s default flag that would otherwise disable extensions.

If you need to load more than one extension, Chromium’s command-line behavior and Pyppeteer revision matter; verify the exact flags in the process actually launched rather than assuming a configuration works across versions.

Find the extension ID and open its popup

An extension popup is not necessarily an ordinary tab that appears when Chromium starts. First find the extension ID from an extension target, then navigate a page to the popup’s chrome-extension:// URL.

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

Check background pages and service workers

Manifest V2 extensions expose a background page where supported. Manifest V3 extensions use a service worker, which may start asynchronously and can later be suspended when idle. A target may therefore not be visible in the first immediate listing after launch. Poll or wait for targets instead of treating their absence at startup as proof that the extension failed to load.

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(
        headless=False,
        userDataDir="./.pyppeteer-profile",
        ignoreDefaultArgs=["--disable-extensions"],
        args=[
            "--disable-extensions-except=/absolute/path/to/my-extension",
            "--load-extension=/absolute/path/to/my-extension",
        ],
    )
    try:
        for attempt in range(10):
            for target in browser.targets():
                print(target.type, target.url)
                if target.url.startswith("chrome-extension://"):
                    print("Possible extension ID:", target.url.split("/")[2])
            await asyncio.sleep(1)
    finally:
        await browser.close()

asyncio.get_event_loop().run_until_complete(main())

Use an absolute path in the second example too. Its path is illustrative and must be replaced with the resolved directory on your machine. An extension target URL commonly looks like chrome-extension://<id>/...; the segment after the scheme is the ID. Once identified, open a page explicitly:

extension_id = "abcdefghijklmnopabcdefghijklmnop"
popup_url = f"chrome-extension://{extension_id}/popup.html"
page = await browser.newPage()
await page.goto(popup_url)

The popup filename is defined by the extension; it is not always popup.html. Check the extension’s manifest and use its declared popup path. If the popup relies on being opened through the toolbar, direct navigation can differ from a user click, so test the interaction your workflow actually needs.

Headless behavior and compatibility

Do not assume extension loading will work in every headless configuration or Chromium revision. Start with headless=False, the bundled Chromium, a valid unpacked directory, and a clean dedicated profile. This provides a useful baseline before investigating headless-specific behavior.

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

Pyppeteer’s project repository describes the project as unmaintained and points users toward playwright-python as an alternative. The repository statement was accessed September 29, 2026. Pyppeteer can still be appropriate for an existing codebase, but maintenance status and browser compatibility are meaningful constraints for new automation.

Playwright Python’s extension documentation offers a current cross-check for Chromium extension concepts: persistent contexts, the same load flags, service-worker discovery, and chrome-extension:// navigation. These concepts map to Pyppeteer’s lower-level launcher and target APIs; Pyppeteer does not provide the same high-level persistent-context helper. See Playwright’s Chrome extensions guide when evaluating a migration.

Common problems and fixes

Symptom Likely cause What to check or change
Extension does not appear Pyppeteer’s default --disable-extensions remains active, the directory is wrong, or Chromium rejects the extension. Remove only that default argument with ignoreDefaultArgs=["--disable-extensions"]; verify the absolute path points to an unpacked directory with a manifest; inspect the launched command line and browser output.
No extension target immediately after launch The background page or service worker has not started yet; Manifest V3 workers are asynchronous and can be suspended. Wait and inspect browser.targets() repeatedly. Look for a background-page target for Manifest V2 or a service-worker target for Manifest V3.
Popup URL fails to load The ID is incorrect, the popup filename differs, or the extension did not load. Read the ID from a chrome-extension:// target URL and check the extension manifest for the actual popup resource path.
Launch fails with a profile or lock error The same user-data directory is in use by another browser process, or the profile is shared with regular Chrome. Close the process using that profile or create a separate user-data directory for this run. Do not automate against an active personal profile.
Works in bundled Chromium but not installed Chrome Pyppeteer does not guarantee support for arbitrary Chrome versions, and flag handling can vary by revision. Use the bundled Chromium as the compatibility baseline. If a specific installed browser is required, pin and test its version and inspect its actual launch arguments.
Removing one default argument does not solve launch behavior Pyppeteer or Chromium revisions may handle defaults and extension flags differently. Inspect the launched command line and make a narrowly scoped override. Avoid ignoreDefaultArgs=True unless necessary: Pyppeteer documents discarding all default arguments as dangerous.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Reliability, debugging, and cost considerations

Make runs reproducible

  • Pin the Python environment and browser version used by the job; changes in either can alter compatibility.
  • Use a dedicated profile per automation workflow, and avoid concurrent launches that share the same profile directory.
  • Record target types and URLs during debugging. This distinguishes a missing extension from a worker that has not started yet.
  • Check the extension manifest, its requested permissions, and the target page’s behavior. A successfully loaded extension does not guarantee it can modify every page or run in every context.

Account for browser work, not just script time

Each run launches and controls a browser process, so page loading, extension startup, and any waits you add affect completion time. Waiting for a fixed delay can be simple but may waste time or still be too short; waiting for the relevant target or page condition is more robust when you can identify it. A Manifest V3 service worker’s lifecycle also means that a one-time target check is not a durable readiness test.

Pyppeteer and Chromium are open-source software; the relevant direct costs are typically the compute, memory, storage, and maintenance needed to run the browser workload. The supplied project documentation does not establish a general runtime cost or performance figure, so measure your own pages, extension, and deployment environment rather than relying on a universal estimate.

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

Or skip the browser setup

If the actual goal is a clean screenshot or PDF of a website—not exercising a Chrome extension inside Chromium—you can use ScreenshotNeo, a screenshot API and MCP server from Yorker Media. It does not load your extension or replace extension testing; it handles website capture by API or through an MCP client.

For example, this cURL request captures a page as WebP:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

See the ScreenshotNeo API documentation for request options. Cookie banners are accepted and removed before the shot, along with known newsletter popups and chat widgets; those steps can each be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

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

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.