---
title: Cloud Rendering
description: "Render a composition on the hosted cloud — no local Chrome, no FFmpeg, no AWS to manage."
---
Render any Frames composition on Hanzo Cloud: the CLI zips your project, uploads it, runs the render there, and downloads the finished video. There's nothing to deploy and no Chrome or FFmpeg to install — the render is metered against your org's balance.
```bash
frames auth login # one-time sign-in
frames cloud render # zip → upload → render → download
```
```bash
# ◆ Zipping my-video
# 42 files · 3.1 MB
# ◆ Uploading the project
# asset_id: asst_abc123 · 1.2s
# Polling hfr_def456 every 10s …
# completed 47s
# ◆ Downloading to renders/hfr_def456.mp4
# 8.4 MB written
```
This is the zero-infra alternative to running your own renderer. If you'd rather own the compute, see [AWS Lambda](/deploy/aws-lambda), [GCP Cloud Run](/deploy/gcp-cloud-run), or the [Vercel, Cloudflare, or Modal templates](/guides/deploy). For local iteration during authoring, use [`frames render`](/guides/rendering).
## Authenticate
Cloud rendering uses the same credential as everything else: a Hanzo IAM token
in `HANZO_API_KEY`, read from [Hanzo KMS](https://kms.hanzo.ai). One token, every
surface — no separate sign-in, no credential file, no per-repo `.env`.
```bash
export HANZO_API_KEY=... # from Hanzo KMS; in CI, injected by the pipeline
frames auth status
# Shows which credential is in use, the identity behind it, and the balance.
# Exit 0 = usable; exit 1 = absent or rejected.
```
Point the CLI at a different deployment with `HANZO_BASE_URL` (default
`https://api.hanzo.ai`). A publishable `pk-` key is read-only and is refused;
use an IAM token or an `sk-` key. For what the same token unlocks across the
skills, see [Authentication](/guides/authentication).
## How a cloud render flows
`frames cloud render` runs the whole pipeline end-to-end:
```
Your machine Hanzo Cloud
┌─────────────────────────┐ ┌─────────────────────────────────┐
│ zip project │ ──PUT───▶│ direct-to-store asset upload │
│ (.framesignore + │ upload │ → asset_id │
│ generated outputs) │ │ │
│ │ ──POST──▶│ submit the render │
│ │ submit │ → render_id (queued) │
│ │ │ Chromium + FFmpeg render │
│ poll the render │ ◀────────│ queued → rendering → completed │
│ stream video to disk │ ◀────────│ signed video_url │
└─────────────────────────┘ └─────────────────────────────────┘
```
1. **Resolve the project** — a local directory (default `.`), or skip the upload with `--asset-id` / `--url`.
2. **Auto-detect the aspect ratio** from the entry HTML's `data-width`/`data-height` so you rarely set it by hand.
3. **Zip** the project (same ignore set as `frames publish`, including `.framesignore`).
4. **Upload** the zip through the direct-to-store asset flow, yielding an `asset_id`.
5. **Submit** the render, yielding a `render_id`.
6. **Poll** that render until it completes or fails (skip with `--no-wait`).
7. **Download** the signed video URL to disk.
## Control archive size
Hosted cloud project uploads are limited to 200 MB. Frames automatically excludes root-level `renders/` and `snapshots/` plus development-only paths such as `.git`, `node_modules`, `dist`, `.next`, `coverage`, and dotfiles.
Use a gitignore-style `.framesignore` at the project root for additional generated or intermediate files that are not needed at render time:
```gitignore
/snapshots2/
/exports/
/assets/source-master.mp4
```
Inspect the exact archive without authenticating, uploading, or starting a render:
```bash
frames cloud render . --dry-run
frames cloud render . --dry-run --json
```
The dry run reports compressed size, file count, and the ten largest included files. Rules also apply to `frames publish`. Avoid broad patterns such as `assets/`: dynamically selected media may not appear as an obvious static HTML reference.
## Render options
The most-used flags — see the [CLI reference](/packages/cli#frames-cloud) for the full list.
| Flag | Default | Meaning |
| --- | --- | --- |
| `--fps` | `30` | Frames per second, 1–240. |
| `--quality` | `standard` | `draft`, `standard`, or `high`. |
| `--format` | `mp4` | `mp4`, `webm`, or `mov` (webm/mov carry alpha). |
| `--resolution` | `1080p` | `1080p` or `4k`. 4k is billed at 1.5×. |
| `--aspect-ratio` | auto | `16:9`, `9:16`, or `1:1`. Auto-detected from a local project's `data-width`/`data-height`; for `--asset-id`/`--url` it defaults to `16:9` unless set. |
| `--composition` / `-c` | `index.html` | Entry HTML file inside the zip. |
| `--output` / `-o` | `renders/.` | Local destination for the download. |
| `--dry-run` | off | Build and inspect a local project zip without authenticating, uploading, or rendering. |
```bash
# Pick a composition and an output path.
frames cloud render . \
--composition compositions/intro.html \
--output ./renders/intro.mp4
# Higher quality at 60fps.
frames cloud render --quality high --fps 60
```
`--resolution 4k` can't be combined with `--format webm` or `--format mov`. The 4k supersampling path runs through the screenshot capture pipeline, which has no alpha channel. Render 4k as `mp4`, or render alpha at the composition's native resolution.
## Templates and variables
Cloud rendering supports [variables](/concepts/variables) — the same mechanism that powers templates everywhere else in Frames. Declare `data-composition-variables` on your composition, then fill them at render time:
```bash
# Inline JSON
frames cloud render --variables '{"title":"Q4 Recap","theme":"dark"}'
# From a file
frames cloud render --variables-file ./vars.json
# Fail fast on undeclared keys or wrong types
frames cloud render --variables '{"title":"Q4 Recap"}' --strict-variables
```
For a **local project**, the CLI validates your `--variables` against the composition's declared schema *before* uploading. For `--asset-id` / `--url` the schema lives server-side, so mismatches surface as a `frames_project_invalid` API error.
The idiomatic template workflow is **upload once, re-render many**: render a local project to get its `asset_id`, then submit new renders against that same asset with different variables — no re-zip, no re-upload.
```bash
# 1. Upload + render once; note the asset_id printed during upload.
frames cloud render ./card-template
# 2. Re-render the same asset with new values (skips zip + upload).
frames cloud render --asset-id asst_abc123 --variables '{"name":"Ada"}'
frames cloud render --asset-id asst_abc123 --variables '{"name":"Linus"}'
```
For high-volume personalized batches, the bring-your-own-AWS path adds a JSONL fan-out — see [Templates on Lambda](/deploy/templates-on-lambda).
## Fire-and-forget and webhooks
By default the CLI blocks, polls, and downloads. Pass `--no-wait` to submit and exit with just the `render_id`, and `--callback-url` to get an HTTPS webhook when the render terminates. The webhook fires whether or not the CLI is still polling, so combine them for true fire-and-forget:
```bash
frames cloud render --callback-url https://example.com/hf-hook --no-wait
# ✓ Submitted hfr_def456
# Poll with: frames cloud get hfr_def456
```
| Flag | Meaning |
| --- | --- |
| `--no-wait` | Submit and exit immediately; print the `render_id`. |
| `--callback-url` | HTTPS webhook fired when the render terminates. |
| `--callback-id` | Opaque tracking ID echoed in webhook payloads. |
| `--poll-interval` | Poll cadence in seconds (default `10`). |
| `--max-wait` | Max poll duration in minutes (default `60`). |
## Managing renders
```bash
frames cloud list # recent renders (--limit, --token, --all)
frames cloud get hfr_def456 # full detail + short-lived signed video_url
frames cloud delete hfr_def456 # soft-delete (--no-confirm to skip the prompt)
```
`video_url` and `thumbnail_url` are short-lived presigned URLs — re-fetch with `cloud get` rather than caching them.
## Safe retries
A retry is harmless for reads, but the zip upload is **not** idempotent on its own — a blind retry would create a duplicate asset and meter the org twice. Pass `--idempotency-key` so retries are safe:
```bash
frames cloud render . --idempotency-key "$(uuidgen)"
```
The key is forwarded to both the upload and submit calls; the server scopes idempotency per-endpoint, so reusing one value across both steps is safe. Use any opaque string in `[A-Za-z0-9_:.-]` (1–255 chars).
## Cloud vs. Lambda vs. local
- **`frames render`** (local) — fastest iteration loop; use while authoring. See [Rendering](/guides/rendering).
- **`frames cloud render`** — zero-infra; Hanzo Cloud runs the render, metered against your org's balance. Use when you don't want to manage Chrome/FFmpeg/AWS.
- **`frames lambda render`** — bring-your-own-AWS distributed rendering with chunked parallelism. Use when you've already invested in AWS. See [AWS Lambda](/deploy/aws-lambda).
## Next steps
Declare and fill template slots in a composition
High-volume personalized renders on your own AWS
Render locally or in Docker during authoring
Every `cloud` and `auth` flag in detail