Chrome Web Store preflight checker
Chrome Web Store Submission Checklist
Check the ZIP you are about to upload, not the folder you have been working in.
A source directory and a production build are different things, and the difference is where submissions get lost. Your bundler rewrites paths. Your ignore rules decide what ships. A file that resolves in development can be missing, misspelled or capitalised differently inside the archive. Reviewers open the archive, so that is the artifact worth checking.
Work through this list against the exact file you will upload. The first section is automated by the scanner below. The rest is manual, because it depends on your store listing, your privacy policy and whether your extension actually works.
Scan your production ZIP
Your file is read in this tab and never sent to a server.
Want to see a report first? Read the Dark Reader sample on the scanner page.
Your ZIP and scan results never leave this browser. We record only aggregate page views, scan counts and errors, never file information or findings.
See the exact analytics data
Nothing sent yet.
Package and manifest
Every item here is checked automatically by the scanner above, except the last one, which needs a real browser.
- manifest.json exists and parses. It must sit at the archive root, not inside a wrapper folder. Zipping the folder instead of its contents is the single most common packaging mistake. A trailing comma or comment will also stop it parsing.
- Every referenced file exists in the ZIP. Service worker, content scripts, popup, options page, icons, web accessible resources. A path that resolves on your machine can be absent from the archive.
-
Paths and capitalisation are correct.
Icons/Logo.pngandicons/logo.pngare the same file on macOS and Windows and different files everywhere else. Case mismatches survive local testing and break after upload. -
The content security policy is valid.
An unparseable or over-permissive
content_security_policyis rejected. Manifest V3 does not allowunsafe-evalor remote script sources in the extension pages policy. -
No remotely hosted executable code.
No
<script src>pointing at a CDN, no injected remote bundles. Under Manifest V3 all executable code ships inside the package. -
Production files are present.
Confirm the built output is in the archive rather than an empty or stale
distdirectory from a failed build. -
Development files and secrets are excluded.
No
.env, no API keys, no.gitdirectory, no source maps you did not mean to publish, no test fixtures. Anything in the archive is public the moment you upload it. -
The packaged build loads in Chrome.
Not covered by the scanner. Unzip the archive to a clean directory, load it
unpacked at
chrome://extensions, and confirm it registers without errors.
Permissions
Permissions are the most common cause of avoidable review friction, and the easiest thing to fix before you upload.
- Remove permissions you do not use. Permissions accumulate during development and rarely get cleaned up. If nothing in your code calls the API, delete the permission.
-
Narrow your host permissions.
<all_urls>and*://*/*ask for every site a user visits. If you need three domains, list three domains. Consider whetheractiveTabcovers your case instead. - Do not request permissions for functionality you have not built. Requesting ahead of a planned feature means requesting something your current code cannot justify. Add it in the release that uses it.
- Be able to justify every permission in one sentence. You will be asked to, in the dashboard. If you cannot name the feature that needs it and the API call that uses it, that is a sign to remove it.
Code
- No remotely hosted JavaScript. Everything executable is in the package. This includes analytics snippets, tag managers and font loaders that pull scripts at runtime.
-
Avoid eval and remote command interpretation.
eval,new Functionand evaluating strings fetched from a server all read as remote code execution, whatever the intent. - Your code stays reviewable. Minification is expected. Deliberate obfuscation is not, and a reviewer who cannot follow what the code does has no reason to approve it.
- Check what your bundler produced. Aggressive minifier settings can make ordinary output look obfuscated. The scanner flags packages whose file statistics look that way, so you can see what a reviewer would see.
Store listing
Not covered by the scanner. These live in the dashboard, not the package, so nothing automated here can see them.
- A meaningful title and icon. The title should say what the extension does rather than borrow a better known product's name. The icon needs to be legible at 16 pixels.
- Screenshots that show the current build. Screenshots of an older interface, or of features you removed, are a mismatch between what you promise and what you ship.
- A description that matches behaviour. Every claim in the description should be something the uploaded package actually does.
- All major functionality disclosed. If the extension also collects analytics, changes search settings or injects content on unrelated sites, say so. Undisclosed behaviour found during review is treated far more harshly than disclosed behaviour.
- One clear purpose. An extension that does four unrelated things is harder to justify than four extensions. A single narrow purpose is both a policy requirement and an easier review.
Privacy
Not covered by the scanner. The scanner reads your package, and none of this is in your package.
- A privacy policy URL that works. Public, reachable without a login, and not a 404. Open it in a private window and confirm.
- The policy entered in the designated dashboard field. A link buried in your description does not count. There is a specific field for it.
- Data use disclosures that match reality. The dashboard asks what you collect and what you do with it. Answer for the code you are uploading, including anything a third party library sends.
- Prominent disclosure and consent where required. If you collect data that is not obviously necessary for the feature the user invoked, you need an in-product disclosure and consent, not just a line in the policy.
Functional release test
Not covered by the scanner. A package can be perfectly formed and still not work.
- Test the packaged release, not your dev build. Unzip the upload candidate, load it unpacked, and use it. Hot reloading hides real failures.
- Walk through onboarding as a new user. Use a fresh Chrome profile with no existing storage. First run is the path you have tested least and reviewers see first.
- Exercise the main flows end to end. Every feature you named in the description.
- Try it signed out, with a server error, and on a poor connection. Reviewers may have no account with your service. If signed-out use shows a blank panel, that reads as broken.
- Make the error states understandable. An error should say what went wrong and what to do next. A spinner that never resolves is the worst outcome of the three.
What a clean scan actually means
The honest boundary
A clean scan means only that Extension Preflight's package-level rules found no risk in the archive you gave it. It does not guarantee Chrome Web Store approval.
The scanner reads your package. It has no view of your store listing, your privacy policy, your screenshots or whether your extension works. Those account for most rejections, and no package-level tool will ever catch them. That is why two thirds of this page is manual.
What it does read
Nine rules across the manifest, the file listing, the content security policy, HTML script references and file statistics. Every finding says which evidence it used, and the two weakest rules say so explicitly.
The full rule list and its limitations are on the scanner page.
Check your package
The automated part of this list takes about a second. Your file is read in this tab and never sent to a server.