How to publish a Chrome extension to the Chrome Web Store

By

Publish your Chrome extension with a clean ZIP, store listing, privacy disclosures, permission justifications, review instructions, and tested updates.

~~~

Publishing a Chrome extension means preparing its package and explaining what it does to people who haven’t seen your code.

The Chrome Web Store needs a listing, privacy disclosures, and a reason for each permission. Google reviews that information alongside the extension before it can reach users.

I distribute Repo Foyer, my GitHub home-page extension, as an unpacked download. This guide covers the store workflow I would follow to publish an extension. It doesn’t mean Repo Foyer has moved to the store.

We’ll use Page Notes, the extension from my Chrome extension tutorial, as the example. It stores notes about web pages. If you need to build the extension first, start there or with the free Browser Extensions course.

Fig 1 · 1/3A store release is a review workflow
A ZIP and the listing describe the extension and its requested access.

Test the release without your development setup

Before opening the developer dashboard, make a release folder containing only files the extension needs.

A development checkout can hide missing files. Your popup works because the stylesheet exists somewhere in the repository, but the release script forgets it. Or a bundled dependency downloads code from a server that Manifest V3 doesn’t allow.

Load the release folder through chrome://extensions using Developer mode → Load unpacked. Use a clean Chrome profile so old storage doesn’t fill in missing setup steps.

For Page Notes, I would test saving a note, closing the popup, reopening it, and returning to the page after restarting Chrome. I’d also try a page where injection isn’t permitted, such as a browser settings page, and check the extension shows a useful message.

Test without your editor’s development server running. A store user won’t have that server.

If the extension connects to an API, also check an expired token and a failed request. Those are ordinary user states. They should produce an explanation instead of a blank popup.

Register the developer account

Open the Chrome Web Store Developer Dashboard with the Google account that should own the extension.

Developer registration requires accepting the terms and paying a one-time registration fee. Check the amount shown during registration before paying. I’d avoid tying ownership to an email address I expect to stop using.

Complete the publisher profile and verify the contact email. Use an inbox you read, because review decisions and policy notices go there. A listing you published months ago can still need action later.

Google also requires 2-Step Verification before publishing or updating extensions. Set that up before planning a launch around a submission.

Registration creates a publisher account. It doesn’t approve your extension, and paying the registration fee doesn’t remove the review step.

Check the manifest and permissions

Your store package uses manifest.json just like the unpacked version. For a new extension, use Manifest V3.

Check the extension’s name and short description. Make sure the version is the release you intend to submit. For example:

{
  "manifest_version": 3,
  "name": "Page Notes",
  "version": "1.0.0",
  "description": "Save a private note for the page you are visiting."
}

This is the metadata portion of the manifest, not a replacement for the complete file. Keep your action, scripts, icons, and required permissions alongside it.

Read every permission as someone deciding whether to install the extension. Can you explain the feature that needs it?

For the Page Notes tutorial, the explanation would look like this:

PermissionWhy the extension needs it
storageSave and retrieve the user’s notes.
activeTabAccess the current page after the user invokes the extension.
scriptingRun the packaged content script that displays the note on that page.

Write justifications against your implementation. If your version never injects a content script, don’t copy the scripting permission or its explanation.

For a tool that runs when the user clicks its button, temporary activeTab access may fit better than requesting access to every website. A tool that must react automatically on a particular site has different needs. The permission warning guidelines explain those choices.

Remove unused permissions before packaging. Keeping broad access for a feature you may build later makes the current extension harder to justify.

Build a clean ZIP

The ZIP must contain manifest.json at its root. Uploading a ZIP with an extra enclosing project folder creates the wrong structure.

For the Page Notes tutorial’s files, create a new staging folder and copy the release files into it. Run this from the project’s root:

mkdir -p releases
EXTENSION_STAGE=$(mktemp -d /tmp/page-notes-store.XXXXXX)
cp manifest.json popup.html popup.css popup.js content.js service-worker.js "$EXTENSION_STAGE/"

If your manifest references icons, copy their directory too:

cp -R icons "$EXTENSION_STAGE/icons"

For a bundled extension, stage the build output instead of the source project. Include every file the manifest references, together with resources those files load.

Create the archive from inside the staging directory. Use a new output filename so an old ZIP can’t retain files you removed from this release:

STORE_ZIP="$PWD/releases/page-notes-1.0.0.zip"
(cd "$EXTENSION_STAGE" && zip -r "$STORE_ZIP" .)
unzip -l "$STORE_ZIP"

If that output file already exists, choose another fresh filename or remove the previous local artifact deliberately before packaging.

Inspect the file list. It should have manifest.json directly, followed by the extension’s runtime files. Keep .env files, account tokens, private signing keys, and browser profiles out of the archive.

Then extract that exact ZIP to a fresh folder and load it unpacked. This catches a missing file that testing the staging directory alone could miss. The preparation guide covers Google’s package requirements.

Check dependencies for remotely hosted code

Manifest V3 restricts remotely hosted executable code. Loading a JavaScript library from a CDN can work on a normal website and still be unsuitable in an extension.

Package the code the extension executes. Look at the output your bundler produces, including dynamic imports and worker files. A source file can look local while a dependency fetches more executable code at runtime.

