ribo.toml Reference
Every Cell has a ribo.toml configuration file. ribo deploy reads it from the current directory by default, or from the path given by --config.
[cell]
The [cell] table is required.
[cell]
name = "my-cell" # required
js = "./cell.js" # required (or wasm, or static)
build = "npm run build" # optional — run before uploading
Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Human-readable name. Used to derive the stable address. |
js |
string | one of | Path to the JavaScript entry point (relative to ribo.toml) |
wasm |
string | one of | Path to the compiled .wasm file |
static |
string | one of | Path to a local directory — deploys a static site with no JS required |
build |
string | no | Shell command to run before deploying. Runs in the directory containing ribo.toml. |
bindgen_glue |
string | no | Path to the wasm-bindgen JS glue file. Required when wasm is set and the binary uses wasm-bindgen. |
account_id |
string | no | Pin this project to an account (acct_...). See Account pin. |
account |
string | no | Human-readable label for the pinned account. Display-only — never enforced. |
Exactly one of js, wasm, or static must be present.
Account pin
account_id pins the Cell to a specific account so it can't be deployed into the wrong one. When set, ribo deploy compares it against your active account (from your auth token) and refuses to deploy on a mismatch, before uploading anything:
[cell]
name = "my-cell"
js = "./cell.js"
account_id = "acct_3f8c1a2b4d6e8f00" # only deployable from this account
account = "Acme Corp" # optional label, shown in output
The pin is an assertion, not a selector: it never changes which account is used, it only guards against deploying this workspace somewhere unintended. The optional account label is display-only and is never used for enforcement (account names are mutable; the id is the identifier). Generate the pin without hand-copying the id with ribo account pin.
Don't pin shared/example templates that others deploy into their own accounts — the pin would refuse their deploys.
The pin catches a wrong account; it doesn't choose the right one. If you regularly work across several accounts — or run agents that deploy for you — bind each project tree to its own credential with profiles, and keep the pin as the backstop.
[[bindings]]
Each [[bindings]] block declares a resource that the Cell can access at runtime. The binding name becomes a property on the env object passed to fetch.
c3 — SQLite database
[[bindings]]
type = "c3"
binding = "DB" # env.DB in your cell
database = "myapp" # database name (created with ribo db create)
Multiple databases:
[[bindings]]
type = "c3"
binding = "USERS"
database = "users"
[[bindings]]
type = "c3"
binding = "LOGS"
database = "request-logs"
g7 — Object storage bucket
[[bindings]]
type = "g7"
binding = "IMAGES" # env.IMAGES in your cell
bucket = "avatars" # bucket name (created with ribo bucket create)
files — Static file tree
[[bindings]]
type = "files"
binding = "ASSETS" # env.ASSETS in your cell
dir = "./public" # local directory to upload
Files are uploaded at deploy time and served from object storage. The binding has a fetch(request) method for URL-based serving and a get(key) method for direct access.
text — Plain string value
[[bindings]]
type = "text"
binding = "API_KEY" # env.API_KEY in your cell
value = "sk-..." # literal string value
Use text bindings for configuration, feature flags, or non-secret string values.
vault — Encrypted secret
[[bindings]]
type = "vault"
binding = "JWT_SECRET" # env.JWT_SECRET in your cell
A vault binding injects a secret value at dispatch time. Unlike text, the value is not stored in ribo.toml — only the key name is. The value itself is set separately with ribo vault set and stored encrypted server-side. Use vault bindings for API keys, signing secrets, database credentials, and any other sensitive value you don't want committed to source control.
ribo vault set my-cell JWT_SECRET # set the value (prompts for hidden input)
ribo deploy # then deploy
Important: set the vault value with ribo vault set before deploying a cell that declares a vault binding. If the value is missing at deploy time the binding is stored as a placeholder, and the cell will receive that placeholder instead of the real secret — causing silent failures. See ribo vault for the full command reference.
For how vault values are protected — encryption at rest, when decryption happens, scopes, auditing, and rotation — see the Vault Security Model.
[[pulse]]
Each [[pulse]] block declares a cron schedule. The platform calls the Cell's pulse(event, env) handler at each scheduled time.
[[pulse]]
schedule = "0 * * * *" # every hour at :00
[[pulse]]
schedule = "*/15 * * * *" # every 15 minutes
Multiple [[pulse]] blocks are allowed. Each fires independently.
Fields
| Field | Type | Required | Description |
|---|---|---|---|
schedule |
string | yes | 5-field POSIX cron expression (UTC). Validated at deploy time. |
Cron syntax: min hour dom month dow. All times are UTC. Minimum interval is 1 minute.
See Pulse Overview for the handler interface and examples.
[server]
Override the default management API URL. Useful for local development.
[server]
url = "http://localhost:8082"
When omitted, ribo uses https://api.tissue.systems.
Complete example
[cell]
name = "notes-app"
js = "./dist/cell.js"
build = "esbuild src/cell.ts --bundle --outfile=dist/cell.js --format=esm"
[[pulse]]
schedule = "0 * * * *" # hourly cleanup
[[bindings]]
type = "c3"
binding = "DB"
database = "notes"
[[bindings]]
type = "g7"
binding = "UPLOADS"
bucket = "note-attachments"
[[bindings]]
type = "text"
binding = "APP_VERSION"
value = "1.0.0"
Static site shorthand
For a Cell that only serves static files, the static field eliminates the need for a JS entry point entirely:
[cell]
name = "my-site"
static = "./public"
ribo generates the pass-through worker automatically and deploys the directory contents. See Static Sites for routing behaviour and the hybrid (static + dynamic) pattern.