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:
| # | v1 | v2 |
|---|---|---|
| 1 | www.googleapis.com | chromewebstore.googleapis.com |
| 2 | bare itemId | publishers/PUBLISHER_ID/items/ITEM_ID |
| 3 | PUT /chromewebstore/v1.1/items/ID | POST /upload/v2/NAME:upload |
| 4 | GET ...?projection=DRAFT | GET /v2/NAME:fetchStatus |
| 5 | query param publishTarget | request body publishType: DEFAULT_PUBLISH or STAGED_PUBLISH |
| 6 | OAuth scope is unchanged — both use .../auth/chromewebstore | |
| 7 | items.insert | gone — creating an item is now manual |
| 8 | visibility / trusted testers | gone — now confirmed in the dashboard |
| 9 | OAuth client + refresh token | service 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.