Rehoboth Builds

Chrome Web Store API v1 to v2 — what actually changes

The v1 Publish API stops working on 15 October 2026. We moved a working release pipeline across in August. This is what we found, including two failures that print a success line.

31 August 2026

The deadline is real and it is on the v1 page

The banner at the top of the v1 reference says, verbatim:

The Chrome Web Store API (V1) is deprecated and will only be supported until 15th October 2026.

There is no migrate-to-api-v2 page. The migration information is spread across the announcement blog post, the using the API guide, and the v2 REST reference, and none of the three is a checklist. Hence this page.

The nine changes

If your script only uploads a package and publishes it — which is what almost every CI setup does — the migration is mechanical:

#v1v2
1www.googleapis.comchromewebstore.googleapis.com
2bare itemIdpublishers/PUBLISHER_ID/items/ITEM_ID
3PUT /chromewebstore/v1.1/items/IDPOST /upload/v2/NAME:upload
4GET ...?projection=DRAFTGET /v2/NAME:fetchStatus
5query param publishTargetrequest body publishType: DEFAULT_PUBLISH or STAGED_PUBLISH
6OAuth scope is unchanged — both use .../auth/chromewebstore
7items.insertgone — creating an item is now manual
8visibility / trusted testersgone — now confirmed in the dashboard
9OAuth client + refresh tokenservice account, one per publisher

Number 4 is worth a second look. The v1 call took a projection query parameter to choose between the draft and the published revision. v2 has no projection: one fetchStatus call returns both, as publishedItemRevisionStatus and submittedItemRevisionStatus, each with its own state and its own per-channel deploy percentages. If your old code made two calls to compare draft against published, it becomes one.

The response also carries lastAsyncUploadState, takenDown and warned. Those last two are worth surfacing in whatever your CI prints: an item that has been taken down or flagged will otherwise look healthy right up until a publish fails.

Two failures that print a success line

These are the ones that cost us time, and neither is in the documentation.

The upload state is SUCCEEDED, not SUCCESS

Our pipeline checked the upload response against an allow-list copied from v1-era code. v2 returns uploadState: "SUCCEEDED". The allow-list said "SUCCESS".

The result was worse than a plain error: the last line on stdout was a green uploaded, uploadState=SUCCEEDED, and the rejection went to stderr. Two release runs looked like they had uploaded and then quietly done nothing. If you are porting a v1 script, check every string you compare a response field against, not just the URLs.

There is a design lesson underneath it. The step after upload is publish, and publish will itself reject a bad upload. A guard placed in front of a step that already validates should warn and continue on an unrecognised value, not stop. Stopping there can only ever block a legitimate release.

A service account is one per publisher, and needs no role

Create the service account in Google Cloud and grant it no IAM role at all — the authorisation that matters is adding its email address in the Chrome Web Store dashboard, under the publisher settings, not in Cloud IAM. Granting roles because it feels incomplete does nothing except widen the credential.

A publisher can have exactly one service account. If you publish under two publisher accounts you need two.

What v2 cannot do — and v1 could not either

The resource has five methods and that is the whole surface: media.upload, publishers.items.publish, .fetchStatus, .setPublishedDeployPercentage, .cancelSubmission.

None of them touch the store listing. The title, the description, the screenshots, the category, the support URLs, and the entire privacy and data-use declaration are dashboard-only, and always have been. This surprises people who assume the removal of items.insert is what blocks listing automation; it is not. Listing text was never in the API.

The practical shape of that: creating a product and filling in its listing is permanently manual. Everything after that — upload, submit, publish, staged rollout, cancel — can be fully automated. Plan your pipeline around that line and you will not fight it.

What v2 adds

cancelSubmission pulls a submission back out of review. setPublishedDeployPercentage gives you staged rollout.

The publish body gained three optional fields beyond publishType: deployInfos[] for the initial rollout percentage; blockOnWarnings, which makes the request fail if validation raises any warning and puts the details in error.details; and skipReview, described as an attempt — the API validates whether the item qualifies and returns a validation error if it does not. The reference does not enumerate what qualifies, so treat it as a request rather than a guarantee.

One small thing that saves a lookup: PUBLISH_TYPE_UNSPECIFIED behaves the same as DEFAULT_PUBLISH, so omitting publishType publishes on approval.

What the documentation still does not say

We could not find official answers to these, and we would rather say so than guess: the rate limits and quotas for v2; whether a read-only scope is still offered; and whether STAGED_PUBLISH is subject to the same rule that an approved but unpublished item falls back to draft after thirty days.

One thing worth doing while you are in there

Make the package byte-for-byte deterministic — fixed timestamps, sorted entries. Two builds from the same commit should produce the same hash. It costs a few lines and it converts the store has what we wrote from a belief into something you can check.


We are Rehoboth Builds. We publish browser extensions and we run this pipeline for our own releases — Custom Keyboard Shortcuts ships through it. If something here is wrong, tell us and we will fix the page.