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

To load an unpacked Chrome extension with Pyppeteer, launch Chromium in headed mode, pass the extension directory with --load-extension and --disable-extensions-except, and remove Pyppeteer’s default --disable-extensions flag. Use a dedicated user-data directory. Then inspect browser targets to find the extension’s background page or service worker and navigate to its popup by extension ID if needed.

This approach gives Python control of Chromium with the extension installed; it does not make an extension popup appear as an ordinary tab automatically. Pyppeteer is also unmaintained, so use its bundled Chromium as the safest compatibility baseline and consider Playwright Python for actively maintained extension automation.

Load an unpacked extension in Pyppeteer

Chrome extensions are loaded from an unpacked extension directory using Chromium command-line flags. Pyppeteer accepts those flags through launch(args=...), but its launcher defaults include --disable-extensions. Unless you remove or override that flag, Chromium may start with extensions disabled even when you pass the load flags.

The following example launches a browser with one unpacked extension, prints target information for diagnosing extension startup, and opens a regular web page. Replace ./my-extension with the directory containing the extension’s manifest.json.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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 flag that disables extensions.
        ignoreDefaultArgs=['--disable-extensions'],
        args=[
            f'--disable-extensions-except={EXTENSION_PATH}',
            f'--load-extension={EXTENSION_PATH}',
        ],
    )

    # Extension targets may appear after the browser starts.
    for target in browser.targets():
        print(target.type, target.url)

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

    # After finding the extension ID, open one of its pages if needed:
    # await page.goto(f'chrome-extension://{extension_id}/popup.html')

    await browser.close()

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

Install Pyppeteer in the Python environment you use to run the script with python -m pip install pyppeteer. The extension must be present locally as an unpacked directory; the example does not install an extension from the Chrome Web Store. The browser profile path is also local to the working directory in this example. Use an absolute, writable path appropriate to your environment if the script runs elsewhere.

Why both extension flags are present

  • --load-extension=... tells Chromium which unpacked extension to load.
  • --disable-extensions-except=... restricts enabled extensions to the specified directory, which is useful for an isolated automation run.
  • ignoreDefaultArgs=['--disable-extensions'] removes the conflicting default without discarding all of Pyppeteer’s launcher defaults.

The precise interaction between Pyppeteer and Chromium command-line arguments can vary by version. If the extension still does not load, inspect the actual launched command line and verify that the disabling flag is absent and that both extension flags point to the correct directory.

Find the extension ID and open its popup

Loading an extension and opening its popup are separate tasks. The extension ID is part of its internal URL, for example chrome-extension://<id>/popup.html. Do not assume the ID or popup filename: find the ID from an extension target, then use the resource path defined by that extension.

Manifest V2 background pages

Where Manifest V2 is supported by the Chromium version in use, an extension may expose a background-page target. Inspect browser.targets() for target URLs associated with the extension. The extension ID appears in the chrome-extension:// URL. A background page is not the popup; it is the extension’s background context.

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

Manifest V3 service workers

Manifest V3 extensions use a service worker rather than a persistent background page. The worker target may not exist immediately when launch() returns: extension startup is asynchronous, and a service worker can be suspended when idle. Poll or wait for the target instead of treating its absence in the first target listing as proof that loading failed.

Once you have the ID, navigate a page to the popup resource if the extension defines one:

extension_id = 'replace_with_id_found_in_a_target'
popup_url = f'chrome-extension://{extension_id}/popup.html'
await page.goto(popup_url)

A popup normally exists only while opened through the browser’s extension UI. Navigating directly to its URL is often more reliable for automation than expecting a popup tab to appear on startup, but it is not identical to clicking the toolbar icon. For testing behavior that depends on a user gesture or popup lifecycle, account for that difference in your test design.

Headless mode, browser compatibility, and maintenance

