# Cloud rendering

> Render a project on framefields GPUs from git, by name, from the CLI, code or the dashboard, and split long films across several GPUs.

framefields renders your project on its own GPUs, straight from git: no GPU, Docker or render farm on your side. Name a render once in `framefields.json`, then start it from the terminal, from code or from the [dashboard](https://framefields.dev/dashboard/renders). Long films split across several GPUs and come back as one file.

```bash
npx framefields render film --cloud --plan   # Plan: 4 workers, 1 h 50 min of render work, ready in about 35 min, estimated $1.20.
npx framefields render film --cloud          # Done in 34 min: output/film.mp4 (412.3 MB), 6620 GPU seconds, $1.18.
```

## Before you render

- **Push.** The cloud renders the **pushed commit**. Uncommitted changes are not rendered (the CLI warns), and a commit that isn't on the remote is refused.
- **An API key.** Renders bill the account behind `FRAMEFIELDS_API_KEY`, the same key as the [AI gateway](/docs/guides/ai/). Create one at [framefields.dev/dashboard/keys](https://framefields.dev/dashboard/keys) and add credit under [Billing](https://framefields.dev/dashboard/billing).
- **Private repos:** connect GitHub once under [Settings → Connections](https://framefields.dev/dashboard/settings#connections). Install the framefields GitHub App on **all repositories**, so repos you create later render without another step. It only gets read access, and each render gets a token for its own repo that expires in an hour. Public repos need nothing.
- **A render-ready project:** a committed lockfile, assets pinned with [`framefields assets`](/docs/guides/assets/) (or in Git LFS), and an entry that only builds the film. A project made with `framefields init` already is one; the full list is the [project checklist](/docs/guides/render-service/#project-checklist).
- **Run from the project folder** (where `framefields.json` is). In a monorepo, that folder is what renders.

## Name your renders

`framefields.json` at the project root names what the project renders. `npx framefields init` writes `film`; add an entry for each cut.

```json
{
  "components": "src/components",
  "renders": {
    "film": {
      "title": "Launch film",
      "entry": "src/film.ts",
      "export": "buildFilm",
      "workers": "auto",
      "output": "output/film.mp4"
    },
    "teaser": {
      "title": "15 s teaser",
      "description": "Vertical cut for socials",
      "entry": "src/film.ts",
      "export": "buildTeaser",
      "codec": "vp9"
    }
  }
}
```

| Field | |
| --- | --- |
| `entry` | The module that builds the composition, relative to the project root. Required. |
| `export` | A `Composition`, or a function returning one. Default `buildFilm`. |
| `title`, `description` | Shown in the dashboard and `framefields renders`. |
| `args` | Passed to the export when it is a function. |
| `select` | When the export returns several compositions, the one to render. |
| `workers` | `1` (default), a number, or `"auto"` to split long films. |
| `maxWorkers` | Upper bound for `"auto"`. |
| `codec` | `h264` (default, MP4) or `vp9` (WebM). |
| `quality` | `very_low`, `low`, `high` or `very_high` (default). |
| `output` | Where the CLI downloads the video. |

Names use letters, digits, `-`, `_` and `.`. `npx framefields renders` lists them; `npx framefields check` checks every one, `npx framefields check teaser` one.

## From the terminal

```bash
npx framefields render film --cloud --plan   # workers, time and estimated cost; renders nothing
npx framefields render film --cloud          # render, then download to the render's output
```

Without `--cloud`, `npx framefields render film` renders the same named render on this machine, to the same file, and `npx framefields preview film` plays it in the browser: switching is the flag. Flags override the render's fields (`--workers 4`, `--codec vp9`, `--output`). With a single named render, `npx framefields render --cloud` needs no name. A module renders without a manifest too: `npx framefields render src/film.ts --cloud --export buildFilm`.

`--plan` probes the film and prints the GPUs, time and estimated cost, then the command that renders it; it renders nothing. While rendering, the CLI prints one line per phase and progress every 10 seconds with the cost so far. `--json` prints the final status as JSON. Ctrl-C cancels the render; GPU time already used is charged.

## From code

```ts
import { renderCloud } from "framefields/cloud";

const result = await renderCloud({
  render: "film",
  outputPath: "output/film.mp4",
  onProgress: (s) => console.log(s.status, s.progress.frames),
});
```

`framefields/cloud` needs no GPU. Options passed here override the named render's; `dryRun: true` returns the plan and estimate, and `signal` cancels. A failed render throws a `CloudRenderError` with the worker's `code`.

### HTTP API

The CLI and `renderCloud` call the same API, with `Authorization: Bearer $FRAMEFIELDS_API_KEY`:

```bash
curl https://api.framefields.dev/v1/render \
  -H "Authorization: Bearer $FRAMEFIELDS_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "source": { "repo": "https://github.com/acme/launch-film", "ref": "main" },
    "target": { "entry": "src/film.ts", "export": "buildFilm" },
    "render": { "codec": "h264" },
    "workers": "auto",
    "dry_run": false
  }'
```

`GET /v1/render/:id` returns its status (`probing`, `rendering`, `stitching`, then `completed` with `output.url`, or `planned`, `failed`, `cancelled`), progress, plan and cost so far. `DELETE /v1/render/:id` cancels it. Optional `source.commit` pins a commit and `source.workdir` a folder; optional `name` and `title` label it in the dashboard.

## From the dashboard

[Connect GitHub](https://framefields.dev/dashboard/settings#connections) once, on all repositories, then **Renders → New render**: pick a repo and branch, and the dialog lists the renders its `framefields.json` names, with their titles and descriptions. Picking one applies its GPUs and format. **Plan** shows GPUs, time and cost before anything renders. Repos without named renders still render: choose **Another file in this repo** and pick the module and export.

The Renders page lists every render, from the dashboard, the CLI or the API: live progress, cost so far against the estimate, Cancel, and the finished video.

## Splitting long films

`workers: "auto"` measures frame costs on one GPU first, then splits the film into as many chunks as stay efficient: a 5-minute film stays on one GPU, a 3-hour film spreads across up to 10. Each chunk renders on its own GPU, the audio renders once, and the chunks are joined without re-encoding. Effects with state (trails, datamosh, particles) replay earlier frames, so every chunk renders the same picture as a single render.

## Cost

You pay for GPU seconds actually used. When a render starts, a credit hold is placed on your balance; once the film is planned the hold is raised to the estimate, and when it finishes you are charged the GPU time used and the rest is released. Failed and cancelled renders charge the GPU time they used. `--plan` (or **Plan**) costs the few seconds of GPU time the probe takes.

## When a render fails

The error names the worker's code. Fix the project, commit, push and render again; network errors, stalled workers and lost machines are retried by the API, and a straggling chunk gets a backup worker.

| Code | Cause | Fix |
| --- | --- | --- |
| `REPO_NOT_FOUND`, `GIT_AUTH_FAILED` | A private repo the GitHub App can't reach | Connect GitHub with access to this repo |
| `REF_NOT_FOUND`, `COMMIT_MISMATCH` | The branch or commit isn't on the remote | `git push` |
| `LOCKFILE_MISSING`, `INSTALL_FAILED` | No committed lockfile, or it doesn't install | Commit a pnpm, npm or yarn lockfile matching `package.json` |
| `ENTRY_NOT_FOUND`, `EXPORT_NOT_FOUND` | `entry` or `export` is wrong | `npx framefields check <name>` |
| `EXPORT_SIDE_EFFECT`, `COMPOSITION_BUILD_FAILED` | The entry renders or writes while building, or throws | Keep `film.ts` to building; `framefields check` reproduces it |
| `ASSET_NOT_FOUND`, `LFS_OBJECT_MISSING`, `LFS_POINTER_UNRESOLVED` | An asset wasn't committed or its LFS object wasn't pushed | `git lfs ls-files`, `git lfs push --all origin` |
| `ASSET_NOT_UPLOADED`, `ASSET_PRIVATE`, `ASSET_HASH_MISMATCH` | A pinned asset isn't on the bucket, or the worker can't read it | [Assets: when something fails](/docs/guides/assets/#when-something-fails) |
| `ENGINE_UNSUPPORTED` | The project's framefields is too old to split | Upgrade framefields, or use `--workers 1` |
| `RENDER_TIMEOUT`, `RENDER_OOM` | One GPU can't finish its part | `--workers auto`, or more workers |
| `insufficient_credits` | The balance doesn't cover the hold | Add funds under [Billing](https://framefields.dev/dashboard/billing) |

All worker codes: [Render on a server](/docs/guides/render-service/#errors).
