Hand it off to your users
This chapter is about taking the extension you built off your own machine and onto someone else’s. The shape ranges from showing it to one or two people, to shipping it with auto-update to a real user base. Decide up front how far you actually need to go, and only follow the section that matches.
One thing to mention before starting. The base only automates the build and the zip. Who you send the zip to, where you keep the key file for auto-update, whether to list on the Chrome Web Store — all of that is yours to decide and yours to execute. If a tool reached into those steps it would be touching your keys, your accounts, your payment details, which is not safe.
| Step | Who | How |
|---|---|---|
| Build and zip | Base, automated | pnpm run build && pnpm run package, or the /build-and-package skill |
| Send the zip | You | Email, Drive, Slack — whichever the recipient prefers |
Self-host .crx | You | Keep the .pem key, pack the .crx, register update_url |
| List on the Web Store | You | Upload the zip in the developer console and submit for review |
Pick the section below that matches your situation and follow only that one.
Three distribution paths
Section titled “Three distribution paths”| Path | When it fits | What you need | Auto-update |
|---|---|---|---|
| Hand off the zip directly | One or two users at a time | A single extension.zip | No. You re-send a new zip for every update. |
| List on the Chrome Web Store | Releasing to the general public | A developer account (one-time fee), passing review | Yes. The store handles updates. |
Self-host .crx with update_url | You have your own hosting and want to own auto-update yourself | A .pem key, a .crx package, an update.xml, and a hosting location | Yes, but Chrome blocks this on regular user machines by default, so extra steps follow. |
For your first time out, path 1 is almost always enough. Worry about paths 2 and 3 when the user count grows or you genuinely need auto-update.
When pnpm run package (or the build-and-package skill) finishes, a DISTRIBUTION.md file appears at the repo root. This chapter is the long-form companion to that file.
1) Handing off the zip directly
Section titled “1) Handing off the zip directly”The simplest option, and almost always the right starting point.
pnpm run buildpnpm run package # produces extension.zipThe instructions you send along with the zip look like this:
- Unzip the file.
- Open
chrome://extensionsin the address bar. - Toggle Developer mode in the top-right corner.
- Click Load unpacked and pick the unzipped folder.
A “Developer mode” warning banner appears once on the recipient’s browser. The extension survives Chrome restarts, and when you ship a new version, the recipient repeats the same four steps. If you do not need auto-update, this is a perfectly fine place to stop.
A couple of small things help if the recipient is not a developer. First, send the four steps as a short install note alongside the zip. A short screen recording or a single screenshot beats a long paragraph here. Second, the zip carries your name, icon, and description as they appear in manifest.config.ts. Confirm those reflect your extension and not the base before you send anything out.
2) Listing on the Chrome Web Store
Section titled “2) Listing on the Chrome Web Store”Two flavours: unlisted (only people with the link can install) or public (visible in search). Either way, the install experience is the most natural one for ordinary users — auto-update and trust badges come along for the ride.
- Sign up at the Chrome Web Store developer console with your own Google account. There is a one-time registration fee.
- Upload the
extension.zipproduced bypnpm run build && pnpm run package. - Fill in permissions, icons, screenshots, description, and a privacy policy.
- Submit for review. The more conservative your permissions, the faster review tends to go.
The base ships with a deliberately minimal permission set in manifest.config.ts, which makes review easier. If you have added <all_urls> to host_permissions, or pulled in tabs or scripting, document why each one is there.
Worth having on hand before upload:
- A consistent icon set (16, 32, 48, 128) — usually under
public/icons/. - Store listing screenshots at 1280×800 or 640×400, usually three to five.
- A one-line description and a longer description. Justify each permission you ask for.
host_permissionsis rejection reason number one. - A privacy policy page. Required if you use an external backend.
When you first enter the developer console, the “New item” button is the most prominent. Clicking it brings up a zip upload dialog. After the zip uploads, the left-hand menu reveals sections for item info, privacy policy, payments, and so on. If a red warning banner appears right after upload, manifest required fields are missing — fill in the named fields in manifest.config.ts, rebuild, and re-upload.
Two things to flag if you are selling the result. First, register under your own Google account and your own extension name — you cannot list it under the base’s identity. Second, the Chrome Web Store no longer takes payments for the extension itself, so if you are charging for your work, the typical pattern is to handle payment on your own site and let users paste a license key into the options page. The base’s options page is a natural place for that one extra field.
3) Self-hosted .crx with auto-update
Section titled “3) Self-hosted .crx with auto-update”This is where certificates show up. A .crx file is in fact a zip signed with your own private key (.pem). The signature determines the extension’s ID, and that ID is what auto-update tracks. Lose the key once and the auto-update path for everyone who installed that build is gone in the same moment. Take this in before going further.
This path is genuinely valuable in exactly one situation: you need auto-update, but you do not want the extension on the Web Store. Outside of that, paths 1 and 2 are almost always easier.
3-1) Generate and store the .pem key
Section titled “3-1) Generate and store the .pem key”The first time, packing through Chrome itself is the most reliable way.
- Open
chrome://extensionsand confirm Developer mode is on. - Click Pack extension.
- For the first build, point “Extension root directory” at your
dist/folder and leave the key field blank. Chrome createsdist.crxanddist.pemnext to each other. - Move the
.pemfile somewhere safe — a password manager like 1Password, or a locked folder only you can reach. Do not commit it. Add*.pemto.gitignoreup front.
The CLI equivalent is:
google-chrome --pack-extension=dist --pack-extension-key=key.pemFrom the second build onwards, you must pass the same .pem back in. If the key changes, the extension ID changes, and from the user’s perspective it becomes a different extension entirely.
3-2) Host the .crx and update.xml
Section titled “3-2) Host the .crx and update.xml”Put both files somewhere your users’ browsers can reach (your own server, S3, GitHub Releases, etc.). The update.xml looks like this:
<?xml version='1.0' encoding='UTF-8'?><gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> <app appid='<EXTENSION_ID>'> <updatecheck codebase='https://example.com/path/to/my-extension.crx' version='1.0.0' /> </app></gupdate><EXTENSION_ID> is the ID Chrome shows the first time you pack the extension. You can also read it off the extension card in chrome://extensions.
3-3) Add update_url to the manifest
Section titled “3-3) Add update_url to the manifest”Add update_url to manifest.config.ts and it will land in the built manifest.json. Chrome polls that URL on its own schedule and pulls in any new version.
{ "update_url": "https://example.com/path/to/update.xml"}For a new release, repeat this loop: bump version in manifest.config.ts, rebuild, repack with the same key, overwrite the .crx at the same URL, and bump version in update.xml to match.
3-4) Working around Chrome’s block on external .crx
Section titled “3-4) Working around Chrome’s block on external .crx”By default, Chrome blocks installation of externally hosted .crx files on a regular user’s machine. You have to step around that. In a corporate environment, a group policy like ExtensionInstallForcelist will install the extension automatically once you register the ID and update_url. For everyone else — regular consumers — self-hosted .crx is an awkward fit. If those are your primary users, listing on the Web Store (path 2) is almost always the right answer instead.
Key handling and what to do if things go wrong
Section titled “Key handling and what to do if things go wrong”The .pem key warning is one line, but losing it is close to unrecoverable, and the stakes are higher when you are selling.
- Do not commit it. Put
*.pemin.gitignorebefore you generate the key. - Keep a second copy in a password manager. If your laptop dies, the copy in the password manager is the only one left.
- If you do lose the key, you have to mint a new key, ship a new extension under that new ID, and ask existing users to install it once more. That costs you trust. Treat key storage as a first-class step from day one.
A few extra checks before you sell
Section titled “A few extra checks before you sell”This base is a commercial template you have licensed from goldtagworks. You are free to build and sell extensions on top of it under license terms of your own choosing — your end users do not need any relationship with goldtagworks; they only see your extension.
Two restrictions on your use of the template itself:
- Do not redistribute the
chrome-ext-basesource code or this manual to anyone. Build your extension on top and ship that, not the template. - Retain
LICENSEandCOMMERCIAL-LICENSE.mdin your local working copy. They do not need to ship inside your built extensiondist/— your end users receive your extension, not the template.
Your own extension’s distribution license is your decision. Common choices: All Rights Reserved (proprietary, typical for paid extensions), MIT (open), Apache 2.0 (open with patent grant). Set it in your extension’s own README and per-file headers if you go that route. Full terms governing your use of the template are at the base’s LICENSE and COMMERCIAL-LICENSE.md.
What’s next
Section titled “What’s next”You have now seen every distribution path the base assumes. Once the flow feels familiar, swing back to the post-clone checklist and confirm LICENSE and package.json reflect your own extension.
If something snags during distribution, start at 06-troubleshooting.md, and read the auto-generated DISTRIBUTION.md alongside it.