A DeepSpace app is a normal Cloudflare Worker. It serves your React SPA, exposes API routes, and owns a set of Durable Objects that hold per-app data. Around your worker, the DeepSpace platform runs multiple shared services - authentication, payments, integrations, and cross-app data - that your worker talks to over service bindings or HTTPS.Documentation Index
Fetch the complete documentation index at: https://docs.deep.space/llms.txt
Use this file to discover all available pages before exploring further.
New to DeepSpace? You don’t need to understand the platform internals to build an app. Skip to the Quickstart and return here when you need to add cross-app data sharing, declare custom bindings, or debug a deploy.
The pieces
A DeepSpace deployment has two halves: the worker you write, and the platform workers DeepSpace runs. Your app worker is a Cloudflare Worker compiled fromworker.ts and the Vite build of src/. It serves the SPA, handles HTTP and WebSocket routes, and owns the per-app Durable Objects. You deploy it with npx deepspace deploy; it lives at <name>.app.space.
The platform workers are managed by DeepSpace; you never deploy or configure them. Your worker talks to three of them at runtime (auth, API, platform) via the helpers in Talking to platform workers; the deploy and dispatch workers sit outside your request path.
| Platform worker | Responsibility |
|---|---|
| Auth worker | Better Auth integration, OAuth flows, JWT issuance (ES256, 5-minute lifetime). |
| API worker | Stripe billing, the integration proxy (215+ third-party endpoints), user profiles, usage tracking. |
| Platform worker | Shared Durable Objects for workspace:*, dir:*, conv:* scopes; R2 file gateway. |
| Deploy worker | Receives deepspace deploy uploads; provisions custom bindings; manages subdomains. |
| Dispatch worker | Routes *.app.space traffic to the correct deployed app via Workers for Platforms. |
Your worker
The scaffolded worker is a Hono app. Its main routes:| Route | What it serves |
|---|---|
GET /ws/:roomId (and variants) | WebSocket upgrades for Durable Object rooms - records, Yjs, canvas, presence, cron |
/api/auth/* | Proxied to the platform’s auth worker (sign-in, OAuth callback, sign-out) |
/api/integrations/* | Proxied to the platform’s API worker - any method, including DELETE /oauth/:provider/disconnect |
POST /api/actions/:name | Server actions defined in src/actions/index.ts |
/api/ai/* | Streamed chat (POST /chat) and chat CRUD (POST/PATCH/DELETE /chats[/:id]), defined in src/ai/chat-routes.ts |
/api/files/* | Scoped R2 file storage, proxied to the platform worker (?scope=app or per-user) |
/_deepspace/* | Allowlisted same-origin proxy for SDK billing hooks (subscriptions, charges) |
/api/debug/* | RecordRoom debug endpoints, gated on ALLOW_DEBUG_ROUTES (set by deepspace dev/test, never in production) |
| Everything else | Static SPA assets (the Vite build output) |
<wrangler.toml name>.app.space.
Durable Objects
State that needs to be shared - across users, across tabs, in real time - lives in a Durable Object. The scaffold ships five DO classes; each is an SDK base class subclassed inworker.ts:
| Class | Purpose | WebSocket route |
|---|---|---|
RecordRoom | SQLite-backed records (your collections) | /ws/:roomId |
YjsRoom | Per-document Yjs CRDT state | /ws/yjs/:docId |
CanvasRoom | Collaborative canvas shapes + viewports | /ws/canvas/:docId |
PresenceRoom | Cursors, typing indicators, viewports | /ws/presence/:scopeId |
CronRoom | Scheduled task scheduler + history | /ws/cron/:roomId |
GameRoom for turn-based games) or subclass any of them with custom behavior.
A DO instance is identified by its name. Same name = same instance with the same state; a different name is a different instance with its own.
Scopes
A scope is a namespaced identifier that determines which DO instance you’re talking to.| Scope | What it represents | Hosted on |
|---|---|---|
app:<APP_NAME> | Your app’s main RecordRoom - everything tied to the app | Your worker |
conv:<id> | A DM or group conversation DO | Your worker |
workspace:default | Cross-app business data (teams, tasks, people) | Platform worker |
dir:<appHandle> | Cross-app directory (conversations, communities, posts) | Platform worker |
app:<APP_NAME>, exported as SCOPE_ID from src/constants.ts. Your RecordScope provider mounts this scope; useQuery / useMutations operate against it.
To read or write a cross-app scope, see Cross-app shared scopes below.
Talking to platform workers
The SDK exposes three fetch helpers indeepspace/worker for addressing platform services. apiWorkerFetch and platformWorkerFetch prefer a Cloudflare service binding when one is configured and fall back to an HTTPS URL otherwise, so the same code path works in production and under deepspace dev. authWorkerFetch is URL-only by design.
| Helper | Signature | Use it for |
|---|---|---|
authWorkerFetch | (env, path, init?) => Promise<Response> | Sign-in flows, JWT issuance, session cookies. URL-only by design - there is no auth-worker service binding. |
apiWorkerFetch | (env, path, init?) => Promise<Response> | Integration proxy, billing, subscriptions, charges. |
platformWorkerFetch | (env, pathOrRequest, init?) => Promise<Response> | Cross-app shared scopes (workspace:*, dir:*, conv:*), scoped R2 files. Accepts a Request so you can forward c.req.raw verbatim. |
c.env.PLATFORM_WORKER.fetch(...) directly works in production but breaks under deepspace dev, where the binding is absent and the CLI writes a PLATFORM_WORKER_URL fallback into .dev.vars for the helpers to pick up.
Security model - WebSocket identity
Durable Objects trust whatever identity the request URL carries. Verifying that identity is the worker’s job - it strips anything the client sent, then re-applies it from a verified JWT. The SDK does this at two entry points: Per-app WebSocket route (wsRoute) - the scaffold ships an inline wsRoute helper in worker.ts (it is not an SDK export). It strips userId, userName, userEmail, userImageUrl, role, and token from the URL on every upgrade, then re-applies identity only from a verified JWT. Three states are possible:
- No token → anonymous (DO assigns
anon-<uuid>) - Invalid token → 401
- Valid token → identity = JWT
sub/name/email/image
workspace:*, dir:*, or conv:* request hits the platform worker, the same URL parameters are stripped and re-applied from the JWT before it forwards to the shared DO. On HTTP forwards, x-user-id is overwritten from the JWT and x-app-action is dropped. These scopes require a valid JWT; unauthenticated requests are rejected with 401.
Cross-app shared scopes
If your app needs to read or writeworkspace:*, dir:*, or conv:* scopes that sync across DeepSpace apps, three edits are required.
Proxy shared scopes in worker.ts
Wrap the scaffold’s inline
wsRoute helper in a small router so cross-app scopes go to the platform instead of your DO:sharedScopes writes to your app’s own DO instead of the platform’s shared DO, and cross-app data won’t show up.
Build & deploy pipeline
npx deepspace deploy performs these steps in order:
Build with Vite
npx vite build runs the Cloudflare Workers Vite plugin, producing the client assets and the worker bundle in a single pass, plus a normalized wrangler.json under .wrangler/deploy/.Extract manifests
The CLI reads the DO bindings, custom bindings (R2, KV, D1, Vectorize, AI, …), and user secrets from
.dev.vars (below the SDK-managed divider) out of the build output.Validate the binding manifest
validateBindingManifest checks custom bindings against allowed types and reserved names. Reserved or duplicate names abort the deploy with a file-pointing error.Upload to the deploy worker
Worker bundle, assets, DO manifest, custom bindings, and user secrets are POSTed as a single FormData to the deploy worker.
Auto-provision resources
Server-side, the deploy worker creates any binding declared with
id = "auto" (or bucket_name = "auto", etc.) on the platform Cloudflare account on first deploy.Register subdomain and dispatch route
The worker is loaded into the dispatch namespace under
<name>.app.space; user secrets become secret_text bindings on the deployed worker. The dispatch worker routes incoming traffic to your worker’s isolate.Next steps
- Data model - collections, records, and how data is shaped.
- Permissions - role-based access control on collections.
- Real-time sync - how WebSocket sync works under the hood.
- Deployment - what happens when you run
deploy.