Calling an API to obtain data is different from downloading JavaScript and executing it. A response containing note text is data. Treating a response as a program to evaluate introduces a different behavior and review problem.

Google’s remotely hosted code guide explains the policy and permitted cases. Don’t tick a declaration based only on code you wrote yourself. It covers the packaged dependencies too.

Create the item and listing

In the dashboard, choose Add new item and upload the ZIP. A valid package creates the item you can finish editing.

The store listing should describe the behavior a new user gets after installation. For Page Notes, I would start with:

Save a note for the page you’re visiting, and find it again when you return.

Then explain how to open it, where the note appears, and where notes are stored. If a service account or paid subscription is required, say so before someone installs it.

Prepare screenshots using demonstration notes. Avoid showing private URLs, email addresses, or tokens. A screenshot of the empty popup can show the interface, but a screenshot with a sample note explains why someone would use it.

The image requirements specify the current dimensions. Prepare a 128×128 store icon, a 440×280 small promotional tile, and clear screenshots in a supported size such as 1280×800. Use the dashboard to check the required assets before submission.

Link to a support page where someone can report a problem. It should remain useful after launch. For a small extension, that can be a page describing known limitations and how to contact you.

Keep the listing tied to the uploaded package. Planned features belong in your own development notes.

Write useful privacy disclosures

Local storage still counts as handling user data. Google’s user data FAQ explicitly covers information processed or stored only on the user’s device.

For Page Notes, I’d explain that the extension handles notes and page URLs, where it saves them, and whether any data leaves the browser. I’d also explain how users remove their notes.

Check the implementation before describing storage as local. chrome.storage.local and chrome.storage.sync have different behavior. An error reporter or analytics dependency can also send information even when the main feature works entirely in the browser.

Write the privacy policy around those facts. A short extension can have a short policy, but it still needs to explain its actual data handling. Host it at a stable public URL and link it in the dashboard.

For an extension using a token, document where the token is stored and which service receives it. Use a dedicated demonstration account for review. A production token with access to your private repositories should never appear in a screenshot or ZIP.

The Privacy practices fields ask for the single purpose, permission justifications, and data declarations. Fill them out from the finished code. Check that the policy and store copy agree with those answers.

A permission warning says what Chrome allows the extension to do. Your privacy explanation says what it uses that access for. Readers need both.

Give the reviewer a working test path

If the extension needs authentication or special setup, complete the dashboard’s test instructions.

I would write numbered steps that start with a fresh installation. Include a demonstration account when needed and explain which page to visit. State what the reviewer should see after each important action.

For Page Notes, a test path could be:

  1. Open a normal HTTPS page in a tab.
  2. Open the extension and save the text “Read this section again”.
  3. Close and reopen the popup, then confirm the note remains.
  4. Remove the note and confirm it disappears.

If your extension requires a particular website account, supply enough access to exercise the feature without exposing production data. Keep that test environment working while the submission is in review.

Instructions can’t compensate for a broken build. I would follow them myself in the clean profile before submitting.

Choose visibility and submit for review

The distribution settings let you choose who can find or access the extension. Use the option that fits your release, such as public discovery, an unlisted link, or a private test audience. An unlisted item is accessible through its URL; that isn’t a confidentiality control.

Choose your publication timing too. The submission flow supports publishing after approval or deferring publication so you can release it manually. Deferred approval has an expiry window, which the dashboard shows.

Then submit for review. Check your publisher email and the item’s status for requests or decisions.

Review time varies. Google’s review process describes both automated and manual checks. Broad permissions or hard-to-inspect code can require closer examination. Don’t promise users an exact release day before approval.

If a submission is rejected, read the specific reason and fix the package or disclosures it identifies. Uploading the same behavior under a new description doesn’t resolve a permission or data-handling problem.

Test the installed store version

After publication, install the extension from its store listing in a clean profile. Disable the development copy first so you know which installation you’re exercising.

Repeat the important workflow. Check setup, saved data, denied permissions, and a failed network request if the extension uses one. Also click the support and privacy links from the public listing.

For people moving from a side-loaded version, check how their data will move. An unpacked installation and the store installation can have different extension IDs, so don’t assume they share storage. Provide an export/import path if users have data worth keeping, or explain the manual setup they need.

That’s something I would settle before moving an extension like Repo Foyer to the store. Easier installation shouldn’t unexpectedly discard someone’s settings.

Publish updates without losing user data

Upload updates to the existing store item. Creating a new item creates a separate distribution instead of updating the installed extension.

Increase version in manifest.json, rebuild the complete ZIP, and use Upload New Package on the item’s Package tab. Include unchanged runtime files too. Then update any affected listing information and submit again. Google’s update guide describes this process.

Test the upgrade with existing stored notes, as well as a clean installation. If you change the data format, preserve older values until the migration has completed successfully. Avoid a startup routine that overwrites existing notes with defaults.

Adding a permission that triggers a warning can disable the extension until the user accepts it. Plan the explanation and test that upgrade path before adding access to a release.

Keep each submitted ZIP and its source revision. If an update breaks, the practical recovery is usually to fix the code, give it a higher version, and submit a new update. Re-uploading an older version number won’t roll the store back.

I would keep the publishing checklist next to the release script. When the next version is ready, it should identify the exact ZIP, the tested upgrade path, and any changes to permissions or data handling. Those details make the next submission much easier to check.

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about js: