> This page is for Webflow CLI.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.webflow.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.webflow.com/_mcp/server.

# Agents & CI/CD

> Run the Webflow CLI non-interactively from AI agents, scripts, and CI/CD pipelines — required flags, machine-readable errors, ID resolution, and Webflow Cloud deployment workflows.

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](/webflow-cloud) app end to end. For the full flag reference of every command, see the [command reference](/cli/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:
>
> ```bash
> npm install -g @webflow/webflow-cli@next
> ```
>
> 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.

```bash
webflow apps deploy --no-input --site-id site_abc123 --mount /app --environment production
```

> **\`--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:

| Code | Meaning                                                                                                                                                                                                          |
| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0`  | Success — including an empty result set (an empty list isn't an error).                                                                                                                                          |
| `1`  | An operational error: auth failure, API error, invalid input, or a required value that couldn't be resolved. Also a poll timeout under [`deployments get --wait`](/cli/command-reference#apps-deployments-wait). |

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:

```
Missing required appId. Pass --app-id, set WEBFLOW_APP_ID, or add it to webflow.json.
```

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 \[#resolver]

Commands that operate on a specific app, environment, site, or workspace resolve that ID the same way, in strict priority order:

1. **An explicit flag** (e.g. `--app-id`, `--site-id`) — always wins.
2. **An environment variable.**
3. **`webflow.json`** in the current directory (or `--manifest <path>`).
4. **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.

| Resource    | Flag                   | Environment variable         | `webflow.json` key                                           |
| ----------- | ---------------------- | ---------------------------- | ------------------------------------------------------------ |
| Site        | `--site` / `--site-id` | `WEBFLOW_SITE_ID`            | `siteId`                                                     |
| App         | `--app-id`             | `WEBFLOW_APP_ID`             | `cloud.app_id` (falls back to the legacy `cloud.project_id`) |
| Environment | `--environment-id`     | `WEBFLOW_APP_ENVIRONMENT_ID` | `cloud.environment_id`                                       |
| Workspace   | `--workspace-id`       | `WEBFLOW_WORKSPACE_ID`       | `cloud.workspace_id`                                         |

`WEBFLOW_API_TOKEN` is the one exception — it's read-only from the environment (or `.env`) and never resolved from `webflow.json`. See [Authentication](/cli/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](/cli/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 \[#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 `--new` at init and `--workspace-id` for the first deploy.

```bash
# Site-attached: connect to an existing site
webflow apps init --no-input \
  --app-name my-app \
  --framework astro \
  --mount /app \
  --site-id site_abc123

webflow apps deploy --no-input \
  --site-id site_abc123 \
  --mount /app \
  --environment production \
  --skip-mount-path-check
```

```bash
# Project app: no existing site
webflow apps init --new --no-input \
  --app-name my-app \
  --framework nextjs \
  --workspace-id ws_abc123

webflow apps deploy --no-input \
  --workspace-id ws_abc123 \
  --app-name my-app \
  --mount / \
  --environment production
```

> **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:

```bash
webflow sites list --json
```

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:

```bash
webflow apps deploy
```

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:

### Push the app to GitHub

The repository needs at least one commit pushed to a remote.

### Connect the repository in the Webflow dashboard

Open your Cloud app in the Webflow dashboard, go to **Settings → Git**, connect your GitHub account if needed, then select the repository and the branch to deploy from.

### Confirm the connection

The dashboard runs one initial deploy automatically to verify the wiring.

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:

**`.github/workflows/deploy.yml`**

```yaml title=".github/workflows/deploy.yml"
name: Deploy to Webflow Cloud

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22

      # `apps` commands need the pre-release channel. Pin an exact version in
      # CI — the `next` tag moves. Use `webflow cloud deploy` with `@latest` to
      # stay on the stable channel.
      - name: Install Webflow CLI
        run: npm install -g @webflow/webflow-cli@next

      - name: Deploy
        run: |
          webflow apps deploy \
            --no-input \
            --site-id "$WEBFLOW_SITE_ID" \
            --mount /app \
            --environment production \
            --skip-mount-path-check
        env:
          WEBFLOW_API_TOKEN: ${{ secrets.WEBFLOW_API_TOKEN }}
          WEBFLOW_SITE_ID: ${{ secrets.WEBFLOW_SITE_ID }}
```

Wait for the deploy to finish, then confirm it succeeded with [`apps deployments get --wait`](/cli/command-reference#apps-deployments-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:

```bash
DEP_ID=$(webflow apps deployments list --limit 1 --json | jq -r '.deployments[0].id')
webflow apps deployments get "$DEP_ID" --wait --timeout 300
```

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](#resolver), and is documented in full — every flag, default, and example — in the [command reference](/cli/command-reference#apps).

### Inspecting apps

```bash
webflow apps list --json                       # all apps in the workspace
webflow apps get app_abc123 --json             # a single app's details
webflow apps domains app_abc123 --json         # live URLs and custom domains
webflow apps environments list app_abc123 --json
```

### Deployments

```bash
webflow apps deployments list --limit 10 --json
webflow apps deployments get dep_abc123 --json
webflow apps deployments get dep_abc123 --wait --interval 10 --timeout 300
webflow apps deployments list --status failed --limit 1 --json
webflow apps deployments redeploy dep_abc123 --idempotency-key "$GITHUB_RUN_ID"
webflow apps deployments trigger --idempotency-key "$GITHUB_RUN_ID"
```

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`](/cli/command-reference#apps-deployments-redeploy) re-runs an existing deployment at the same commit; redeploying an earlier one is how you roll back. [`trigger`](/cli/command-reference#apps-deployments-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`](/cli/command-reference#apps-init-import) or
> [`apps update --github-source`](/cli/command-reference#apps-update).

### Logs

```bash
webflow apps deployments list --limit 1 --json    # get a depId first
webflow apps logs build dep_abc123 --since 2026-07-01T00:00:00Z --q "error" --json
webflow apps logs runtime env_abc123 --limit 200 --json
```

Both commands paginate; when a page has more results, the output includes a `nextCursor` to pass back via `--cursor`.

### Environment variables

```bash
webflow apps env-vars list --app-id app_abc123 --environment-id env_abc123 --json
webflow apps env-vars set NODE_ENV production --app-id app_abc123 --environment-id env_abc123
webflow apps env-vars delete OLD_KEY --app-id app_abc123 --environment-id env_abc123
webflow apps env-vars import .env.production --app-id app_abc123 --environment-id env_abc123 --secret
```

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:

```bash
printf '%s' "$STRIPE_KEY" | webflow apps env-vars set STRIPE_KEY --secret \
  --app-id app_abc123 --environment-id env_abc123
```

### Updating and deleting apps

```bash
webflow apps update app_abc123 --description "Marketing site backend" --dry-run
webflow apps delete app_abc123 --dry-run   # preview — makes zero API calls
webflow apps delete app_abc123 --yes --no-input
```

`--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`](/cli/command-reference#apps-list) to list apps and `apps init --framework` for templates. See the [command reference](/cli/command-reference#webflow-cloud) for the full deprecated alias list.