Start with headless=False while diagnosing extension loading. This makes startup and browser behavior easier to inspect. Do not assume a recipe that works in headed mode will behave identically in every headless configuration; extension support depends on the Chromium revision and launch mode. Confirm behavior against the exact browser version and extension you intend to automate.

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

Pyppeteer works best with its bundled Chromium and does not guarantee compatibility with arbitrary Chrome versions. Pin your Python environment and browser revision for reproducibility, and use a separate profile directory so extension state and browser data are isolated from your everyday profile.

The Pyppeteer project repository describes the project as unmaintained and points users toward playwright-python as an alternative. That matters for new automation work: Pyppeteer can still be useful for an existing codebase, but its lower-level launch and target APIs do not offer the same high-level persistent-context helper used in Playwright’s extension examples. The underlying Chromium concepts—an unpacked extension, the load flags, worker discovery, and chrome-extension:// navigation—remain relevant, but APIs and browser compatibility must be checked for the specific tool and version.

Common problems and fixes

Symptom Likely cause What to check or do
Extension does not appear to load Pyppeteer’s default --disable-extensions flag remains active, or the extension path is wrong. Use ignoreDefaultArgs=['--disable-extensions'], confirm both extension flags are in the launched command, and verify the resolved directory contains manifest.json.
No extension target appears immediately The background page or service worker has not started yet. Wait and inspect targets again. For Manifest V3, account for asynchronous worker startup and possible suspension.
Popup URL fails or shows the wrong page The ID or popup resource path is incorrect, or the extension has no popup at that path. Read the extension ID from a target URL and check the extension manifest for its actual popup resource.
Behavior differs from local Chrome Pyppeteer’s bundled Chromium and the installed Chrome version may differ, or profile state may affect behavior. Reproduce with a dedicated profile and the bundled Chromium first; pin the environment and browser version for repeatable runs.
Removing the disabling flag is not enough A Chromium or Pyppeteer revision may handle defaults or flags differently. Inspect the launched command line and use a narrowly scoped override. Avoid ignoreDefaultArgs=True unless you understand the consequences: it discards all defaults, and Pyppeteer documents that option as dangerous.
Automation works until the worker becomes idle A Manifest V3 service worker can be suspended rather than remaining a persistent page. Do not build logic around a permanently present worker target. Rediscover or wait for it when needed, and test the extension’s real lifecycle.

Practical reliability and cost considerations

  • Keep the profile isolated. A dedicated userDataDir prevents automation state from mixing with a personal browser profile. Ensure the directory is writable and do not run concurrent browser instances against the same profile.
  • Make startup observable. Log target types and URLs after launch, then wait for the specific target your workflow needs rather than relying on an immediate listing.
  • Pin the browser environment. Pyppeteer’s bundled Chromium is the safest starting point according to the project’s compatibility guidance. An arbitrary Chrome executable may work, but compatibility is not guaranteed.
  • Use headed mode for diagnosis. Once the extension reliably loads, evaluate any headless configuration you need separately; do not assume it has identical behavior.
  • Budget for maintenance. Because Pyppeteer is unmaintained, new projects should weigh the cost of keeping its browser integration working against adopting a maintained alternative.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a rendered website screenshot rather than testing an installed Chrome extension, ScreenshotNeo can return an image or PDF from one GET request. It does not load or operate a Chrome extension, so it is not a replacement for extension testing. For screenshot capture, it removes cookie/consent banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed; its MCP server lets AI agents take screenshots; and the free plan includes 1,000 screenshots a month with no card, with paid plans starting at $5 for 3,000.

For API details, see ScreenshotNeo documentation. Example using cURL:

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.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

Or use Python:

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)

Or Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

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

Frequently Asked Questions

Can I load an extension directly from the Chrome Web Store with this script?

No. This recipe expects a local unpacked extension directory containing its manifest; it does not automate Chrome Web Store installation.

Can I use the same extension ID for every installation?

Do not assume so. Discover the ID from the extension target URL in the browser session you launched, then construct the internal URL from that value.

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.

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