Skip to content

Schema ​

Form YAML contract for gform. This is the page agents and humans should learn first when editing answers. Command UX (gform edit, --ask, editors) lives in commands/edit — keep that page for how to open/save, not for field tables.

gform verify, edit --ask Save, and run all enforce this shape. Unknown keys are rejected.

Every write from init / edit --ask save / restore stamps a short header that points here.

Top-level file ​

FieldRequiredNotes
nameyesLibrary id; must not be a reserved run subcommand (list, ls, view, v, last)
urlyesAbsolute Google Forms viewform URL
descriptionnoFree-text summary for humans / agents
tagsnoString labels (listing / filter later)
schemaVersionnoOptional integer when you break your own conventions
optionsnoDefaults applied when omitted
stepsyesAt least one step
yaml
# gform form — https://thegform.pages.dev/schema
# agents — https://thegform.pages.dev/agents
# …

name: my-form
url: https://docs.google.com/forms/d/e/…/viewform

options:
  headed: false
  variantMode: rotate

steps:
  - action: text
    question: Name
    value: Ada
  - action: submit

options (form config — not run --policy) ​

Run presets (safe / demo / load / watch) are CLI --policy only. Form YAML owns variants, unique rules, and starters.

KeyDefaultRole
headedfalseStarter for headed launch. Applies only when CLI --headed / headed --policy did not set it. Run intro shows source.
repeat(omit)Optional starter for -r. CLI -r / policy win when set.
variantModerotateHow step pools pick across repeats: rotate · random · zip (equal-length persona rows)
unique(omit){ scope: history|batch, fields: [question titles…] } — skip when those resolved answers already succeeded (independent of --once / -sd).
slowMo100Playwright slow-mo ms
timeoutMs30000Navigation / action timeout
localeen-USForms UI + browser locale (hl= + Playwright)
labelsengine defaultsOptional button label overrides: next / back / submit string arrays

Starter precedence (highest wins): explicit CLI → named --policy → form YAML → engine default. Never silent YAML overrides — see commands/run.

unique detail ​

yaml
options:
  unique:
    scope: history   # or batch
    fields: [Email, Name]
scopeBlocks when
historySame field values already succeeded in past runs or this batch
batchSame field values collide inside this --repeat wave only

Always enforced when set — separate from whole-payload --skip-duplicates / --once.

steps (discriminated by action) ​

Every step may include optional note (docs only — ignored by fill).

ActionFieldsNotes
textquestion, value, optional poolShort / paragraph / date-time-as-text
radioquestion, value, optional poolSingle choice, linear scale, star rating
selectquestion, value, optional poolDropdown
checkboxquestion, values[], optional pools[]Multi-select; pools = one pool per value slot
next / back—Multi-section navigation
submit—Final submit
filequestion, optional source (local|gdrive|remote), pool/urls, accept, maxBytes, messageAuto-upload when source set; else headed assist. Deep dive: file-uploads.
pauseoptional messageSoft-wall wait for the operator (TTY)
waitms (default 1000)Sleep between steps

File uploads ​

Use nested source: only — bare path: / url: on the file step are not accepted.

yaml
- action: file
  question: Resume
  source:
    type: local
    path: ./files/resume.pdf
  accept: [pdf]   # categories (presentation) or exts (pptx / .pptx)
  maxBytes: 10485760
yaml
- action: file
  question: Signed PDF
  source:
    type: gdrive
    # Support both — id wins if both set; link alone is parsed
    id: 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms
    link: https://drive.google.com/file/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/view
  accept: [pdf]
yaml
- action: file
  question: Photo
  source:
    type: remote
    url: https://example.com/avatar.png
  accept: [image]

Omit source for assist-only (operator uploads in the headed browser — headless fails with assist-headed-required). Drive / My Drive picks are detected via the Forms Selected files list (data-id chip). When possible, gform fetches bytes with the signed-in session into artifacts/<id>/content/; otherwise metadata-only. Configured sources always copy into content/ on success. Status on gform run view: uploaded / assisted / skipped.

Full behavior map (decide logs, pools, chip detect, headed cheat sheet): file-uploads.

Pools ​

yaml
- action: radio
  question: "Age range?"
  value: "18-25"
  pool: ["18-25", "26-35", "36-45"]
yaml
- action: checkbox
  question: "Skills"
  values: ["Python"]
  pools:
    - ["Python", "Go", "Rust"]
  • pool — single-answer steps (text / radio / select / file paths)
  • urls — file remote URL pool (same variant rules)
  • pools — checkbox only; length should match how you vary each slot across repeats
  • Without pools, --repeat + --skip-duplicates mostly skips (same fingerprint)

See repeat.

Variant modes ​

ModeBehavior
rotateDeterministic: run index i → pool[i % length]
randomPick randomly each run
zipShared index: every pooled field takes slot i (name[i] with email[i]). Needs ≥1 pool and equal lengths — mismatch → hard error on verify / load / run.
yaml
options:
  variantMode: zip

steps:
  - action: text
    question: Name
    value: Ada
    pool: [Ada, Grace, Alan]
  - action: text
    question: Email
    value: ada@example.com
    pool: [ada@example.com, grace@example.com, alan@example.com]

-r 3 → three personas. Preview rows with gform view. Agents must ask before assuming zip vs rotate/random. Optional later sugar: top-level rows: / personas: (expands to the same pools — not shipped).

How validation runs ​

SurfaceWhen
gform verify <form>Explicit Zod check
gform verify <form> --checkLive inspect → safe YAML sync (accept + new stubs); -A / -y with --check only
edit --ask → Save / PreviewZod before write; Preview shows validity
gform runLoads via the same schema

Fail closed on unknown keys and bad types — fix YAML, do not invent fields. variantMode: zip with unequal pool lengths fails verify / run (Zod still loads so gform view can diagnose).