Get started
Getting started
Nuxter is a Nuxt 4 app for Cloudflare Workers with auth, organizations,
payments and the rest of a product's plumbing already built. You switch those
parts on and off in starter.config.ts(Licensees only), build your
product in the same repository, and pull in kit updates with one command.
What you get
- Auth: password, magic links, passkeys, TOTP and OAuth, with email verification.
- Organizations with members, roles, seats and invitations, and permissions granted by role or by plan.
- Payments through Stripe, Polar, Lemon Squeezy or Creem: subscriptions, one-time plans, trials and coupons.
- Background jobs on Cloudflare Queues, and events your code can listen to.
- Email, in-app notifications, R2 storage, feature flags and AI through the Workers AI binding.
- Server-side analytics, languages, a Nuxt Content blog, rate limiting, security headers and error reporting to Sentry or Better Stack.
The database is Postgres through Drizzle: Hyperdrive in production, PGlite on your machine (Database). The UI is Nuxt UI.
Requirements
- Node 22.19+, 24.11+ or 26+ (
enginesinpackage.json) - pnpm 11 (
packageManagerinpackage.json(Licensees only) pins the exact version) - Access to the kit repository,
nuxter-app/kiton GitHub
You need a Cloudflare account only to deploy, or to call AI models from
pnpm dev.
Create your app
Your app gets its own repository and keeps the kit as a second git remote
named template. That remote is where updates come from.
On GitHub, click "Use this template" on the kit repository, clone the new repository, and add the remote:
git remote add template https://github.com/nuxter-app/kit.git
To host the repository anywhere else, clone the kit and drop its history:
git clone https://github.com/nuxter-app/kit.git my-app
cd my-app
git update-ref -d HEAD
git commit -m "chore: start from the template"
git branch --unset-upstream
git remote rename origin template
git remote add origin <your repository>
Either way, keep your first commit identical to the kit and make your changes in the commits after it. The first update compares against that commit to find the kit version you started from. To start the app in a folder of a monorepo, see Template.
Run it locally
pnpm install
pnpm db:migrate
pnpm db:seed
pnpm dev
pnpm install also runs wrangler types and nuxt prepare; nothing
typechecks until it has. pnpm db:migrate creates a PGlite database in
.data/pglite so the seed has tables to write to. From then on, pnpm dev
migrates it as it starts and serves it on 127.0.0.1:5432, next to the app on
http://localhost:3000. If something else holds 5432, such as another project's
dev server or a Postgres of your own, pnpm dev takes the next free port up to
5441 and logs which one.
pnpm db:seed adds 50 accounts, all with the password seed-password-123.
With the shipped config each one also owns an organization. Three have fixed
addresses:
| Account | Role |
|---|---|
admin@example.com | superadmin |
author@example.com | staff |
user@example.com | user |
pnpm db:seed --help lists its options, such as --count and --reset. It
only writes to a database on your machine.
What runs without credentials
A fresh clone needs no .env and no Cloudflare login. The first pnpm dev
writes a random NUXT_SESSION_PASSWORD to .env for you.
| Part | With nothing configured |
|---|---|
| KV, R2, Queues, Durable Objects | Wrangler's local simulators |
| Printed to the terminal, including verification and reset links | |
| Turnstile | Cloudflare's always-pass test keys |
| Workers AI | Off. Run pnpm exec wrangler login, then set NUXT_DEV_REMOTE_AI=true in .env |
| Stripe checkout and webhook | Answer 500 until NUXT_PAYMENTS_STRIPE_SECRET_KEY and NUXT_PAYMENTS_STRIPE_WEBHOOK_SECRET are set |
| OAuth | None listed in auth.oauth. Each provider you add needs its client id and secret in .env |
Payments are on as shipped, so a route that needs a plan permission
(insights:read in the shipped config) answers 402 to a user whose plan does
not grant it, with or without Stripe keys. To try a paid feature without
Stripe, sign in as admin@example.com, grant user@example.com a plan on
/dashboard/customers, then sign in as that user and open their organization.
Add variables to .env as you turn things on.
.env.example(Licensees only) lists every one the kit reads and where each
value comes from.
Your first superadmin
On an empty database, the first account to sign up becomes superadmin once it verifies its email address. Locally, the verification link is in the terminal. On a new deploy, register your own account first.
This happens only while no account holds users:manage, so after
pnpm db:seed it never fires: sign in as admin@example.com instead. Set
access.firstAccountRole to false in starter.config.ts to turn it off.
Where your code goes
| Path | What goes there |
|---|---|
starter.config.ts(Licensees only) | Which parts and providers are on, plans, roles, permissions, locales |
app/(Licensees only) | Pages, components, layouts, composables. The kit's pages are examples to delete or replace |
server/api/(Licensees only) | Your API routes, next to the kit's |
server/jobs/(Licensees only) | Background jobs, one file each |
server/listeners/(Licensees only) | Event listeners, one file each |
server/database/custom/schema.ts(Licensees only) | Your tables. pnpm db:generate writes their migrations |
server/<part>/adapters/custom/ | A provider of your own for email, payments, AI, flags or monitoring. The filename is its id |
shared/(Licensees only) | Types, schemas and helpers both sides import. Add yours as new files |
content/, i18n/locales/ | Blog posts, legal pages, translations |
Everything else is the kit's: the other directories under server/,
modules/, build/, emails/, test/. You can edit them, but when an update
changes the same lines you resolve a conflict. Layout maps
the whole tree.
Everyday commands
| Command | What it does |
|---|---|
pnpm dev | App and local database |
pnpm db:generate | Writes a migration for changes in custom/schema.ts |
pnpm db:migrate | Applies pending migrations to the local database |
pnpm db:studio | Drizzle Studio on the local database, while pnpm dev is running |
pnpm test --run | The unit and nuxt test suites |
pnpm lint && pnpm format:check && pnpm typecheck && pnpm test --run | What CI runs on every pull request |
Take kit updates
git switch -c template-update-$(date +%Y%m%d)
pnpm template:update
The update applies everything the kit changed since your last one as a single
commit, chore(template): update to …. Push the branch and review it as a pull
request. If you and the kit changed the same lines, it stops with conflict
markers: resolve them and run git cherry-pick --continue, or
git cherry-pick --abort to back out. Template covers
release tags, monorepos and which files conflict.
Good to know
- The remote must be named
template.pnpm template:updatelooks for it by that name. pnpm devstops when a port pinned withNUXT_DEV_PGLITE_PORTis taken, or when 5432 to 5441 all are. Its error says what to do (Database).- A
DATABASE_URLexported in your shell outranks.envfor the dev server and the database scripts. If it is production's,unset DATABASE_URL. - After
pnpm install, a commit hook checks messages against Conventional Commits (feat: …,fix(auth): …, header up to 72 characters).
Next
- Configuration: every key in
starter.config.ts. - Deployment: the Cloudflare resources to create, the secrets, and the deploy itself.
- Template: updates, releases and what conflicts.
A link with a lock opens the kit’s private repository, which every licensee can read.