A XanoTS project: a Xano
backend authored in TypeScript under xano/, and a React + Vite
frontend under frontend/ that derives its request paths and
types from the backend defs — so the two can't drift.
npm install
npm run dev # run the frontend (no backend needed yet)Then author your backend in xano/index.ts — start with the
walkthrough in xano/EXAMPLE.md.
xanots login # once, to authenticate against your Xano account
npm run build # build the static frontend into frontend/dist
npm run xano:deploy # ship the backend + static frontend togethernpm run xano:exportcompiles the backend toworkspace.json(don't commit it).npm run xano:deploydeploys the backend and the built frontend to a live ephemeral environment and prints its URL. Run it again to refresh the same environment; if it expired, a fresh one is created and the new URL is called out.- Deploying to your throwaway singleton sandbox instead:
xanots deploy ./xano/index.ts --dest sandbox.
Object identity derives from (type, name), so a rename would otherwise change
an object's guid and the engine would delete and recreate it rather than
renaming it in place — losing its rows on a record-preserving import.
xano/xano.lock freezes each guid and each API group's
canonical slug, so renames and re-deploys keep the same identities (and the same
public URLs).
It is created by npm run xano:export or npm run xano:deploy, and must be
committed. Adopt it from the first export: once identities have drifted, the
recovery path is xanots lock adopt <live-bundle.json> --lock=xano/xano.lock
against the deployed workspace.
npm run xano:check # CI: fail if the export would change xano.lockTo rename an object: rename it in code, run npm run xano:export (stderr prints
the exact fix-up), run xanots lock rename <kind> <old> <new> --lock=xano/xano.lock,
then export again. lock rename and lock adopt need that flag here — they take no
entry file, so they look for the lock in the current directory, while
lock prune ./xano/index.ts derives it from the entry like export does.
frontend/src/lib/api.ts imports the XanoTS query
defs and derives paths (getPath()) and request/response types
(InferInput / InferResponse) from them. Never hand-type a URL or a request
body — change a def and the frontend types follow.
To spot-check a def from Node (read
getPath()/verb, log a value), run a real file withtsx <file.ts>from inside the project root — nottsx -e, not barenode file.ts, and not from another directory (they mis-resolve the intra-workspace.jsimports and the@xanots/sdkspecifier). Or usexanots paths xano/index.tsto list every endpoint's verb + path.
React + Vite, styled with Tailwind CSS v4 and
shadcn/ui. shadcn is not a dependency — its components
are copied into frontend/src/components/ui/ and
owned by this project, so edit them freely. Button and Card are already
there; add more with:
npx shadcn@latest add dialog input formcomponents.json is pre-configured, so that works with no
shadcn init step. Icons are Lucide, installed as
lucide-react and imported by name —
import { ArrowRight } from "lucide-react", as
frontend/src/App.tsx does.
Components import through the @/ alias
(@/components/ui/button, @/lib/utils), which maps to frontend/src/ in both
tsconfig.json and vite.config.ts — change one and change the other.
To rebrand, edit the color tokens at the top of
frontend/src/index.css. Tailwind v4 has no
tailwind.config.js; the theme lives in that stylesheet.
XanoTS is composable with other @xanots/* packages:
@xanots/auth— turnkey authentication (user/login/signup tables and endpoints). Install it withxanots marketplace install @xanots/auth, then register it inxano/index.ts.- More
@xanots/*packages register onto the same workspace. This list does not update itself — runxanots marketplace listfor the live catalogue,xanots marketplace search <words>to narrow it, andxanots marketplace details <package>to see what an add-on installs and how to register it. All three work before you log in.
None of these ship with the scaffold. Install one only when you need it — an
add-on you never register is weight in package.json for nothing.