> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# blobhub workflow deploy

> Compile, upload only the definitions that changed, then check what was uploaded

`deploy` is the command you run day to day: it compiles every bound component, uploads only the definitions
whose document actually changed, and runs the platform's own `check_definition` against each workflow definition
it uploaded. Re-running it with nothing changed uploads nothing and says so.

It is [`build`](/cli/workflow/commands/build) plus [`push`](/cli/workflow/commands/push) plus
[`check`](/cli/workflow/commands/check), with one difference that matters at each step: it refreshes the import
allowlist live, it skips definitions that would upload identically, and it checks after every upload has
finished rather than between them.

## Synopsis

```bash theme={null}
blobhub workflow deploy -f <manifest> [--definition <alias>] [--force] [--strict-imports] [--dry-run]
```

| Flag               | Default                          | Description                                 |
| ------------------ | -------------------------------- | ------------------------------------------- |
| `--file`, `-f`     | required                         | Path to the workflow manifest.              |
| `--definition`     | every definition in the manifest | Limit to this alias. Repeatable.            |
| `--force`          | off                              | Overwrite code edited outside the CLI.      |
| `--strict-imports` | off                              | Treat an unverifiable import as fatal.      |
| `--dry-run`        | off                              | Report the intended calls and perform none. |

## What it does

1. Resolves the blob and revision. This is a write: status `ready`, phase `draft` or `managed`.
2. **Refreshes the import allowlist live.** Reading it needs an `admin`-scoped key. On success the cache under
   `~/.blobhub/cache/allowlist/` is rewritten, so a later `build` verifies against something current. On a `403`
   — which is what the `read`/`write` keys the platform recommends for automation get — it falls back to that
   cache, and with no cache either it raises `ALLOWLIST_UNREADABLE` and degrades exactly as `build` does.
3. Lists the revision's definitions in both categories and matches the manifest against them.
4. For each entry: compiles every bound component into an in-memory copy of the document, applying the same
   remote-edit gate, `--force` and `--strict-imports` as `build`.
5. Compares that document against the remote one. **Equal means skipped** — no upload, and the local file is not
   rewritten either.
6. Otherwise writes the built document to `source`, creates the definition if its alias is absent, and uploads.
7. Once every entry above is done, and not before, runs `check_definition` against each **workflow**-category
   definition it uploaded.

```text theme={null}
created workflow/checkout_flow
deployed workflow/checkout_flow
unchanged pricing_flow
skipped check_definition for playground playground/checkout_lab -- it would check against the wrong manifest
check passed: workflow/checkout_flow
```

The `unchanged` line names a bare alias where every other line names `category/alias`; on a revision where one
alias legitimately names both a workflow and a playground definition, read it together with the lines above it.

Under `--json` the payload carries `created`, `uploaded`, `unchanged`, `planned`, `skipped_check`, `checked`,
`dry_run` and `drift`, alongside the envelope's `schema_version`. `checked` holds the checker's own events per
definition, not just a status.

## Uploads and checks are two passes, on purpose

Every changed definition is uploaded before the first check runs. Compilation and upload do interleave — each
definition is compiled and uploaded before the next one is read — but no check runs until the last upload has
finished. The obvious alternative, checking each definition right after uploading it, is worse, and the reason
is that **`deploy` cannot roll back an upload**.

Stopping at the first failing check would leave one definition on its new code and its sibling still on the old
one: a half-deployed revision. And it would leave that behind on top of an incomplete report, because the checks
for everything after the failure never ran. Finishing every upload first means a failing check is always
reported against a fully applied deploy.

A `CHECK_FAILED` from `deploy` therefore says *the upload happened and the result does not pass*, never *the
upload was refused*. The definition is on the revision. Fix it and deploy again.

Within the check pass itself, `deploy` reports the **first** failure and stops. To see every failing definition
at once, run [`blobhub workflow check`](/cli/workflow/commands/check), which collects them all.

## Playgrounds are not checked

`check_definition` loads the **workflow** manifest unconditionally, so running it against a playground definition
checks it against the wrong manifest and produces meaningless findings. `deploy` skips it and says so on its own
output line rather than silently. A playground is still uploaded exactly like a workflow definition; only the
check is skipped.

## What counts as unchanged

The comparison is the full serialized document, local against remote — not a hash of the code, not a timestamp.
A definition is skipped only when everything about it matches: components, connections, ports, the compiled
`content[]`, and the attributes the compiler stamps.

Two consequences worth knowing:

* **An unchanged definition leaves its local file untouched.** `deploy` writes `source` only for the definitions
  it uploads. In the ordinary case the file on disk already equals the built document, so there is nothing to
  write; if it does not, run `build` to bring the file itself up to date.
* **Someone else's browser edit does not stop the upload.** The remote-edit gate reads the code port in the
  *local* definition file, which is all `build` has to work with. A change made in the visual editor and never
  pulled is simply a document difference, so `deploy` uploads over it. On a revision other people edit in the
  browser, run [`diff`](/cli/workflow/commands/diff) first — it is the only command that reports the remote's
  state.

## `--dry-run`

Reports the intended calls without compiling, uploading, or writing anything — including the allowlist cache,
which a dry run reads offline rather than refreshing:

```text theme={null}
would create and upload workflow/checkout_flow
would upload playground/checkout_lab
```

The plan comes from presence alone. Nothing is compiled, so a dry run cannot tell you which definitions would
turn out unchanged, and cannot surface a compile error. [`diff`](/cli/workflow/commands/diff) answers both of
those; `--dry-run` answers which calls would be made.

## Errors

| Code                                                                                                                                                                                                               | When                                                                                                                                                      |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MANIFEST_NOT_FOUND`, `MANIFEST_INVALID`, `MANIFEST_TYPE_MISMATCH`, `MANIFEST_VERSION_UNSUPPORTED`                                                                                                                 | The manifest is absent or unusable — including an entry whose `source` file does not exist.                                                               |
| `DEFINITION_ALIAS_INVALID`, `DEFINITION_CATEGORY_UNKNOWN`, `DEFINITION_NOT_ACCESSIBLE`                                                                                                                             | An alias fails the pattern; a category cannot be inferred or contradicts the document; a `--definition` alias is not in the manifest.                     |
| `BLOB_NOT_ACCESSIBLE`, `BLOB_NOT_WORKFLOW`, `REVISION_NOT_ACCESSIBLE`, `REVISION_NOT_WRITABLE`                                                                                                                     | The target cannot be resolved, or the revision is not `ready` in phase `draft`/`managed`.                                                                 |
| `COMPONENT_NOT_FOUND`, `COMPONENT_NAME_AMBIGUOUS`, `COMPONENT_PORT_MISSING`, `LANGUAGE_NOT_PYTHON`                                                                                                                 | A binding does not resolve to exactly one component with the port it needs, in Python.                                                                    |
| `REMOTE_EDIT`, `UNMANAGED_CODE`                                                                                                                                                                                    | The local code port was edited since the last build, or never built by this CLI. `--force`, or reconcile with `blobhub workflow eject`.                   |
| `ENTRY_POINT_NOT_FOUND`, `BASE_PATH_ESCAPE`, `IMPORT_UNAVAILABLE`, `IMPORT_NAME_NOT_FOUND`, `IMPORT_CYCLE`, `SYMBOL_COLLISION`, `SANDBOX_NAME_SHADOWED`, `LOCAL_MODULE_IMPORT_FORM`, `UNSUPPORTED_IMPORT_FORM`     | The compiler refused the package. Definitions are compiled and uploaded one at a time, so entries earlier in the manifest may already be on the revision. |
| `ALIAS_IN_USE`, `LIMIT_EXCEEDED`                                                                                                                                                                                   | The create was refused: the alias is taken, or the revision is at its per-category cap.                                                                   |
| `PLAYGROUND_LAYOUT_IMMUTABLE`, `PLAYGROUND_POSITION_INVALID`                                                                                                                                                       | The playground upload was refused over its `layout` or a component's grid position.                                                                       |
| `CHECK_FAILED`                                                                                                                                                                                                     | A `check_definition` returned body `status: "failure"`. Every upload already happened.                                                                    |
| `FORMAT_UNSUPPORTED`, `FORMAT_PARSE_ERROR`                                                                                                                                                                         | A `source` or `value.source` has an unsupported extension, or does not parse.                                                                             |
| `CREDENTIALS_NOT_FOUND`, `PROFILE_NOT_FOUND`, `INSECURE_CREDENTIALS_PERMISSIONS`                                                                                                                                   | No usable credential, or a credentials file the CLI refuses to read.                                                                                      |
| `API_RATE_LIMITED`, `API_TRANSIENT_ERROR`, `API_NETWORK_ERROR`                                                                                                                                                     | A call failed after its retries.                                                                                                                          |
| `DEFINITION_SIZE_NEAR_LIMIT`, `DEFINITION_LIST_TRUNCATED`, `ALLOWLIST_UNREADABLE`, `ALLOWLIST_STALE`, `ALLOWLIST_ENTRY_UNUSABLE`, `ALLOWLIST_SHADOWS_SANDBOX`, `IMPORT_UNVERIFIED`, `LOCAL_SHADOWS_SANDBOX_MODULE` | Advisories, on stderr. The exit code stays `0`.                                                                                                           |

`IMPORT_UNAVAILABLE` reads differently here than under `build`: `deploy` holds the live allowlist, so its message
appends what that allowlist currently permits. `DEFINITION_SIZE_NEAR_LIMIT` fires at 8 MB — 80% of the
platform's 10 MB payload ceiling — so a definition that is growing tells you before an upload starts failing at
the gateway.

Every code above, with its remediation, is in [Error codes](/cli/error-codes).

## See also

* [`blobhub workflow diff`](/cli/workflow/commands/diff) — the same comparison, reported and never performed.
* [`blobhub workflow build`](/cli/workflow/commands/build) — the compile step alone, offline.
* [`blobhub workflow check`](/cli/workflow/commands/check) — every failing definition, where `deploy` reports the
  first.
* [Check Definition](/blob-types/workflow/operations/check-definition) — the operation behind the check pass.
* [Workflow overview](/cli/workflow/overview) — where `deploy` sits among the ten commands.
