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.
Destructive commands still require confirmation
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.
Pass IDs explicitly in CI, even when webflow.json has them
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.
Pass --mount and --environment together, every time
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.
Both need a GitHub-connected app
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.