Agents & CI/CD
Agents & CI/CD
Drive the Webflow CLI from AI agents, scripts, and CI/CD pipelines without a terminal.
The Webflow CLI is built to run unattended — from an AI coding agent’s subprocess, a shell script, or a CI/CD job. This guide covers the non-interactive contract every command follows: which flags to pass, how errors and exit codes behave, and how to configure and operate a Webflow Cloud app end to end. For the full flag reference of every command, see the command reference.
apps commands need the @next channel
The webflow apps namespace used throughout this guide ships only on the CLI’s
pre-release channel. Install it explicitly — on the stable channel these commands do not
exist and fail with an unknown-command error:
A pre-release install reports a version with a -next. suffix (webflow --version). The
stable channel covers webflow cloud init and webflow cloud deploy only; there is no
stable equivalent for the management commands.
Why this matters
The CLI’s interactive prompts (picking a site, naming an app, confirming a delete) only fire when the process has an attached TTY. An agent invoking the CLI through a subprocess — or a CI runner — has no TTY, so every prompt is silently skipped. If a required value wasn’t also passed as a flag, the command doesn’t hang waiting for input: it fails immediately with an error naming the missing flag.
The rule for every command: pass every required value explicitly as a flag, and add --no-input to make that contract explicit.
--no-input is also auto-detected
You don’t need to pass --no-input yourself. The CLI enables it automatically when CI=true, when stdout or
stdin isn’t a TTY, or when it detects a coding agent’s execution environment — which covers most agent and CI runners
already. Passing it explicitly is still good practice: it documents intent and behaves the same in every environment,
including a developer’s terminal.
If the detection is wrong for your setup — an interactive terminal that trips one of those checks — pass --input to
force prompts back on. An explicit --no-input always wins over --input.
Reading errors from a script
Exit codes are semantic and safe to branch on:
When a required ID can’t be resolved non-interactively, the CLI fails fast with a single-line message naming the exact flag, environment variable, or webflow.json key to set, for example:
This message format is stable and safe to grep for in CI logs. The CLI doesn’t currently wrap it in a JSON envelope, though — even with --json set, error text stays plain, human-readable text on stderr. --json only changes success output (see below) and suppresses incidental info lines like Using appId from webflow.json. Treat the exit code, not stdout/stderr parsing, as the authoritative pass/fail signal; reserve message-text matching for surfacing the reason to a human or a log.
apps delete refuses to run under --no-input (or --json) unless you also pass --yes — it never silently deletes
anything. Preview any destructive or mutating command with --dry-run first; every write command (apps update,
apps delete, apps env-vars set/delete/import) supports it and makes zero API calls.
ID resolution
Commands that operate on a specific app, environment, site, or workspace resolve that ID the same way, in strict priority order:
- An explicit flag (e.g.
--app-id,--site-id) — always wins. - An environment variable.
webflow.jsonin the current directory (or--manifest <path>).- An interactive prompt — skipped entirely under
--no-input, in CI, or without a TTY. If exactly one candidate exists (a workspace with a single app, an app with a single environment), the CLI selects it automatically, even non-interactively. Only an ambiguous choice — several candidates, none specified — produces the missing-flag error above.
WEBFLOW_API_TOKEN is the one exception — it’s read-only from the environment (or .env) and never resolved from webflow.json. See Authentication for how it’s issued.
Environment variables here are read-only inputs — the CLI never writes WEBFLOW_APP_ID or the others back into your .env; only apps init / apps deploy write the equivalent keys into webflow.json. See Configuration for the full webflow.json schema.
Flags always override the manifest, which is useful for recovering from a half-written webflow.json or targeting a
different environment for one run without editing a file. In a CI job, prefer --site-id / --app-id /
--environment-id flags sourced from secrets over relying solely on a committed webflow.json.
Your first deploy
A Webflow Cloud app deploys one of two ways, chosen once at setup:
- Site-attached — the app is bound to an existing Webflow site and served from a mount path on that site’s domain (e.g.
/app). Pass--site-id. - Project app — a standalone app with no existing site. The first deploy provisions a new Webflow site automatically. Pass
--newat init and--workspace-idfor the first deploy.
The deploy prompts (pick an app, name a new app, pick an environment) are gated on whether --mount and
--environment are both set — not on --no-input. Passing --no-input without both still triggers the prompt and
hangs in a non-TTY context.
Discovering a site ID
Site IDs aren’t secret, but an agent still needs a way to find one without a human copying it from the dashboard:
Parse the JSON array and match on displayName to present a human-readable choice, rather than asking a user to type a raw ID.
Discovering a workspace ID
Workspace IDs aren’t surfaced anywhere in the Webflow dashboard UI — there’s no page to copy one from. The only reliable way to discover one is running an interactive command locally, once:
Run without --no-input and without --workspace-id, the preflight prompt walks a human through creating a new app and picking a workspace, then writes cloud.workspace_id into webflow.json. From that point on, an agent can read the value back out of the manifest and pass it as --workspace-id on every subsequent run.
Continuous deployment
Once an app deploys once, there are two ways to keep it deploying automatically:
GitHub-linked deployment (recommended)
A one-time, dashboard-only setup — the CLI can’t perform it, and pushing to GitHub alone doesn’t enable it:
From that point on, every git push to the connected branch triggers a deploy — no CLI invocation, no workflow file to maintain.
GitHub Actions
Use this when you need custom build steps, per-environment secrets, or deploy gates the native Git integration doesn’t cover:
Wait for the deploy to finish, then confirm it succeeded with apps deployments get --wait. It polls until the deployment reaches a terminal status and exits with a status-derived code — useful as a gate before a downstream step like smoke tests or a Slack notification:
The --json output is an envelope, { "deployments": [...], "nextCursor": ... } — not a
bare array — so the id is at .deployments[0].id.
Operating apps
Once an app is deployed, the apps namespace covers day-to-day operations without leaving a script. Every command below supports --json, resolves its app/environment per ID resolution, and is documented in full — every flag, default, and example — in the command reference.
Inspecting apps
Deployments
A deployment’s status is one of starting, building, deploying (in-flight) or success, failed, canceled, unstaged (terminal). --wait polls until a terminal status, then exits 0 on success and 1 on anything else. Note that a poll timeout also exits 1, so a step that needs to tell a failure from a still-running build has to read the status rather than gate on the exit code alone.
redeploy re-runs an existing deployment at the same commit; redeploying an earlier one is how you roll back. trigger takes no deployment ID and builds the environment’s configured branch at its current HEAD. Pass --idempotency-key on either so a retried CI step acknowledges the first enqueue instead of queueing a second build.
redeploy and trigger build from a connected repository, so an app created by deploying
local files with apps deploy is not eligible. Connect one with
apps init --import or
apps update --github-source.
Logs
Both commands paginate; when a page has more results, the output includes a nextCursor to pass back via --cursor.
Environment variables
Secret values are masked wherever they’re read back — in the table view and in --json output alike — and env-vars import never echoes plaintext values, even on success. To set a secret without it ever touching shell history or the process list, pipe the value via stdin instead of passing it as an argument:
Updating and deleting apps
--dry-run validates inputs (including auth) and prints exactly what would change, without calling the API. Always preview a delete before running it for real — especially when the target ID came from an agent’s own reasoning rather than a human.
Deprecated cloud aliases
webflow cloud init and webflow cloud deploy still work and share identical options with apps init / apps deploy, but are deprecated in favor of the apps namespace. New scripts and CI pipelines should use apps directly. webflow cloud list is also deprecated — it lists scaffold templates for init, not your Cloud apps, so use apps list to list apps and apps init --framework for templates. See the command reference for the full deprecated alias list.