The frontend and API starter templates for Seamless Auth, an open source, passwordless authentication system.
This repository is the single source of truth for the starters that seamless-cli scaffolds. The CLI reads registry.json, presents the available templates during seamless init, and copies the chosen ones into a new project already wired to the auth server.
New to Seamless Auth? The self-hosted quickstart runs the full stack locally with Docker. If Seamless hosts your auth instance, follow the managed quickstart instead.
This repo holds the starters the CLI scaffolds, so it is not one node in the diagram below: the web, mobile, and API templates become the browser app, the native app, and your backend (the Next.js starter is both browser app and backend).
flowchart LR
browser["Browser<br/>@seamless-auth/react"] -- "signed httpOnly cookies" --> backend
native["Native app<br/>@seamless-auth/react-native"] -- "bearer tokens" --> backend
backend["Your backend<br/>@seamless-auth/express, fastify, or nextjs<br/>mounted at /auth"] -- "bearer token + service token" --> api
api["seamless-auth-api<br/>owns the session"] --> db[("Postgres")]
backend -. "verifies tokens with JWKS" .-> api
How the pieces connect explains each hop. Compatibility matrix lists which package versions work together.
You usually do not clone this repository directly. Run the CLI instead:
npx seamless-cli init my-appseamless-templates/
├─ registry.json # the catalog the CLI reads to build its prompts
├─ templates/
│ ├─ web/
│ │ └─ <framework>/ # one directory per web starter
│ │ ├─ template.json # how the CLI fetches and configures this template
│ │ ├─ .env.example # the template's environment contract
│ │ └─ ... # the actual starter project
│ ├─ api/
│ │ └─ <framework>/ # one directory per API starter
│ ├─ mobile/
│ │ └─ expo/ # the Expo (React Native) starter
│ └─ fullstack/
│ └─ nextjs/ # the Next.js starter, which serves /auth itself
├─ shared/
│ └─ react-app/ # source of truth for what both React starters share
└─ scripts/
├─ validate-templates.mjs
└─ sync-shared.mjs
Each template is a complete, runnable project. The CLI downloads this repository at a pinned tag, copies the selected template directories into the new project (web/, api/, and optionally mobile/), and fills their .env files from each template's declared contract.
Because the CLI copies one template directory and nothing else, a template cannot reference anything outside itself. Anything two templates share therefore lives in shared/, and each template carries a committed copy of it. See shared/react-app/README.md.
registry.json is the catalog. Every template the CLI can offer has one entry:
status: "coming-soon" advertises a template in the CLI as a disabled option without requiring its content to exist yet. alias (optional) lets users select the template directly with seamless init --<alias>, skipping the prompt.
Each template directory carries a template.json that tells the CLI where to place it and how to configure its environment. This is what replaces per-framework wiring living inside the CLI:
{
"id": "react-oauth",
"targetDir": "web",
"env": {
"fromExample": ".env.example",
"set": {
"VITE_AUTH_SERVER_URL": "{{authServerUrl}}",
"VITE_API_URL": "{{apiUrl}}",
},
},
"verify": {
// optional: how `seamless verify` tests this template
"project": "react", // the Playwright project that drives it
"flows": ["oauth"], // which flow tags to run (@oauth); omit to run all
},
"setup": {
// optional: interactive setup the CLI runs
"oauth": true, // prompt for OIDC providers and wire them into auth
},
"requires": { "cliMin": "0.3.0" },
}The CLI computes the shared values and resolves the {{...}} placeholders in env.set. A new framework with different variable names (for example NEXT_PUBLIC_*) only needs a different set map, not a CLI change.
verify.flows scopes conformance to the flows a template actually supports (the harness tags specs @login, @oauth, ...); a template with no verify block runs the full browser suite. setup.oauth tells the CLI to prompt for OIDC providers (Google, GitHub, Microsoft, GitLab) and wire the chosen ones into the scaffolded auth server. Both fields are ignored by older CLIs, so they degrade gracefully.
| Placeholder | Resolves to |
|---|---|
{{authServerUrl}} |
URL of the Seamless Auth server |
{{apiUrl}} |
URL of the project's API service |
{{apiToken}} |
Service token shared between the API and the auth server |
{{jwksKid}} |
JWKS key id the auth server signs with |
{{serveAdminConsole}} |
Whether the API serves the admin console at /console |
{{databaseUrl}} |
Connection string for the project's database |
{{secret:N}} |
A freshly generated N-byte hex secret, unique per scaffold |
- Create
templates/<kind>/<framework>/with a complete, runnable starter. - Add a committed
.env.exampledescribing its environment contract. - Add a
template.jsonmanifest (see above). - Add an entry to
registry.json. - If it is a React web starter, add it to
targetsinshared/react-app/sync.jsonand runnpm run sync:shared. - Run
npm run validateand open a pull request.
CI validates the registry and every manifest, then installs each template and runs its typecheck, lint, format check, tests, and build to confirm it works before it ships. The mobile template's build is expo export, which bundles the JavaScript for iOS and Android without Xcode or the Android SDK; it proves the bundle, not a signed native binary.
A scaffolded project is expected to be verifiable on the first npm install, so each template declares the same script names. CI runs them with --if-present, and a user gets the whole set locally with npm run check.
| Script | Purpose |
|---|---|
typecheck |
TypeScript with no emit |
lint |
ESLint flat config over the project |
format:check |
Prettier, with eslint-config-prettier keeping the two from disagreeing |
test |
Vitest, no database or network needed |
check |
All of the above in one command |
Tests sit next to the code they cover as *.test.ts / *.test.tsx. They are meant to be a starting point a user extends, not exhaustive coverage: they cover the configuration and startup logic that decides whether a fresh scaffold runs at all. The mobile template tests its pure modules this way; its screens are React Native and are exercised on a simulator rather than in Vitest.
A kind: "mobile" template is placed at mobile/ next to web/ and api/, and is optional in seamless init. It differs from the web starters in one way that is onboarding cost rather than code: native passkeys need the relying party to be a domain the adopter controls, with apple-app-site-association and assetlinks.json hosted over HTTPS, because native WebAuthn has no localhost exemption. Email codes and magic links work against the local stack immediately; passkeys do not until that domain exists. The Expo starter's README and tools/associations/ cover the setup.
A kind: "fullstack" template is one project that is both the web application and its backend.
The Next.js starter serves the Seamless Auth /auth routes itself through @seamless-auth/nextjs,
reads the session in server components, and protects pages in proxy.ts, so it needs no api
template beside it. It is placed at web/ and serves on port 5173, like the web starters.
seamless-cli 0.18.0 and later offer it as a beta template: seamless init my-app --nextjs (or
--web=nextjs) scaffolds it into web/, and seamless check treats the web app as the backend.
Its manifest requires that version (requires.cliMin), and older CLIs never show the kind. A
full-stack template cannot be combined with an api template, and it cannot host the admin console
yet, so only --admin=none applies. seamless verify in CLI 0.18.0 skips full-stack templates
(fells-code/seamless-cli#222); coverage for the Next.js starter is on seamless-cli main but not in
a release yet.
It takes the design tokens and fonts from shared/react-app but not the UI kit, which routes with
react-router. shared/react-app/sync.json lists it as a target with an only list for that reason.
npm install
npm run validatenpm run validate checks that registry.json is well-formed, that every referenced template has a valid template.json and .env.example, and that no template copy of a shared source has drifted from shared/.
To change something both React starters use (the design tokens, the app shell layout, the UI kit, or the fetch seam under lib/), edit the file under shared/react-app/ and run:
npm run sync:sharedReleases are managed with Changesets and published as git tags. The CLI pins a specific tag, so scaffolding is reproducible. Add a changeset with any change that affects scaffolded projects:
npm run changesetApache-2.0 © 2026 Fells Code LLC
See LICENSE for the full text.
{ "schemaVersion": 1, "templates": [ { "id": "react-vite", // unique, kebab-case "kind": "web", // "web", "api", "mobile", or "fullstack" "framework": "react", "label": "React (Vite)", // shown in the CLI prompt "alias": "basic", // optional: enables `seamless init --basic` "status": "stable", // "stable" | "beta" | "coming-soon" "path": "templates/web/react-vite", }, ], }