Declarative config: onvibe.json
An app's configuration — who can access it, which emails are authorized, which routes stay public,
its scheduled jobs and its framing policy — lives in a single declarative file, onvibe.json,
at the project root. This is the only way to change those settings: there are no per-setting
tools. You edit the file and deploy it. A version lock stops you from wiping config you never
saw.
The golden rule: download → edit → redeploy
Never write onvibe.json from scratch. Always start from the current one:
- Download the live config with
get_config({ project_id }). It returns the currentonvibe.jsonincluding aversionfield (a hash of the live config). - Edit the parts you want to change. Keep the
versionfield exactly as it is. - Deploy the edited
onvibe.json(stage it anddeploy). The platform applies it only ifversionstill matches the live config. If it doesn't (someone/something changed the config since you downloaded), the deploy is rejected withCONFIG_OUT_OF_DATE— callget_configagain, re-apply your edit on top of the fresh file, and redeploy.
This guarantees you can't accidentally delete config by regenerating a partial file.
// onvibe.json — from get_config; edit and redeploy
{
"version": "d2a37fc5c623712a", // KEEP AS-IS — the lock
"title": "My Book Club", // display name on the login pages (default: the subdomain)
"access": {
"policy": "signup", // public | signup | allowlist | password
"publicPaths": ["/", "/d/*"], // routes open despite auth
"allowlist": ["a@example.com"] // only for policy "allowlist"
},
"crons": [ // scheduled jobs (full set; omit to remove all)
{ "name": "daily-digest", "schedule": "0 9 * * *", "path": "/cron/digest" }
],
"security": {
"frame": "deny" // deny (default) | sameorigin | allow
}
}
What it covers
title— the app's display name, shown on the managed login/signup pages. Omit it to fall back to the subdomain (the project id).access.policy— the managed access policy (see the managed-auth doc).access.publicPaths— routes open to anonymous visitors even under auth.access.allowlist— authorized emails (only underallowlist).crons— the complete set of scheduled jobs. A versioned deploy makes the crons match this list exactly (adds new ones, removes any not listed). Omit the section (or use[]) to remove all.security.frame— anti-framing policy.deny(default, the app can't be embedded in an iframe),sameorigin(only same-origin framing), orallow(opt out — embeddable anywhere). Omitting it is the safe default (deny).
Custom domains and public-fork publishing are not part of onvibe.json on purpose — they aren't
cloneable config (a fork starts private; a domain is unique to one app). Manage those with their own
tools (add_custom_domain, set_project_public_fork) or the dashboard.
What is NOT in onvibe.json
These are managed separately and must not be put in the file:
- Environment variables (keys or values) — use
set_env/ the dashboard. Secrets never go in a committed config file. - Resources / tier — set by the plan, not by the app.
- The shared password (for
policy: "password") — a secret; set it withset_access_policy. The file can declarepolicy: "password", but not the password itself.
No per-setting tools
There is no set_public_paths, add_allowed_email, create_cron or delete_cron. Every one of
those settings is a field in this file: download, edit that field, deploy. One mental model, one
audit trail, and the file in your project always matches what's live.
The one exception is set_access_policy, which stays only because of the shared password of
policy: "password" — a secret, and secrets never go in the file. Use it just for that.
Two related tools are about state, not config:
get_project({ project_id })— schedule, next run and last run of each cron job.run_cron({ project_id, name })— trigger a job once, now, for testing.