How to Publish a Chrome Extension to the Web Store in 2026 (Full Checklist)
The Web Store review is a product gate — privacy policy, assets, and MV3 compliance matter as much as your code.
How to Publish a Chrome Extension to the Web Store in 2026 (Full Checklist)
You've built the extension. It works locally. Now comes the part nobody enjoys: packaging, listing, and surviving Google's review process.
This post is the full checklist — everything you need from "Load Unpacked works" to "Published on the Chrome Web Store."
Step 0: Developer Account
- Go to Chrome Web Store Developer Dashboard
- Sign in with a Google account
- Pay the $5 one-time registration fee
- Accept the developer agreement
That's it. The $5 is non-refundable and per-account, not per-extension. One account can publish unlimited extensions.
Step 1: MV3 Compliance Check
The Web Store only accepts Manifest V3 extensions. Before packaging, verify:
-
"manifest_version": 3in manifest.json - Background script is a
service_worker, not abackground.page - No
eval(),new Function(), or inline scripts in extension pages - No remotely hosted code (
<script src="https://...">) - Using
chrome.scripting.executeScriptinstead ofchrome.tabs.executeScript - Using
chrome.declarativeNetRequestif modifying network requests (notchrome.webRequestblocking mode) - Content Security Policy doesn't include
unsafe-eval(onlywasm-unsafe-evalis allowed)
If you followed the TypeScript setup guide, you're already MV3-compliant. If migrating from MV2, Google has a migration checklist.
Step 2: Package the Extension
Build your production bundle:
npm run build
With CRXJS, this outputs to dist/. The folder contains:
- Compiled JavaScript files
- HTML pages (popup, options)
manifest.json(processed by CRXJS)- Icons and assets
Create a ZIP file of the dist folder contents (not the folder itself):
cd dist
zip -r ../my-extension.zip .
Step 3: Prepare Store Listing Assets
You need these before uploading:
Required
- Extension name — 45 characters max. Include the primary keyword naturally.
- Summary — 132 characters. Shown in search results. Make it count.
- Description — Up to 16,000 characters. First 2-3 sentences are most important (shown in preview).
- Icon — 128×128px PNG. Must look good at 48px and 16px too (Chrome resizes it).
- Screenshots — At minimum 1, up to 5. Size: 1280×800 or 640×400. Show the actual extension UI, not marketing graphics.
- Category — Choose the most relevant. "Productivity" is the most competitive.
- Language — Primary language of the extension.
Required if Accessing User Data
- Privacy policy URL — Required for any extension that uses
activeTab,storage, host permissions, or collects any data. Must be publicly accessible. - Permissions justification — For each permission declared, explain WHY the extension needs it. This is filled in during the submission form. Be specific.
Optional but Recommended
- Promo tile — 440×280 PNG. Used for featured placement.
- YouTube video — Embedded in the listing. Dramatically increases installs.
- Website URL — Link to your site for credibility.
Step 4: Write a Privacy Policy
Every extension that accesses user data needs one. Here's the minimum viable privacy policy structure:
- What data you collect — Be specific. "Browsing history on blocked sites" not "some data."
- How you use it — "To display time tracking statistics locally."
- Where it's stored — "All data is stored locally using chrome.storage. No data is transmitted to external servers."
- Third-party sharing — "We do not share any user data with third parties."
- Contact — An email address for privacy inquiries.
If your extension is fully local (like ResistGate or Amethyst), the policy is short: "All data stays on your device. Nothing is sent anywhere."
Host this on your website. A simple page at yoursite.com/privacy or yoursite.com/extension/privacy-policy works. Google checks that the URL is accessible.
Step 5: Upload and Submit
- Go to the Developer Dashboard
- Click "New Item"
- Upload the ZIP file
- Fill in all listing fields
- Upload screenshots and icons
- Add privacy policy URL
- Justify each permission in the designated fields
- Choose distribution: Public (everyone), Unlisted (link only), or Private (specific users)
- Click "Submit for Review"
Step 6: The Review Process
Timeline
- Initial review: 1-3 business days for simple extensions
- Extensions with broad permissions: 3-7+ business days
- Extensions with
<all_urls>: Expect longer review. Detailed justification required. - Updates to existing extensions: Usually faster than initial review (1-2 days)
What Reviewers Check
- Permission justification — Every requested permission must have a clear reason
- Code clarity — Obfuscated or heavily minified code gets flagged
- Privacy policy accuracy — Must match actual data practices
- MV3 compliance — All the items from Step 1
- Malware/spam — Automated scans run first, then human review if flagged
- Store policies — No deceptive functionality, no unauthorized data collection
Common Rejection Reasons (and Fixes)
"This extension requests more permissions than necessary"
Cause: Using <all_urls> when narrower host permissions would work, or requesting tabs when activeTab suffices.
Fix: Audit your permissions. Move non-essential permissions to optional_permissions. In the justification, explain exactly why each permission is needed for core functionality.
"Privacy policy not found or insufficient"
Cause: Broken privacy policy URL, or policy doesn't mention the specific data the extension accesses.
Fix: Verify the URL works. Mention each permission's data access explicitly. For extensions using storage, state where data is stored (locally vs. synced).
"Code readability"
Cause: Heavy minification or bundler output that looks obfuscated.
Fix: Configure Vite to produce readable output in production:
// vite.config.ts
export default defineConfig({
build: {
minify: false, // or 'terser' with readable options
sourcemap: true,
},
// ...
});
Source maps help reviewers but aren't strictly required. Readable output without sourcemaps usually passes.
"Deceptive functionality"
Cause: Extension does something not described in the listing, or the description implies functionality that doesn't exist.
Fix: Match listing description to actual features exactly. If your extension blocks sites, say it blocks sites. Don't claim it "boosts productivity by 300%" without evidence.
"Duplicate or spam"
Cause: Listing is too similar to an existing extension, or multiple extensions from the same account do nearly the same thing.
Fix: Differentiate clearly. Explain what makes your extension different from alternatives.
Post-Publication Checklist
After approval:
- Test the installed version from the Web Store (not just the dev version)
- Verify the listing appears in search for your target keyword
- Check all screenshots and description render correctly
- Add the Web Store link to your website
- Submit to extension directories and review sites for visibility
Updating Your Extension
- Increment the
versioninmanifest.json - Rebuild and re-zip
- Go to Developer Dashboard → your extension → "Package" tab
- Upload the new ZIP
- Submit for review
Updates go through the same review process but are typically faster (1-2 days).
Breaking change warning: If you add new permissions in an update, Chrome will disable the extension for existing users until they accept the new permissions. Minimize permission changes in updates.
Pricing Models
The Web Store supports free, freemium (free + in-app purchase), and paid extensions. Options:
Free — Maximum installs, build audience. Monetize through a separate product or service.
Freemium — Core features free, premium features behind a payment. Use chrome.storage.sync to store license status. Implement license checking via your own backend or a service like Gumroad/LemonSqueezy.
Paid — Lowest install rate but direct revenue. Chrome Web Store Payments is deprecated — use external payment processing.
ResistGate uses freemium: core blocking is free, analytics and commitment mode are paid.
The Full Checklist (Copy This)
PRE-SUBMISSION
[ ] manifest_version: 3
[ ] All code bundled locally (no remote scripts)
[ ] No eval() or unsafe-eval in CSP
[ ] Service worker handles idle termination correctly
[ ] Permissions audited — minimum necessary set
[ ] Privacy policy hosted and accessible
[ ] Icons: 128×128, 48×48, 16×16
[ ] Screenshots: 1280×800, showing real UI
[ ] Description written with target keywords
[ ] Tested in Chrome, Edge, Brave
SUBMISSION
[ ] ZIP contains dist/ contents (not the folder)
[ ] All listing fields filled
[ ] Permission justifications written
[ ] Privacy policy URL added
[ ] Distribution set to Public
POST-APPROVAL
[ ] Install from Web Store and test
[ ] Verify search visibility
[ ] Add Web Store link to website
[ ] Monitor reviews for bug reports
What's Next
If you've followed this series from the beginning, you've gone from empty folder to understanding the architecture, typed message passing, persistent storage, React popups, locked-down permissions, AI integration, AI-assisted building, and a real product case study — all the way to published on the Web Store.
Ship something.
This is the final post in the Chrome Extensions from Zero to Product series. If you found it useful, ResistGate and Amethyst are the products this series was born from.
Notes from the build
Get more AI engineering insights
Follow the work: AI tools, browser products, product decisions, and honest lessons from the build.
By subscribing, you agree to receive Orlando's emails. No spam. Unsubscribe anytime.