Building apps

Limits

What the platform deliberately does not do, and the runtime and data constraints to design around.

When a requirement hits a row below, say so up front and take the nearest supported shape. Do not build an approximation that cannot work.

Platform shape

Not possibleWhy, and the nearest supported path
Public or customer-facing appsEvery viewer must be a signed-in org member. There is no anonymous access and no self-signup. These are internal tools
Inbound webhooks or public API endpointsYour backend function only runs on an authenticated app request or your own cron. Poll the source on a cron instead of receiving events
Arbitrary outbound callsEgress is an allow-list. Declare hosts under egress: (exact names or one wildcard level; no schemes, ports, or paths). The default is the data plane only
Real-time push (websockets, presence)No push surface; UIs poll. LLM streaming is the only streaming response
Next.jsIts SSR model does not map to the bounded single backend function. Any other bundler that emits one self-contained ES module works
Custom domains, native mobile, push notificationsApps are responsive web apps at <app>.<org>.<serving-domain>
Bring-your-own API keys in frontend codeThe frontend holds nothing. Use secrets in the backend function, or a connector

Backend runtime

ConstraintValue
The backend function must be one self-bundled ES moduleA code-split, CJS, or dependency-referencing module deploys and then crashes on the first request. The CLI guarantees this for its templates; bring-your-own is your responsibility
Subrequest budgetAbout 100 per invocation. Use files.urls() for batches, not a loop of files.url()
Module size5 MB soft cap
Secrets64 per app, 5 KB per value, write-only
Daily capsLLM tokens and emails per app; both return a typed 429
Invocation logsKept about 14 days

Data

ConstraintDetail
db is one flat storeNo joins, transactions, or aggregations. Partitioning is your key design. Keep heavy data in a warehouse and read it through saved queries
query() returns one pageDefault 100, max 500. Paginate in the backend function or you silently drop the tail
No embeddings or vector searchThe LLM gateway is text-in, text-out

Agents and cron

Scheduled routes are alpha and will change. The refusals below are deliberate, but the limits are under review.

ConstraintDetail
A cron invocation has no callerctx.user is null, ctx.trigger is "cron". Every limit below follows from this
Cron cannot start an agent runA run is owned by (app, caller), so a cron-started run would have no owner. 409. Give the agent its own schedule
Cron cannot poll a run eitheragents.get() matches the same pair, so a cron cannot read back a run that an http invocation started. Also 409
Your own authz code must handle a null callerA route that reads ctx.user.roles throws under cron, or returns everything. Guard with if (!ctx.user)
Cron caps5 schedules per app, 1-minute minimum. A schedule pauses with a visible reason if the current deploy has no backend function
Cron is at least once and may overlapctx.invocationId is your idempotency key. Never promise "exactly once"
Cron sends POSTA route declared GET-only will 404 on every fire and look like a broken schedule
Agent runs are never awaited while the request is openagents.start() returns a queued run; agents.get(request_id) reads it back. There is no call that waits, and a get() loop is not one
Not exposed to the backend functionThe org's role list, and design-system guidance. ctx.user.roles gives the caller's own roles. Fetch design guidance at build time with railcode design-system

Managed agents

See agent limits for what an agent cannot do regardless of its manifest: reaching the open web, running continuously, reacting to events, or invoking another agent.

On this page