Skip to main content

init flags and add-ons

init flags: --framework <react|svelte> (default: react), --name <name> (default: the folder name), --theme <id> / --radius <len> / --dark <mode> / --font <id> / --font-mono <id> / --font-heading <id> / --icons <id> (the look — see Theming), --ai <claude|codex|cursor|none> (repeatable; writes CLAUDE.md/AGENTS.md/Cursor rules — none by default), --marketplace <pkg> (repeatable, comma-separated; installs add-ons and registers them — below), --force (scaffold into a non-empty folder), --no-install (skip npm install), --web (choose all of the above in a browser instead — see Choosing in a browser), --from <source> (fill xano/ from an existing backend rather than the starter — see Pulling an existing workspace). In a terminal, init prompts for the framework and the AI files; every prompt has a default, so pressing enter twice is a valid answer. The look is never prompted for — it comes from the flags above, or from --web. The starter backend is empty but already compiles and deploys — grow it from the walkthrough in xano/EXAMPLE.md. A scaffold ships @xanots/sdk and nothing else from the @xanots scope. Add-ons install on demand:
The three read verbs hit a public catalogue, so they work before you log in. Every add-on is optional and none is assumed by anything in the scaffold — install one when you need it. init takes the same package names, so a project can be scaffolded with its add-ons already wired:
That installs each package and writes its registration into xano/index.ts. Installing alone would leave dependencies nothing imports — a different project wearing the same name. A module declares how it registers in its own package.json ("xanots": { "register": "registerAuth" }); one that has not adopted the field is read for a single register* export, and two of those without the field is a refusal rather than a guess. @xanots/auth is authentication, not authorization — user/login/signup tables and the endpoints over them. It ships no roles, permissions, or route guards, so it is not the RBAC answer. Build role guards natively: a role column on the auth table, then a s.precondition on each endpoint gating auth("role"). details is the one to reach for when wiring an add-on: it prints the objects the add-on puts on your workspace, what you have to supply, and the xano/index.ts registration to copy. Piped, it emits JSON; --prompt emits instructions written to be handed straight to a coding agent. That is npm install with two additions: add-ons are discoverable from xanots --help, and the command refuses before npm runs when you are not standing in a project — the mistake npm answers by silently writing to the wrong package.json. The package name is passed through exactly as typed, so version specifiers, tags, and third-party packages all work.

Choosing in a browser

--web collects the same choices against a live preview instead of on the command line:
It is a launcher, not a second scaffolder: a configurator is downloaded on demand, serves a local page, and finishes by running init with the flags your choices imply — printing the equivalent command so the project stays reproducible from a script. Everything after --web is passed to it untouched, including --help, which is why that one form reaches the network. xanots help init stays offline, like init itself. Because the configurator is fetched at run time, --web needs the npm registry before it can start. init on its own reaches out only to install the new project’s dependencies, which --no-install skips.

The frontend preset

To point npm run dev at a real backend, copy .env.example to .env.local — both live at the project root, next to vite.config.ts — and set VITE_XANO_HOST to a deployed URL. Deployed builds don’t need it: xanots deploy <entry> --static <dir> injects the backend URL as window.XANO_HOST, which takes precedence. The frontend ships Button and Card already vendored, plus a pre-configured components.json, so npx shadcn@latest add dialog form input (or npx shadcn-svelte@latest add … on a Svelte scaffold) works immediately — no init step for either CLI. Components are copied into your repo rather than installed, so you own and edit them directly. Lucide is installed on both scaffolds — lucide-react on React, @lucide/svelte on Svelte — and the landing page already uses it.

Theming

