Muhammad Atif
Command Palette

Search for a command to run...

Blog

Publishing on the Chrome Web Store: Permissions, Privacy and Review

Publishing on the Chrome Web Store: Permissions, Privacy and Review

What the Chrome Web Store review process actually checks - permission justifications, privacy disclosures, listing quality, the common rejection reasons, and how to ship updates without breaking your users.

Getting an extension written is the first half. Getting it published, and keeping it published, is the second - and it catches out more developers than the code does.

I have taken five extensions through Chrome Web Store review, including Gemini Watermark Remover Pro, Grok Automation, Kick Video Downloader, VidPulse and Design.md. This is what the process actually looks like and where it goes wrong.

What review is really checking

Review is largely an alignment check. Reviewers compare three things:

  1. What your listing says the extension does.
  2. What your permissions allow it to do.
  3. What the code actually does.

When those three agree, review is usually quick. When they disagree - you request a permission the description does not explain, or the code touches something the listing never mentions - you get rejected or asked for clarification.

Almost every rejection I have seen or had traces back to that gap. Fix alignment and most of the process takes care of itself.

Permissions: the biggest single factor

Every permission is a cost. It shows up in the install dialog, it lowers conversion, and it adds review scrutiny.

Request the narrowest thing that works

activeTab grants access to the current tab only when the user clicks your extension. It requires no scary install warning. If your flow is user-initiated, use it instead of host permissions.

Compare what users see:

// "Read and change all your data on all websites"
"host_permissions": ["<all_urls>"]
 
// "Read and change your data on kick.com"
"host_permissions": ["https://kick.com/*"]

The second converts far better and reviews faster. <all_urls> is the single biggest red flag you can put in a manifest, and it needs a genuinely convincing justification.

Ask at runtime

Optional permissions let you start minimal and request more only when a feature needs it:

{
  "optional_host_permissions": ["https://*/*"]
}
const granted = await chrome.permissions.request({
  origins: ["https://example.com/*"],
})
 
if (!granted) {
  showMessage("Access is needed to download from this site.")
}

The user sees the request in context, when they already understand why. That is a much easier yes than a wall of warnings at install.

Write the justifications properly

The dashboard asks you to justify each permission and the single-purpose requirement. These are read by a human. Vague answers cause delays.

Weak: "Needed for the extension to work."

Better: "downloads is used to save the video the user selects to their device. It is invoked only after an explicit click on the Download button and is not used at any other time."

Name the permission, name the feature, say when it fires. Do this for every one.

The single purpose rule

An extension must do one narrow thing. This trips up developers who bundle several tools into one package to save on listings.

A downloader that also injects a theme changer and a tab manager will be rejected. The fix is separate extensions, which is also better for users - they install what they want and see a smaller permission set.

If your extension genuinely has multiple features, frame them as one purpose. "Save media from supported sites" covers detection, quality selection and downloading, because those are steps in one job. It does not stretch to "and also blocks ads".

Privacy disclosures

You must declare what data you handle, and the declaration must match reality.

The categories that matter most: personally identifiable information, authentication information, personal communications, location, web history, user activity, and website content.

Two failure modes, both common:

Under-declaring is a policy violation. If your licence check sends an email address, that is PII, and it must be declared. See monetising a Chrome extension for how to keep that surface as small as possible.

Over-declaring by copy-pasting a generic policy is also a problem. Claim you collect browsing history when you do not, and a reviewer will ask you to justify why you need it. You will have created work for yourself over data you never touch.

You must also affirm that data is not sold, is used only for the stated purpose, and is not used for creditworthiness or lending. If you cannot make those statements truthfully, the extension will not pass.

Keep the honest version short

The strongest privacy position is a design where there is little to disclose. If processing happens in the browser and your server only stores hashed licence identifiers, your disclosure is genuinely two lines. That is easier to write, easier to review and easier for users to believe.

Remote code is not allowed

Manifest V3 bans executing code that is not in your package. That means:

  • No loading scripts from a CDN.
  • No eval of fetched strings.
  • No remotely-defined logic evaluated at runtime.

Fetching data is fine. Fetching code is not. The distinction is whether the response is interpreted as executable logic.

This catches people who bundle an analytics snippet that loads a remote script. Vendor the library into your package instead, or drop it.

The listing itself

The listing is a store page. It is also read carefully during review.

Name. Clear, not keyword-stuffed. "Kick Video Downloader - Download Kick Clips" is fine; a name that is only keywords will be rejected.

Short description. 132 characters, appears in search results. Say what it does in plain words.

Detailed description. Explain the features, the permissions and any limits. If there is a paid tier, say so - users who discover a paywall after installing leave one-star reviews. Being upfront costs a few installs and saves your rating.

Screenshots. 1280×800 or 640×400. Show the actual interface. Mockups that do not match the product get flagged.

Category and language. Set them correctly; they drive discovery more than most developers expect.

Store listing SEO

Chrome Web Store search is mostly name, short description and detailed description. Some practical points:

  • Lead with the term people actually search. "Facebook video downloader" is a real query; "media acquisition utility" is not.
  • Use the phrase naturally a few times across the descriptions. Repetition without sentences reads as spam and is a rejection reason.
  • Ratings and install count dominate ranking long-term. The listing gets the first click; the product earns the rest.

