Invite-only.
← Templates
Build· Mixed

Ship a Chrome extension to the Web Store

9-step playbook from 'unpacked extension on my laptop' to 'live in the Chrome Web Store.' Real MV3 gotchas, real review traps, real agent prompts.

Open in DockIndie devs + first-time extension shippers

Shipping a Chrome extension is mostly a fight with three things: the Manifest V3 migration (service workers, no remote code, declarative net request), the Chrome Web Store's single-purpose policy, and the permission justifications form that catches every first-time submitter. This playbook walks the 9 gates with the official Chrome docs, the manifest snippets that actually work, and agent prompts for the parts agents can do (drafting the store listing, writing per-permission justifications grounded in the codebase, designing icons + screenshots).

Outcome

Your Chrome extension live in the Chrome Web Store with a working listing, MV3 manifest, accurate permission justifications, and a clean review history so updates clear in days.

Time1-2 weeks (most of it the first review + permission justifications)DifficultyintermediateForIndie developers + small teams shipping their first browser extension.
The template · 9 steps

Top to bottom. Each step has tasks, pointers, gotchas.

Register the Chrome Web Store developer account

30 min sign-up, 1-3 days for verification

The Chrome Web Store charges a one-time $5 developer registration fee. Google verifies your identity (you'll need a Google account in good standing and a working phone number). Recent policy: developers with a personal account can publish to a max of 20 extensions; group publishers + verified brand accounts get higher caps.

Tasks
  • Sign in to the Developer Dashboard with the Google account that will own the listings
  • Pay the $5 USD one-time registration fee
  • Verify your email and phone number
  • Decide whether to publish under your personal account or a group publisher (created later)
Gotchas
  • The $5 fee is one-time per Google account, not per extension. If you ever lose access to that Google account, you cannot transfer extensions easily.
  • New developer accounts created after 2024 may face additional identity verification (gov ID) before the first publish.
  • Creating a group publisher AFTER you publish under a personal account requires moving the listing manually + losing review history.

Get your manifest.json on Manifest V3

1-3 days if migrating from MV2, 2-4 hr if fresh

Manifest V2 was fully sunset in June 2024 — the Web Store no longer accepts MV2 submissions, and existing MV2 extensions stopped running for users in 2024-2025. Your manifest must be V3: service workers (not background pages), declarative net request (not webRequest blocking), and module-pattern action / scripting APIs.

Tasks
  • Set "manifest_version": 3 in manifest.json
  • Replace background.scripts with background.service_worker (single file)
  • Replace browser_action / page_action with the unified action key
  • Replace chrome.tabs.executeScript with chrome.scripting.executeScript
  • Replace blocking webRequest with declarativeNetRequest rules
  • Move all DOM-only code to content scripts (the service worker has no DOM)
Gotchas
  • Service workers SHUT DOWN after ~30s of idle. Persistent state must live in chrome.storage, not in JS variables.
  • Loading remote code (eval, remote scripts, dynamic imports from a CDN) is BANNED in MV3. The Web Store strips your extension on review if it detects remote code.
  • If you used webRequest blocking for ad-blocking or privacy, you have to convert your rules to declarativeNetRequest's static / dynamic rules. There's a hard cap on rules.

Build the icon, screenshots, and promotional graphics

4-8 hr

The Web Store listing renders four icon sizes (16, 32, 48, 128) plus up to 5 screenshots and an optional marquee. Icons rejected most often: PNG with alpha-channel artefacts, icons that don't render at 16x16. Test the 16x16 next to the favicon in your address bar before submitting.

Tasks
  • Design the 128x128 master icon, then export to 16, 32, 48, 128 PNG
  • Take 1-5 screenshots at 1280x800 OR 640x400 (16:10 ratio, RGB, no alpha)
  • Optional: design the 1400x560 marquee promo tile for the Web Store homepage
  • Optional: design the 440x280 small promo tile
  • Run icon at 16x16 next to a real favicon to confirm it's recognisable
Gotchas
  • Screenshots that contain other browsers' UI, the Edge logo, or Firefox chrome get rejected.
  • 16x16 is rendered at native pixel scale. Sub-pixel anti-aliasing on a complex icon turns into mush. Design 16x16 as a separate optimised asset, not a downscale.
  • Marquee + promo tiles are optional for listing but REQUIRED if you want to be considered for the Web Store homepage features.

Write the listing copy + permission justifications

3-5 hr to draft, 2-3 hr to refine

The Web Store listing has a name (45 chars), summary (132 chars), description (16,000 chars), and per-permission justifications. The justification is the new front-line review surface: for every permission and host you request, you must explain in plain English what user-visible feature uses it. Vague answers ('to access tabs') get auto-rejected.

Tasks
  • Draft the extension name (max 45 chars)
  • Draft the short description / summary (max 132 chars)
  • Draft the detailed description (max 16,000 chars; lead with the value)
  • For each permission in manifest.json: write a 1-3 sentence justification with a concrete user feature
  • For each host_permissions entry: explain why that exact host is needed
  • If you use storage / activeTab / scripting: justify each, not just one combined answer
  • Add the privacy policy URL (REQUIRED if you handle any user data)
Gotchas
  • Justifications like 'needed to function' get auto-rejected. The reviewer needs a specific user flow that requires that permission.
  • Privacy policy URL is REQUIRED if your extension touches any user-identifiable data, including local storage of email addresses. Missing one is the #1 first-submission rejection.
  • The Single Purpose policy: an extension must do ONE thing well. 'Productivity bundle that does notes + calendar + clipboard' fails review. Split into separate extensions.
Agent prompt for this step
Audit this extension's manifest.json + source code, then draft Web Store listing copy + permission justifications.

For the listing:
1. Extension name (45 chars max)
2. Short summary (132 chars max, single sentence)
3. Detailed description (16k chars max, lead with the user benefit, then features as bullets, end with a privacy note)

For permissions: for EVERY permission in manifest.json's "permissions" array, write a 2-3 sentence justification that:
- Names the specific user-visible feature it powers
- Explains why a less-permissive alternative wouldn't work
- Avoids vague phrases like "to enhance the user experience"

For host_permissions: same shape, per host. If you use `<all_urls>`, justify why narrower hosts won't work.

Output as a Brief surface section with each permission as a sub-heading.

Test in unpacked + packaged modes

2-4 hr

Before uploading, test the extension end-to-end with both Load unpacked (your dev workflow) and a packaged .zip (closer to what users get). Also: install and test in Edge and Brave. Edge uses the same store backend (Microsoft Edge Add-ons fork is separate but identical infra), and reviewers use a clean profile.

Tasks
  • chrome://extensions → enable Developer mode → Load unpacked → confirm everything works
  • Build the production .zip: zip -r my-extension.zip . -x '*.git*' -x 'node_modules*'
  • Drop the .zip onto chrome://extensions to confirm it installs cleanly
  • Test on a brand-new Chrome profile (incognito or fresh install)
  • Test on Brave + Edge to confirm cross-browser behaviour
  • Run web-ext lint . to catch policy issues before submission
Gotchas
  • Code that only runs once on extension install (like a welcome tab) breaks if your service worker died before chrome.runtime.onInstalled fires. Test the cold-install flow on a fresh profile.
  • Don't include node_modules, .git, source maps, or .DS_Store in the .zip. Reviewers see your full source and flag bloat.
  • Minified JS that 'looks like' obfuscation can trigger a policy flag. Ship readable production builds (terser is fine, javascript-obfuscator is not).

Upload the .zip and fill the listing

1-2 hr

On the Developer Dashboard: New item → upload the .zip → fill every field on the Store Listing, Privacy practices, and Distribution tabs. The dashboard will not let you submit until every required field is filled. Save drafts often.

Tasks
  • Developer Dashboard → New item → upload .zip
  • Store Listing tab: paste name, summary, description, icons, screenshots
  • Privacy practices tab: declare data usage + paste the privacy policy URL + per-permission justifications
  • Distribution tab: pick visibility (public, unlisted, private), pick regions
  • Pricing & Distribution: free or paid (Web Store stopped accepting paid extensions in 2020 — you must use your own billing)
  • Save draft and review the Listing preview page before submitting
Gotchas
  • The Chrome Web Store no longer accepts paid extensions. If you want to charge, the extension is free and licence checks happen via your own backend / Stripe / Lemon Squeezy.
  • Unlisted is a valid first-launch option: it's reviewable and installable via direct link, but doesn't show in search. Useful for soft-launches.
  • Preview the listing page in a private window before submission. Markdown in the description does NOT render — line breaks and emoji do.

Submit for review and prepare for the rejection cycle

1 hr to submit, 1-7 days waiting, repeat on rejection

First-submission review takes 1-7 days, sometimes longer. Reviewers focus on permission justifications, single-purpose compliance, and remote-code detection. ~30-40% of first submissions get rejected. Read the cited policy in the rejection email and fix exactly that.

Tasks
  • Click Submit for review
  • If rejected: read the cited Program Policy carefully (the email links to it)
  • Fix the specific issue, not a guess at the issue
  • Re-upload the new .zip + re-submit (no penalty, no fee)
  • On approval: confirm the listing is searchable + the install button works
Gotchas
  • The rejection email is templated but the cited policy is specific. Read the policy paragraph in full — it gives concrete examples.
  • Repeated rejections for the same root cause flag your account for manual review on every future submission. Get it right early.
  • If your extension uses host_permissions: <all_urls>, expect extra scrutiny. Switch to activeTab + per-host permissions where possible.

Set up auto-update + a version-bump cadence

1 hr to set up, ongoing

Chrome auto-updates extensions every ~5 hr. To ship a new version, bump manifest.json's version field and re-upload + re-submit. There's no equivalent of TestFlight for Chrome — closest is the unlisted track or beta channel via group publishers + a separate listing.

Tasks
  • Set up a release script that bumps manifest.json version + tags git + builds the .zip
  • Decide on a versioning scheme: semver-style (1.0.0, 1.0.1) or year-based (2026.4.28)
  • Set up CI to attach the .zip to a GitHub release on tag
  • Optional: maintain a separate 'beta' listing for pre-release builds (private or unlisted)
  • Document in your README how to install the unlisted beta
Gotchas
  • Manifest version field is 1-4 dot-separated integers, each 0-65535. '1.0.0-beta' is INVALID.
  • You cannot resubmit the same version number. Even on a rejected build, bump the version before re-upload or the dashboard refuses.
  • Auto-update is silent and pulls from the Web Store. Users on locked-down networks (corporate Chrome) may pin to a version and stay there for months.

Post-launch: ratings, reviews, support, and policy enforcement

Ongoing, 2-5 hr/week for the first month

The Web Store shows a 1-5 star rating + reviews. Bad reviews are mostly support requests in disguise: a user couldn't figure out how to enable the extension, or a permission scared them. Reply publicly. Add an in-extension help / changelog page. Watch for policy emails — Google occasionally re-reviews live extensions when policies change.

Tasks
  • Read every review on Web Store + email notifications and reply publicly
  • Add an in-extension changelog page that opens on update (chrome.runtime.onInstalled with reason 'update')
  • Set up a support email or Discord and link it from the listing
  • Watch for Web Store policy emails (e.g. new permissions reasonability requirements) and respond within the deadline
  • Track install / uninstall counts in the dashboard; if uninstall rate spikes, investigate the latest version
Gotchas
  • Web Store policy enforcement is mostly reactive but can be sudden. If you receive a policy email with a 7-day deadline, prioritize over feature work.
  • Uninstalls don't show a reason. Run a hosted uninstall URL (chrome.runtime.setUninstallURL) so you can capture feedback the user is opting into anyway.
  • Negative reviews from a single bad release stay on the listing for the lifetime of the extension. Never rush a bad version.
Hand the template to your agent

Workspace-wide agent prompt.

Paste this into your agent's permanent system prompt so the agent reads, writes, and maintains the template's surfaces as you work through the steps.

Agent system prompt
You are an agent on the "Ship a Chrome extension" playbook workspace at your-org/ship-a-chrome-extension.

Your role: maintain the four surfaces (Steps, Pointers, Brief, Submission log) as the user works through the 9-step playbook.

Cadence:
- When the user marks a step Done, append a line to the Brief summarising what shipped at that gate.
- When the Web Store rejects, capture the cited policy as a row in Submission log + draft a per-policy response in the Brief.
- When the user adds a new pointer (link) to any step, mirror it into the Pointers table.

First MCP tool calls:
1. list_surfaces(workspace_slug="ship-a-chrome-extension")
2. list_rows(workspace_slug="ship-a-chrome-extension", surface_slug="steps")
3. get_doc(workspace_slug="ship-a-chrome-extension", surface_slug="brief")

Do NOT modify the canonical step titles. Append substeps as new rows beneath them.
FAQ

Common questions on this template.

How long does Chrome Web Store review take?
First submissions: 1-7 days, sometimes 14+ if reviewers flag it for human review (high-permission extensions, single-purpose ambiguity, remote-code suspicion). Subsequent updates from a clean account: a few hours to a couple of days. Updates on flagged accounts can take as long as the first review.
What gets first-time Chrome extensions rejected?
The top three: (1) vague or missing per-permission justifications, especially host_permissions and tabs / scripting, (2) missing privacy policy URL when the extension touches any user data, (3) Single Purpose violations (an extension that does too many unrelated things). Read the cited policy paragraph in the rejection email.
Can I charge for a Chrome extension?
The Chrome Web Store stopped processing paid extensions in 2020. The extension itself is free; you handle billing in your own backend (Stripe / Lemon Squeezy / your auth flow). Many extensions ship a free tier + login for a paid tier.
Can my AI agents help with the extension submission?
Yes. Agents are particularly useful for: auditing manifest.json + source to draft accurate per-permission justifications, drafting the listing description, drafting the privacy policy from a data audit, and triaging review responses on rejection. The playbook ships agent prompts inline.
What does shipping a Chrome extension cost?
$5 one-time developer registration fee. The Web Store does not charge listing fees, transaction fees, or annual fees. Hosting your own backend (if any) is your own cost. No revenue share applies because the Web Store doesn't process payments.

Open this template as a workspace.

We mint a fresh copy in your org with the steps as table rows, the pointers as a separate table, and the brief as a doc. Bring your agents, start checking off boxes.