Hosted vs local
When you finish this page you can say, for any piece of Sorb™, whether it runs on your machine or on Sorb Cloud, and what — if anything — that piece sends off your machine. You need to have read Getting started once; nothing here is setup.
The short answer
sorb-juice — the bridge — is the same binary either way. Run it yourself with
sorb dev and it is the local loop: single-tenant, no account, nothing
leaves your machine except what a Figma plugin you are running locally sends
to 127.0.0.1. Point the plugin or the React SDK at bridge.sorbcloud.com
instead and the identical bridge code runs multi-tenant behind Sorb Cloud's
auth and storage — the hosted platform. Nothing about the endpoint paths
changes between the two; only whether a database URL is configured on the
process you're talking to.
What runs where
LOCAL LOOP (free, single-tenant) HOSTED PLATFORM (Sorb Cloud)
───────────────────────────────── ─────────────────────────────
Figma plugin (Canopy) Figma plugin (Canopy)
│ POST /preview, GET /artifacts │ POST /preview, GET /artifacts
▼ ▼ (Bearer <key>)
sorb dev — on YOUR machine bridge.sorbcloud.com — sorb-juice,
127.0.0.1:7777, no auth hosted mode, key-authenticated
reads sorb.config.json, reads/writes org + project data
.sorb/resolved.json on disk in Postgres, preview payloads in
│ Redis (TTL, see below)
▼ │
Your React app (@sorb/leaf) ▼
applies CSS vars, polls the app.sorbcloud.com — sorb-cloud
local bridge over loopback (accounts, orgs, projects, keys,
billing)
│
▼
Your React app (@sorb/leaf)
applies CSS vars, polls the
hosted bridge over HTTPS
Both loops run the identical @sorb/leaf SDK in your app and the identical
sorb-juice bridge code. The only thing that changes is which process answers
POST /preview and whether that process was started with a database
configured.
What leaves your machine
| Local loop | Hosted platform | |
|---|---|---|
| Runs on | Your machine (sorb dev, 127.0.0.1:7777) | bridge.sorbcloud.com + app.sorbcloud.com |
| Auth | None — anything that can reach the port can call it | Every request carries an API key or a signed-in session |
| Token values, preview payloads | Never leave your machine | Sent to the hosted bridge over HTTPS; held in Redis, TTL 24h on the free plan |
| Org/project/account data | Doesn't exist — there's no account | Stored in Postgres by sorb-cloud (orgs, projects, members, API keys) |
| Figma plugin traffic | Loopback only | HTTPS to bridge.sorbcloud.com, key-authenticated |
| Cost | Free, no account | Free during the public beta — see Cloud & accounts |
The bridge itself decides which mode it's in with a single switch: hosted mode turns on when the process it's running as has a database URL configured, and that gate controls auth, entitlement checks, and CORS — no route ever moves between the two modes.
Which key type does what
The hosted bridge and app.sorbcloud.com authenticate every request with an
API key issued from your org's dashboard. There are two types, and the type —
not any setting you choose — determines what the key can do:
| Key type | Prefix | Scope | Use it for |
|---|---|---|---|
| Publishable | sorb_pk_ | Read-only | Everything the Figma plugin gets on sign-in pairing today: reading tokens, opening and editing previews, and the plugin's "Save to project" publish; the React SDK reading resolved tokens and previews; anywhere a key might end up visible client-side |
| Secret | (shown once at creation, never printed again) | Read-write | Server-side or CI writes — opening a token PR, and any call the bridge write-gates (POST /verify, POST /tokens/figma) |
A publishable key is read-only by design — scope is derived directly from
the key's type, publishable always maps to read, secret always maps to
write, and there is no setting on a key that overrides it. POST /verify and POST /tokens/figma on the bridge both fail closed with a 403 on
a publishable key rather than silently upgrading it — see
Troubleshooting for what that looks like and how to
fix it. One deliberate, bounded exception today: the cloud's "Save to project"
publish route accepts a publishable key too, because that's what plugin
pairing currently issues — it can only overwrite the project that key already
belongs to, never another one.
What stays true either way
- Your
tokens/*.jsonand the generated.sorb/resolved.jsonlive in your repo in both modes — the bridge reads them off disk locally, and the hosted platform only ever sees the values you explicitly publish or preview. - The vocabulary guard, the sanitizer, and the banner states in
@sorb/leafrun identically against either bridge — the SDK doesn't know or care which mode answered its request. - Switching from local to hosted (or back) is a config change — the
preview.originyour app points at and the key you configure — not a code change.
Next
- Cloud & accounts — create an org and issue keys.
- The bridge —
sorb dev,sorb serve, and the route table. - Troubleshooting — what a wrong key type looks like.
Works with Figma. Not affiliated with, or endorsed by, Figma. Figma is a trademark of Figma, Inc.