Skip to content

Repository files navigation

Seamless Templates

License: AGPL-3.0-only

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.

You usually do not clone this repository directly. Run the CLI instead:

npx seamless-cli init my-app

Repository layout

seamless-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
├─ 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.


The registry

registry.json is the catalog. Every template the CLI can offer has one entry:

{
  "schemaVersion": 1,
  "templates": [
    {
      "id": "react-vite", // unique, kebab-case
      "kind": "web", // "web", "api", or "mobile"
      "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",
    },
  ],
}

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.

The template manifest

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 vocabulary

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

Adding a template

  1. Create templates/<kind>/<framework>/ with a complete, runnable starter.
  2. Add a committed .env.example describing its environment contract.
  3. Add a template.json manifest (see above).
  4. Add an entry to registry.json.
  5. If it is a React web starter, add it to targets in shared/react-app/sync.json and run npm run sync:shared.
  6. Run npm run validate and 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.

Checks every template ships

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.

Mobile templates

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.


Local development

npm install
npm run validate

npm 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:shared

Versioning

Releases 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 changeset

License

AGPL-3.0-only © 2026 Fells Code LLC

See LICENSE for the full text and LICENSE.md for a summary.

About

Template examples for building seamless auth projects

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages