Chrome Web Store
The runbook for your first Chrome Web Store (CWS) submission. Work top to bottom; nothing here assumes a previous publication.
1. Create the developer account
Section titled “1. Create the developer account”- Sign in to the Chrome Web Store Developer Dashboard with the Google account that will own the listing.
- Pay the one-time $5 registration fee and verify your email.
- Turn on two-factor authentication. A phished publisher account can push a malicious update to every install; the account is a bigger target than the code. Longer term, publish from CI with scoped API credentials, never from laptops.
2. Build the zip
Section titled “2. Build the zip”pnpm zippnpm audit:remote-codepnpm zip builds production and writes the store-ready zip to
apps/extension/.output/ (the filename embeds the version). The audit
scans the built output for remote-code patterns before review does.
3. Minimize permissions
Section titled “3. Minimize permissions”Every permission adds install-warning friction and review time. Pruned modules already took their permissions with them; review two items yourself:
- The on-page demo matches
https://*/*, while your clone keeps it. That broad pattern exists only so the demo status card shows on every page, and it is the kit’s single biggest review-time item. Without on-page UI, remove the demo: commit, thenpnpm create extstart --on-page no --yes. With on-page UI, narrowmatchesto the sites your product operates on, or switch to programmatic injection withactiveTab. - Every remaining permission needs a one-line justification in the privacy tab:
| permission | why it’s there | drop it when |
|---|---|---|
storage | every store/state primitive | never (core) |
identity | Google sign-in (launchWebAuthFlow) | you remove Google sign-in |
offscreen | Google sign-in fallback (Firebase), used while WXT_GOOGLE_OAUTH_CLIENT_ID is unset | you set WXT_GOOGLE_OAUTH_CLIENT_ID (the Firebase step does when Google sign-in is on): the build then drops the permission by itself |
sidePanel | the sidepanel surface | you answer no to the wizard’s Side panel question |
Never request <all_urls>, webRequest, cookies, history, or
management “for later”: each is a review escalator; add them with the
feature that needs them.
4. Create the listing
Section titled “4. Create the listing”Store listing tab: name, description, at least one 1280×800 or 640×400
screenshot, the 128×128 icon (your own, not the kit’s placeholder mug),
category, and language. pnpm build && pnpm store-assets:capture && pnpm store-assets
captures your popup’s screens over your website and frames them as the
screenshots and promo tiles (apps/extension/store-assets/README.md in
your clone). Write the
description around your single purpose: CWS expects one narrow
purpose, and the listed functionality must work on a fresh install
(how the kit guarantees that).
5. Fill the privacy practices tab
Section titled “5. Fill the privacy practices tab”Every field must be filled before the submit button enables:
- Single purpose: one sentence.
- Permission justifications: one per permission; use the table above.
- Data usage: paste the kit’s answers from Privacy disclosures.
- Certifications: the three compliance checkboxes; Privacy disclosures covers why the unmodified kit satisfies each.
- Privacy policy URL: required as soon as you collect any user data
(with auth enabled, you do). Host one at the URL in
site.config.ts → urls.privacy.
6. Submit
Section titled “6. Submit”- Typical review is hours to a few days. Broad host permissions and new developer accounts stretch that, up to a few weeks. Don’t plan a launch on review completing overnight.
- Deferred publish lets you get approved first and press publish when ready.
- Staged rollout requires a large existing install base, so it won’t apply to submission #1.
- If rejected, the email names a code; look it up in Rejection codes.
7. After approval
Section titled “7. After approval”- Monitor the listing for unexpected versions.
- Later releases can go through CI:
pnpm submitwrapspublish-browser-extensionand reads store credentials from the environment (submit:dryvalidates credentials without uploading). A version tag builds the zips into a GitHub Release; the upload runs when you start “Publish to stores” in the Actions tab with that tag. It publishes to 10% of Chrome users unless the repository variableCHROME_DEPLOY_PERCENTAGEsays otherwise. Set it to100below 10,000 users: the store allows a partial rollout only above that. - Broadcasts reach installs while a review is pending.
Keep one extension ID
Section titled “Keep one extension ID”Your unpacked builds get their ID from the key in
apps/extension/extension-public-key.txt. The store assigns its own ID
at the first upload, and pnpm zip leaves the key out because a new
store item rejects a manifest that carries one. Google sign-in in the
store build needs that ID’s redirect URI too. Either way works:
- Add a second redirect URI. Copy the item ID from the developer
dashboard and add
https://<store-id>.chromiumapp.org/to the OAuth client next to your development one. - Switch to the store’s key. In the developer dashboard open the item,
then Package → View public key, and paste the whole block into
extension-public-key.txt. Rebuild: development now has the store’s ID, so one redirect URI covers both. A draft upload is enough to get the key; you do not have to publish first.
Commit the new key file so teammates and CI follow.