shadcn components carry no colors of their own: they are Tailwind utilities over a fixed set of semantic tokens (--primary, --muted-foreground, --border, the --chart-* ramp, the --sidebar-* set). Those tokens live at the top of frontend/src/index.css, which is the whole theme — Tailwind v4 keeps it in CSS, and there is no tailwind.config.js. That one stylesheet backs both frameworks. init renders it from a theme you choose, using shadcn’s own two-part model:
Base colors — the full token set: neutral (default), stone, zinc, mauve, olive, mist, taupe. Accents — a partial override of primary, secondary, the chart ramp, and the sidebar primary: amber, blue, cyan, emerald, fuchsia, green, indigo, lime, orange, pink, purple, red, rose, sky, teal, violet, yellow. The values are shadcn’s, verbatim, so --theme zinc-blue is what ui.shadcn.com hands out for the same pair. --theme also takes any shadcn registry theme — its own, a third-party generator’s, or your team’s:
--radius <len> overrides the corner radius (a bare number is rem). Everything else about the project is identical whichever theme you pick, and you can change your mind later by editing the token values — or by applying another theme over them with npx shadcn@latest add <registry-theme-url>.

Dark mode

Every theme ships a complete dark palette. --dark decides what turns it on:
  • system (default) — an inline script in the HTML entry applies the OS setting before first paint, so the page never flashes light first. No UI.
  • toggle — that, plus frontend/src/lib/theme.ts (the persisted mode) and a mode toggle on the landing page cycling system → light → dark.
  • off — light only. The .dark block is still there and still complete.
Whichever you pick, style with the token classes (bg-primary, text-muted-foreground) rather than raw palette classes like bg-gray-100: raw ones ignore the theme and are unreadable in dark mode. The scaffolded AI instruction files say so too.

Typefaces and icons

Fonts are opt-in and self-hosted: each choice installs an @fontsource package rather than linking Google’s CDN, which would be a third-party request on every page load of your app, a failure behind a firewall, and a privacy question someone inherits later.
  • --font <id> — body text (Tailwind’s --font-sans, which v4 also uses as the page default). Sans faces only: geist, inter, figtree, manrope, dm-sans, space-grotesk, outfit, and 10 more.
  • --font-mono <id> — code. jetbrains-mono or geist-mono.
  • --font-heading <id> — headings. Accepts any face, sans or serif, since a display serif over a sans body is the reason the slot exists. It emits a base-layer rule, so headings pick it up without touching every <h1>.
  • Omit a slot and it keeps Tailwind’s default stack, installing nothing for it.
An unknown id fails at init naming the slot it was resolving, so --font-mono inter is an error rather than a proportional face quietly rendering your code blocks. --icons <id> picks the icon set: lucide (default), tabler, or phosphor. The binding covers the dark-mode toggle’s icons as well as the landing page’s — without that, --icons tabler --dark toggle would emit a toggle importing a library the project no longer installs, a build failure from a flag with nothing to do with dark mode.
On a Svelte scaffold npm run typecheck runs svelte-kit sync && svelte-check rather than tsc. It checks the backend and the components together — tsc cannot read .svelte files at all.
The SvelteKit scaffold prerenders every route. frontend/src/routes/+layout.ts sets prerender = true, so each route becomes its own HTML document at build time and loads as a real page. Pages live in frontend/src/routes/, and files in the sveltekit() plugin config keeps the project single-rooted with xano/ as a peer. That config lives in vite.config.ts — there is no svelte.config.js, matching where SvelteKit’s own scaffold now puts it. There is still no server at runtime — Xano is the backend and deploy --static ships to a host with no runtime, so +page.server.ts, form actions, and server load have nothing to run on, and the build does not stop you. Treat them as unavailable rather than trusting a green build. Two things follow from prerendering. Because it renders at build time, module-scope window/document access fails the build rather than the browser — use onMount, or guard with browser. And a dynamic route like /posts/[id] fails the build unless it declares which ids exist:
That is deliberate — a loud build error beats shipping a page that 404s for real users. Unmatched paths get a real 404 from frontend/src/routes/404/+page.svelte, which prerenders to 404.html. It has to be a route: SvelteKit never prerenders +error.svelte to a file, so that alone would ship no 404.html and every unknown path would serve the home page with a 200 instead. One more build-time check comes with prerendering: a hash link to an id that is not on the page it renders on — <a href="#pricing"> with no id="pricing"fails the build, naming the route and the id. A hash nav in +layout.svelte is exempt on /404 only, since that route inherits the layout and by definition carries none of the page’s sections.