Distribution
Trade-offs between zip handoff, Web Store upload, and self-hosted .crx with update_url, plus .pem key handling and how chrome’s external-.crx policy is worked around. Build-and-package already produced dist/ and packages/extension.zip; this section assumes those exist.
build-and-package is where automation stops. Channel choice, .pem key custody, hosting domain, and store-review correspondence all sit outside the tool. The .pem key and the billing or review accounts are assets that must move under your identity; if automation steps in on those, key leaks and policy incidents follow. From here on you drive.
Three paths at a glance
Section titled “Three paths at a glance”| Path | Recipient setup | Auto-update | Review | Best fit |
|---|---|---|---|---|
| Zip handoff | Unzip, then Load unpacked from chrome://extensions | None | None | One or two coworkers, internal PoC |
| Web Store upload | One click from the store | Yes (store handles it) | Yes, days to weeks | General release |
Self-hosted .crx + update_url | Install once, auto from then on | Yes (update update.xml) | None | Internal distribution, controlled user base |
The three paths are not exclusive. The same extension can ship through self-host internally and the Web Store for the general public at the same time. Migrating off one path onto another is also common.
Zip handoff
Section titled “Zip handoff”Send the packages/extension.zip that pnpm run package produced. The recipient unzips it, opens chrome://extensions, turns on Developer mode, and points “Load unpacked” at the folder. Updates are a fresh zip and the same steps over again. No automatic refresh.
Fine for a PoC. The recipient repeats the same dance every time, though. Once the user count climbs past five, move to self-host.
Web Store upload
Section titled “Web Store upload”What you need before uploading is short.
- A consistent icon set at 16, 32, 48, and 128, usually under
public/icons/. - Store listing screenshots at 1280×800 or 640×400.
- A short description and a long description. Justify each permission.
host_permissionsis rejection reason number one. - A private listing option. You can release to a small group of testers before going public.
Screenshot capture and store-listing copy are not the expert seat. They want you in front of the screen, step by step. The same topic, paced differently, sits in the Beginner manual’s distribution page.
Self-hosted .crx
Section titled “Self-hosted .crx”The most hands-on path, and the right one when you need control over who installs and when.
Building the .crx and the .pem key
Section titled “Building the .crx and the .pem key”The first time chrome packs your extension into a .crx, it drops a .pem key alongside it. That key is your extension’s identity. Only .crx files signed with the same key are recognized as the same extension. Lose the key and the same extension can no longer be updated; every user has to reinstall as a brand new extension.
The key never goes into git. Keep it in 1Password or a team secret store, and write the rotation policy into an ADR. docs/adr/ already has a page for the enterprise policy decision and the key rotation procedure.
update_url and update.xml
Section titled “update_url and update.xml”Add update_url to the manifest.
"update_url": "https://example.com/extension/update.xml"update.xml follows the format chrome expects.
<?xml version='1.0' encoding='UTF-8'?><gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> <app appid='YOUR_EXTENSION_ID'> <updatecheck codebase='https://example.com/extension/extension-1.2.0.crx' version='1.2.0' /> </app></gupdate>For each release, upload the new .crx to the same host under a fresh path and update version and codebase in update.xml. Chrome polls update_url about once a day and picks up the new version on its own. The host can be GitHub Pages, S3, or your own domain — anything that serves static files.
Chrome’s external-.crx policy
Section titled “Chrome’s external-.crx policy”By default, chrome blocks .crx files that did not come from the Web Store. For internal distribution there are two ways out.
- Register the extension ID and
update_urlin enterprise policy underExtensionInstallAllowlistorExtensionInstallForcelist. Windows uses the registry, macOS uses a plist, Linux uses JSON under/etc/opt/chrome/policies/managed/. - Have users install unpacked. Auto-update is off in this case.
ID registration and the host domain are expensive to change later, which is why an ADR captures that decision too. Which domain hosts update.xml and how the key rotation is scheduled both live there.
End of expert tier
Section titled “End of expert tier”That closes the chrome-ext-base expert tier. Intro named the three guardrails, architecture covered dependency direction and message routing, build-and-package showed how dist/ and the zip come out, and this section split the three paths to the user.
Step-by-step walkthroughs, screenshots, and OS-specific install branches for non-developer readers live in the Beginner manual, where the same topics are paced differently.