Skip to main content
The frontend never hand-types a URL or a request body. Three things come from the backend:

Generate the route manifest

The scaffold’s dev, build, typecheck and xano:deploy scripts run it first, and npm run xano:check fails when the committed file is stale. Commit xano/routes.gen.ts. A route key is "<VERB> <name>", the endpoint’s own identity: "GET posts/{id}", "POST posts". When two API groups both hold the same verb and name, those endpoints are keyed "<group>:<VERB> <name>" instead.

Call an endpoint

  • routePath fills and encodes path params. Keys and {param} names are checked at compile time, so a renamed endpoint stops compiling instead of returning a 404.
  • Import defs as types only. import type erases to nothing. Importing a def as a value runs its s.* and c.* calls at load time and pulls the SDK runtime into the bundle (about 65 kB gzipped).
  • InferResponse follows the stack. A db.get response is Row | null, a db.add is the written row, and an object-literal response has those keys. Where the shape cannot be known statically, it is unknown; declare responseShape on the query to fix every caller.
  • InferRow<typeof table> gives a table’s row type, for props and state.

The backend URL

npm run xano:deploy writes the Xano Engine’s URL into .env.local as VITE_XANO_HOST; restart the dev server to pick it up. A deploy with --static instead injects the backend URL into the built HTML as window.XANO_HOST, so one build serves any backend with no rebuild. The XANO_HOST constant above reads both. See Frontend hosting.

Validate at runtime with zod

The types check what you send at compile time. To validate data at runtime too, install the zod add-on:
It adds ROUTE_SCHEMAS, CHANNEL_SCHEMAS and MESSAGE_SCHEMAS to routes.gen.ts under the same keys, each checked against its type when you type-check:

Realtime

When the workspace has realtime servers, the same file exports socketUrl() and channelPath(), plus ChannelInputs and MessageInputs for their params and payloads:
socketUrl turns a tenant base URL into the socket’s own address form, which a frontend cannot build by hand. A listen-only client must send { action: "ping" } every few minutes, or the idle socket is closed.

Next