Common rejection reasons

From my own submissions and from watching other developers:

Permission not justified. You requested something the description never explains. Fix: remove it, or explain it precisely.

Single purpose violation. Too many unrelated features. Fix: split into separate extensions.

Misleading metadata. The listing promises something the extension does not do - often a leftover claim from an earlier version.

Privacy policy missing or mismatched. You declared data collection but linked no policy, or the policy contradicts the declaration.

Obfuscated code. Minification is fine. Deliberate obfuscation is not - reviewers must be able to read what you shipped.

Downloading from prohibited services. For media tools specifically, targeting a platform whose terms forbid downloading gets you removed. I covered choosing targets deliberately in how to build a video downloader extension.

Timelines and what to expect

A first submission commonly takes a few days. Updates are usually faster. Extensions requesting broad host permissions or handling sensitive data take longer, sometimes a couple of weeks.

Plan around it:

  • Do not promise users a release date that assumes instant approval.
  • Submit well before any deadline you actually care about.
  • If a review drags well past the normal window, use the developer support form. Resubmitting the same package does not speed anything up and can reset your place.

If you are rejected, the email names the policy. Read it precisely, fix that specific thing, and reply explaining what changed. Arguing rarely helps; a clear diff of what you changed usually does.

Shipping updates safely

Once you have users, updates carry real risk - they roll out automatically.

Version numbers only go up. 1.2.01.2.1. You cannot republish a number.

Use partial rollouts. Release to a percentage of users first. If something is broken, you find out from a small group rather than all of them.

Adding permissions disables the extension until each user re-consents. That is a large, silent drop in active users. If you must add one, prefer an optional permission requested at runtime.

Test the upgrade path, not just the install. Users have existing data in chrome.storage from the previous version. Run migrations defensively:

chrome.runtime.onInstalled.addListener(async ({ reason, previousVersion }) => {
  if (reason === "update") {
    await migrateStorage(previousVersion)
  }
})

A migration that assumes a field exists will throw for anyone upgrading from two versions back, and they will simply uninstall.

Ratings, reviews and support

Once published, your rating drives installs more than anything else you control. Extensions live or die in the gap between four and four-and-a-half stars.

Do not ask for reviews too early. A prompt on first launch, before the user has got value, produces low ratings. Wait for a successful outcome - a completed download, a generated file - and ask once. Never ask twice.

Reply to every negative review. Not to argue, but because replies are public. A one-star review reading "does not work on my site" followed by a developer reply explaining the fix and a release version reads completely differently to someone browsing. Silence reads as abandonment.

Give people a support route that is not a review. A support email or a link in the options page catches problems before they become public ratings. Most users would rather be helped than complain.

Watch for the review that signals a break. When a site redesigns, you get a cluster of reviews within days. Treat two similar reports in a week as an incident, not noise.

Localisation

Chrome ships everywhere, and translated listings measurably increase installs in non-English markets. The extension itself supports this natively:

_locales/
  en/messages.json
  es/messages.json
  de/messages.json
{
  "extensionName": {
    "message": "Kick Video Downloader",
    "description": "The extension name shown in the store."
  },
  "downloadButton": {
    "message": "Download"
  }
}
const label = chrome.i18n.getMessage("downloadButton")

Reference them in the manifest with __MSG_extensionName__ and set default_locale. The store listing can be translated separately in the dashboard.

You do not need to translate everything at once. Start with the store listing in the two or three languages where you already see installs - the analytics in the dashboard will tell you which. GripVid is translated into eight languages, and the non-English installs were worth far more than the effort of maintaining the string files.

A pre-submission checklist

  1. Every permission is used, and justified in the dashboard.
  2. Description matches actual behaviour, including paid limits.
  3. Privacy disclosure matches what the code does. Policy linked and reachable.
  4. No remote code. No obfuscation.
  5. Screenshots show the real UI at the right dimensions.
  6. Tested on a clean profile.
  7. Tested with the service worker terminated - see the Manifest V3 guide.
  8. Version number incremented.
  9. Upgrade path tested from the previously published version.

Keeping a published extension healthy

Publishing is not the finish line. A few habits keep an extension alive:

  • Watch the developer dashboard for policy emails. Google announces deprecations and policy changes there, often with a deadline. Missing one can mean removal without a warning you noticed.
  • Keep dependencies current. A vulnerable bundled library is a takedown risk, and you cannot patch it remotely because remote code is banned.
  • Re-read your own listing every few releases. Descriptions drift out of date as features change, and a stale claim is a policy problem waiting to happen.
  • Keep a changelog users can see. It signals the project is maintained, which matters to anyone deciding between your extension and an abandoned competitor.

Closing thought

The store's rules are mostly reasonable once you see the logic: they exist because extensions run with a lot of power on pages users trust. Every rule is aimed at making an extension's capabilities visible and proportionate.

The developers who have an easy time are the ones who ask for less, say plainly what they do, and keep the listing honest as the product changes. That is not a compliance trick - it is the same thing that makes users trust you enough to install in the first place.

You can see the extensions I have published at orbitexaio.com, or find more of my work on GitHub.

Command Palette

Search for a command to run...