#1100·gogcli

feat(forms): extend the persisted batch primitive to Forms (forms.batchUpdate is the only mutation endpoint, exposed one request at a time)

Author: sebsnykCreated Sep 9, 2026Updated Sep 9, 2026
Labelsissue-rating: 🌊 off-meta tidepoolclawsweeper:no-new-fix-prclawsweeper:needs-maintainer-reviewclawsweeper:needs-product-decisionP2impact:other

The gap

The Forms API has exactly one mutation endpoint: forms.batchUpdate, taking an ordered list of CreateItem / UpdateItem / MoveItem / DeleteItem / UpdateFormInfo / UpdateSettings requests.

gog forms exposes that endpoint one request at a time: add-question, delete-question, move-question, update, publish. Building a form is therefore one API call per question, and gog batch cannot help because it is Docs-only. gog batch begin accepts --doc and nothing else, and no gog forms command carries --batch.

The cost

Documented Forms quota is 150 write requests per minute per user per project (https://developers.google.com/workspace/forms/api/limits), so this is less likely to 429 than Docs, Sheets or Slides at 60. The argument here is ordering and atomicity rather than quota.

A generated form is the normal case for this command: a survey or an intake form built from a source file, thirty to sixty questions. Today that is thirty to sixty sequential calls, each of which can fail on its own, leaving a half-built form with no clean way back. A caller who wants a reproducible form has to write its own idempotence, because re-running the script duplicates every question rather than replacing it.

Index-based positioning makes the per-call shape worse than it looks. add-question inserts at an index, and every subsequent insert shifts the indices the caller computed. A script that builds thirty questions has to track index drift across thirty separate calls, where a single forms.batchUpdate applies the whole ordered list against one starting state.

What a caller has to do today

Compute indices defensively, re-read the form between calls, and accept that a mid-run failure leaves a partial form. Or drop to a Google API client library and post one batchUpdate, which is what the API was designed for.

Proposed surface

Same shape as the shipped Docs batch:

gog batch begin --form=<id> [--name=<label>]
gog forms add-question    --batch=<id> ...
gog forms delete-question --batch=<id> ...
gog forms move-question   --batch=<id> ...
gog forms update          --batch=<id> ...
gog batch end <id>

--dry-run, --continue-on-error and the revision lock behave as they do for Docs. forms.batchUpdate carries includeFormInResponse and a writeControl with requiredRevisionId, so the revision lock maps directly.

get, raw, responses and watch are reads or a different endpoint and stay out.

A batched path also opens a forms equivalent of building a whole form from a source file in one submission, which is the workload most callers actually want and cannot express today.