extension_create
Create an extension
Create a browser extension project from a template in the extension.dev catalog; call extension_templates first to see what is available. The scaffolder may initialize a git repository in the new project (with a first commit), and it also writes store metadata and a .gitignore of its own; the result's defaultsApplied block reads those decisions back. The result's `scripts` lists every package.json script that runs the engine, with the browser it targets and the folder it writes: the scaffolder passes no --browser to `dev`, `start` and `build`, so `npm run build` writes dist/chromium while `build:chrome` writes dist/chrome; read the folder from that list instead of assuming one. A scaffold that answers `created` is verified (its manifest was found and read): it needs no build to prove it, so do not run `npm run build` to check it; the next step is extension_dev with the answered projectPath. Not for a project that exists: extension_dev runs it, extension_project_create puts it on extension.dev. After a successful scaffold this tool sends one telemetry event, draft_seeded (template slug, source and commit, a random install id, never a path or a name), to PostHog; the Telemetry section of this package's readme names the two environment variables that turn it off.
ParametersinstalltemplateparentDirprojectNamerequired
extension_templates
List the templates
Browse the extension.dev template catalog. Pass action:'list' (the default) to search and filter it and get metadata per template. Pass action:'source' with a `slug` to read one template's files, for learning a pattern before building something similar. Read `framework` as the UI framework only, never the language: TypeScript and JavaScript templates live under slugs ('typescript', 'content-typescript'), shadcn is a React variant ('sidebar-shadcn'), and provider AIs carry the 'ai' tag ('ai-chatgpt', 'ai-claude'). Reach those through query, tags or slug.
Parametersslugtagsfilesqueryactionsurfacefeaturedframework
extension_build
Build the extension locally
Build a browser extension for production. This tool always passes --browser (chrome unless you set it), so the output lands in dist/<browser>/ (dist/chrome by default) and the answer's outputPath always names the folder that was written (the engine's reported path, else dist/<browser> under the package root, the same folder this tool checked for the manifest), with zipPath beside it when a zip was asked for: read those paths instead of assuming one. To know what is in the build (sizes, entry points, the checks a store reviewer runs) call extension_analyze on the same projectPath and browser instead of listing or reading the output folder by hand; the answer's hint says so. A scaffolded project's own `npm run build` runs `extension build` with no --browser, which the engine defaults to chromium, so that script writes dist/chromium/ instead. Pass zip:true to also package a .zip for store submission. With browser:'safari' the build converts the extension into a macOS app through Xcode, and bundleId sets the identifier it ships under. The build refuses a manifest with build-blocking errors unless you pass skipValidation:true, because such a manifest yields a broken bundle the bundler itself never flags.
ParameterszipmodesilentappNamebrowserbundleIdpolyfillmacOsOnlyzipSourceprojectPathrequiredzipFilenameskipValidation+2 more
extension_dev
Run the dev session
Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded. Reach for this first when the ask is "run my extension": by default it waits for the session to be ready and answers with the readiness and the first log lines, so one call covers the ask; pass wait:false to return as soon as the server is spawned. A dev session carries the control channel that extension_storage, extension_reload, extension_open and extension_dom_snapshot need (local, same machine, same user) unless you pass allowControl:false; the eval channel that extension_eval needs is the one gate that stays off until you pass allowEval:true (which implies control, so you never need to pass both). Use extension_start instead to run the production build in a browser. When the answer's ready.status is "ready" the session is up and nothing else needs calling before extension_logs, extension_inspect or the control verbs; extension_wait is only for wait:false, an answer whose ready.status was not ready, or a build-only session.
ParametershostportwaitappNamebrowserprofilereplacebundleIdheadlesspolyfillallowEvalmacOsOnly+11 more
extension_start
Start a browser
Run the PRODUCTION build in a browser: build, serve and launch. No hot module replacement, so edits are not picked up; use extension_dev while writing code, and this to check what ships. By default there is no control channel, so extension_eval, extension_storage, extension_reload, extension_open and extension_dom_snapshot cannot attach; pass allowControl:true (or allowEval:true, which implies it) to drive the run with them. Both flags need Extension.js 4.1.34 or later; an older engine is refused as engine-unsupported. build:false launches an existing dist/<browser>; outputPath launches any prebuilt unpacked extension directory. By default it waits for the build and the browser like extension_dev (up to 45 s) and answers build-ready with the session, the extension id and cdpPort, so no extension_wait is needed before extension_open or extension_assert; wait:false answers once the engine process is alive 5 s after spawn, and a wait that runs out answers started naming the extension_wait call. It says what a production session cannot do (no hot reload, no console stream for extension_logs, and the bridge verbs need allowControl); whether the browser loaded the extension is read over its debug port on Extension.js 4.1.34 or later, and only extension_dev proves it on an older engine.
ParametershostportwaitbuildbrowserprofilereplaceheadlesspolyfillallowEvalnoBrowserextensions+7 more
extension_try
Try a published extension
Run a published extension.dev build for the person in a fresh browser on this machine, so they see it work without downloading a package, turning on developer mode or running an installer. It downloads the build's package (newest by default), checks it against the platform's integrity record when there is one, unpacks it into a temporary folder and launches it with a new profile; on Extension.js 4.1.34 or later it then opens the popup or replays the toolbar action and saves a screenshot, and on an older engine it only launches and says the extension was not used. The answer names the version, the package size, what was confirmed and what was not, the screenshot, and what the extension can reach in plain words: warn the person about that. Nothing stays installed: call it again with action:'close' when done. For a project on disk use extension_dev or extension_start.
Parametersurlactionbrowserprojectrequiredversionheadless
extension_stop
Stop the session
Stop a session that extension_dev or extension_start is running: terminate the server and the browser it launched, and remove the live-preview carrier if extension_dev placed one. The answer names the server pid and the browser pid the launcher recorded, each with whether it is gone (serverGone, browserGone), so a "close its browser" ask is settled by this answer with no process check by hand. This covers extension_start build:false too, which the registry records as a preview session. Call it when you are done verifying, so sessions do not accumulate.
ParametersallbrowserconfirmprojectPathincludeOtherServers
extension_manifest_validate
Validate the manifest
Validate a manifest.json across browsers. This reports missing fields, invalid permissions, dangling file references, and cross-browser compatibility issues. Read buildBlocking for the errors that make extension_build refuse.
ParametersbrowserbrowsersprojectPathmanifestPath
extension_theme_verify
Verify a theme
Verify a Chrome theme manifest before it ships. This settles the four-leg WYSIWYG contract (app-shows == manifest-says == chrome-paints, plus chrome-accepts) as far as is possible headless: it derives every color current Chrome would paint from the manifest through the transcribed Chromium resolver, and classifies each problem as D1 fabrication, D3 parity gap, or D4 acceptance gap (keys Chrome silently discards: dead legacy, incognito, unknown, out-of-range). It verifies only, and never authors or mutates a theme. The app-rendered and real-pixel legs need a browser, so they come back as needsAttended with the step that settles each one (the theme editor at themes.extension.dev, and a dev session in Chrome), never as passed.
ParametersmanifestmanifestPath
extension_analyze
Analyze the extension
Analyze a BUILT extension on disk: sizes, entry points, permissions, bundle composition, store-readiness checks, and manifestByBrowser, the emitted manifest per built browser with the resolved keys (background, action, sidebar, permissions, host_permissions, chrome_url_overrides, browser_specific_settings) that differ; other dist folders are named, not read as browsers. Static: it reads dist/<browser> only. A dist extension_dev rewrote is a development build: buildType says so, store checks are skipped, extension_build makes the build to analyze. extension_inspect reads a running extension.
ParametersformatbrowserprojectPathrequired
extension_assert
Assert on a page
Run a test stage on an extension_dev or extension_start session: one verdict per expectation (see expect): pass, fail, not ready (a precondition; see settledBy) or inconclusive (a platform limit). page-world-clean, no-outside-requests and background-worker-booted read the debug port: meant for the production build (extension_start, allowControl:true). storage-key-present needs the control channel; content-script-injected and console-errors-empty read the dev log stream: extension_dev only.
ParametersexpectrequiredbrowsertimeoutprojectPathrequired
extension_inspect
Inspect a live page
Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of the extension-root hosts inlined; other shadow roots are not crossed by html, dom_snapshot or probe), DOM structure, content-script injection, console messages, and CSS selector queries through `probe`. This is the ONLY tool that pierces closed shadow roots (deepDom), runs selector probes, and navigates a tab to `url` before reading it. It reads a web or override page: the first inspectable target, the first whose url contains `url`, or the exact page `targetId` names (the target.targetId extension_open answers, or one from extension_dom_snapshot listTargets) without navigating. Not for an extension surface by name (popup, options, sidebar, devtools) or a chrome.tabs id, and not for enumerating what is open: that is extension_dom_snapshot. Not for a built extension's files on disk: that is extension_analyze. Chromium rides the Chrome DevTools Protocol and needs the session's debug port, not allowControl; on Firefox the reads ride the agent bridge and need allowEval:true (console rides the RDP watcher replay on engine 4.0.15 and later). This requires an active dev or start session.
ParametersurlprobebrowserdeepDomincludemaxBytestargetIdprojectPathrequired
extension_list_extensions
List the loaded extensions
List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts. This session's own extension carries ownExtension:true, with name and version from the ready contract even when the browser exposes no identity. On Chromium, entries come from the live CDP targets and from the session profile's installed set, so an extension whose MV3 service worker is dormant is still listed, with running:false and no contexts. Firefox rides the RDP root actor (listAddons, engine 4.0.15 and later), so entries are installed add-ons regardless of contexts, are marked temporarilyInstalled where relevant, and carry no contexts. Other extensions' contexts are never attached to or evaluated in. This requires an active dev or start session.
ParametersbrowserprojectPathrequired
extension_logs
Read the console
Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline. This reads the same agent-bridge plane as the `extension logs` CLI: a one-shot returns the most recent matching lines from logs.ndjson, and follow:true connects to the live control channel, receives the broker's replay of its recent ring (up to 5,000 events, counted in value.replayed) and then the frames that arrive during followMs (value.live). This requires an active extension_dev session.
ParameterstaburllevellimitsincefollowbrowsercontextfollowMsprojectPathrequiredsignalsOnly
extension_eval
Evaluate code in a context
Evaluate an expression in a running extension context: the background, an extension surface (popup, options, sidebar, devtools, newtab, history, bookmarks), a content script or a page, picked by context, url, tab or targetId. Start the session with allowEval:true (extension_dev), which writes a 0600 session token; without that token every route of this tool, the debug port included, answers eval-disabled. A returned promise is awaited; a value that cannot be cloned (a DOM node, a function) comes back as its string form. To wait for a condition that is still settling (a model loading, an element appearing), pass until: {} (or {timeoutMs, intervalMs}) and the eval repeats until its value is truthy, answering that value, or the last one on timeout. Debug-port evaluates run with a user gesture, so gesture-gated APIs (permissions.request, sidePanel.open) can succeed here and still fail when the extension's own code calls them. Naming a tab the browser refuses to script (page-not-scriptable, extension-page-unreachable) needs Extension.js 4.1.34 or later; on an older engine such a refusal still answers as a missing target. On Safari this tool reaches only page contexts, through the automation window's WebDriver session: the background and the extension surfaces are out of reach (extension_logs reads the background). Not for reading a page's structure: that is extension_dom_snapshot, which also lists tabs and targets. Pass `targetId` (the target.targetId extension_open answers, or one from extension_dom_snapshot listTargets:true) to evaluate in that exact page over the debug port; context, url and tab are then ignored.
ParameterstaburluntilbrowsercontexttimeouttargetIdexpressionrequiredprojectPathrequired
extension_storage
Read extension storage
Read or write chrome.storage in a running extension. Every call runs in the extension's background (the engine honors no context), so it proves nothing about what a content script or page can read. A set is read back and the answer says whether the stored value and its type match. Rides the dev session's control channel, on unless extension_dev was called with allowControl:false. One key per call.
ParameterskeyareavalueactionrequiredbrowsertimeoutvalueJsonprojectPathrequired
extension_reload
Reload the extension
Reload a running extension's background context, or a tab. Rides the dev session's control channel, on unless extension_dev was called with allowControl:false. A background reload answers once the engine's ready.json shows the new background attached to the dev server again (value.reattached, value.reattachedMs), so the next read or assertion meets the new generation and not the gap between them; if it has not come back within the budget (timeout, at most 5 seconds) the answer is status reloading with the contract's own stamps.
ParameterstabbrowsercontexttimeoutprojectPathrequired
extension_open
Open a surface
Open an extension surface (popup, options, sidebar, the newtab, history or bookmarks override page, or the extension's DevTools panel), navigate a tab to a url, or replay an event (the toolbar action, a chrome.commands shortcut) in a running session, over the dev session's control channel (on unless extension_dev was called with allowControl:false). Opening a url on Chromium answers target.targetId, the CDP target id of the tab it opened: pass it as `targetId` to extension_screenshot, extension_dom_snapshot, extension_inspect or extension_eval to address that exact tab (extension_dom_snapshot listTargets: true lists every targetId, on Firefox too). A url answer also carries tab {active, visibilityState}, viewport {width, height} and headless: an element below the viewport is not observed until it is scrolled into view. A url opens in a background tab unless foreground: true. Not for evaluating in the page once it is open: that is extension_eval. An action or command replay invokes your listener without a user gesture, so the gesture-derived activeTab grant does not apply.
ParameterstaburlnameasTabpanelreloadwaitMsbrowsersurfacetimeoutforegroundprojectPathrequired
extension_dom_snapshot
Snapshot the page
Take a shallow structured DOM snapshot of one chosen surface: element counts, extension roots, open shadow roots, optional byte-capped HTML, and optional recent console lines. This is the SURFACE PICKER: the only tool that reads an open extension surface by name (`context`: popup, options, sidebar, devtools) or an override page, the only one that takes a numeric chrome.tabs id, and the only one that enumerates what is open (listTargets for targetIds, listTabs for numeric tab ids). Pass `targetId` (the target.targetId extension_open answers, or one from listTargets) to read that exact page over the debug port. Not for closed shadow roots, selector probes, navigation or a deep read of an open web page: that is extension_inspect. Not for the pixels: that is extension_screenshot. The snapshot rides the agent bridge (localhost only, on unless extension_dev was called with allowControl:false) and needs no CDP; listTargets and `tabUrl` resolution ask the browser directly and need the session's debug port.
ParameterstaburltabUrlbrowsercontextincludetimeoutlistTabsmaxBytestargetIdlistTargetsprojectPathrequired+1 more
extension_screenshot
Take a screenshot
Take a screenshot of one open extension surface (popup, options, sidebar, newtab, history, bookmarks) or web page in a live extension_dev or extension_start session as a .png file. It asks the browser for the page's own pixels over the debug port (CDP on Chromium, the screenshot actor on Firefox, WebDriver on Safari, web pages only); it never captures the screen and never brings a window forward: on Chromium a page behind other windows or in a hidden tab is shown briefly for the capture (value.behindWindow, value.hiddenTab). Open the surface with extension_open first. `targetId` captures that exact page; a url matching several tabs of one page captures the most recently active one and warns. width and height set the Chromium viewport, resize the Safari window and crop from the top left on Firefox; deviceScaleFactor sets the pixel ratio. `store` captures a store listing screenshot at that store's size into store/screenshots/<store>/, where extension_submit sync-listing picks it up; `browserCapture` files a capture of the extension running in this browser under captures/<browser>/, where extension_publish sync-captures picks it up; one call per view. The file goes under dist/extension-js/<browser>/screenshots unless `path` names one; the answer gives path and pixel size.
ParametersurlpathstorewidthheightbrowsercontexttimeouttargetIdprojectPathrequiredbrowserCapturedeviceScaleFactor
extension_wait
Wait for the session
Wait for a running dev or start session to be ready, or for a rebuild after a source edit (for: "rebuild"). Call it for a session started with wait:false, after an extension_dev answer whose ready.status was not "ready", and for a build-only (noBrowser) session, which answers as soon as the compile lands; an extension_dev answer with ready.status "ready" already carries this result, so there is nothing left to wait for. It polls the ready.json contract and reports compiled, browserAttached and guestLoaded (the browser's own target list shows your extension, the trustworthy load signal; null when it could not be checked), every answer carries budgetMs and elapsedMs, and on status 'timeout' call again to keep waiting on the same contract. Every ready answer carries the ports the session bound: port (the dev server), controlPort (the control channel) and cdpPort (Chromium) or rdpPort (Gecko), or debugPortNote when the session opened no debug port, so a question about ports is answered here without a shell. The reload stamp a rebuild wait reads (reloadedAt) needs Extension.js 4.1.34 or later; a project on an older engine gets a brief watch for the background to reconnect instead, answering reattached, still-detached or not-stamped. Not for a condition in the page (a model loading, an element appearing): that is extension_eval with until. Never sleep or tail the session log in a shell to wait. A browser that is running while the extension's background went idle answers status "background-asleep" with how to wake it, not "browser-gone"; a Safari WebDriver session that Safari ended answers "webdriver-session-ended" with the restart that opens a new one.
ParametersforsincebrowsertimeouttimeoutMsprojectPathrequired
extension_add_feature
Plan a feature
Plan the surface before adding an options page, a popup, a side panel, a content script, a devtools panel, a new tab page or a background script to an existing extension. It answers with the manifest additions, the files to create and the catalog template to copy from, and modifies nothing: write what the plan says, then validate the manifest and run extension_dev.
ParametersfeaturerequiredframeworkprojectPathrequired
extension_browsers
List the browsers
Find, install and remove the browsers Extension.js tooling can launch. Pass action:'detect' (the default) to scan both system-installed and managed browsers, and report each one's binary path, version, engine and debugger support. Pass action:'list' for the managed cache this tool downloads into, with each binary's version and size on disk. Both report the version the binary itself carries (its bundle metadata, else --version), prerelease suffix included (159.0a1), never the cache folder's name. Pass action:'install' to download a managed binary: several hundred MB in one blocking call, so allow a generous client timeout. Pass action:'uninstall' to remove managed binaries; it never touches a system install.
Parametersallactionbrowserbrowsers
extension_doctor
Diagnose the session
Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness. This returns one {check, status, detail, remediation?} per leg, in dependency order. Read a 'skip' as blocked, not as a pass: it names the check that blocked it. A session started with allowControl: false comes back ok:true with status 'read-only', not as an error. On Chromium, value.capabilities reads each extension context's WebGPU adapter, crossOriginIsolated and headless. Run this first when any act tool errors unexpectedly. Call it with no projectPath for a pre-flight environment check (node, the Extension.js CLI, the template cache).
ParametersbrowserprojectPath
extension_docs_search
Search the docs
Find the docs page to cite before answering any question about a WebExtension API, a manifest key, a permission, or a difference between Chrome, Firefox, Edge and Safari. It searches the Extension.js and extension.dev docs (free, no login) and answers with the pages, each with its URL and an excerpt. Memory is the fallback only when it returns nothing.
Parametersapilimitqueryrequired