Project structure
The TanStack + Convex monorepo layout and where to make changes to routes, data, auth, the admin app, emails, jobs, MCP and tests.
The repository is a pnpm workspace (apps/* and packages/*) driven by Turbo. Convex is configured at the root (convex.json points functions at apps/database/convex), so Convex CLI commands run from the repository root.
Tree
The four workspaces are @nextbase/web, @nextbase/admin, @nextbase/database and @nextbase/email. Both frontends import the generated Convex API as @nextbase/database/api, data model types as @nextbase/database/data-model, and schema-derived types as @nextbase/database/types. The parity/ folder holds a manifest of Nextbase Ultimate features this kit reproduces; .changeset/ drives versioning.
AGENTS.md (root and per package) is the single source of instructions for AI coding tools. pnpm agents:sync generates CLAUDE.md, GEMINI.md, .github/copilot-instructions.md, .cursor/mcp.json and the other tool files from it, from .agents/config.json (MCP servers and hooks) and from .agents/skills/ (new-page, db-migration, billing-plan, mcp-tool). Edit the sources, not the generated files; CI runs pnpm agents:check.
Where to make changes
Routes and pages
Routes are files in apps/web/src/routes/ and apps/admin/src/routes/ using TanStack Router's file conventions. Each app's routeTree.gen.ts is generated; do not edit it. In the web app, public routes (index.tsx, blog.*, changelog*, docs.*, feedback*, roadmap.tsx, terms.tsx, login.tsx, sign-up.tsx) may server-render. Anything under _authenticated.* is client-only and goes through the auth and onboarding gate in _authenticated.tsx.
The sidebar and layout are in each app's src/components/app-shell.tsx; add a navigation item there when you add an authenticated page. UI primitives are in components/ui/ (shadcn-style, configured by apps/web/components.json for the web app). Global styles and Tailwind 4 live in src/styles/globals.css.
Admin app
apps/admin is a second TanStack Start app on port 3002 with its own .env.local (envDir: "."), its own Convex Auth session and its own Vercel project (apps/admin/vercel.json). All /app-admin/* pages live here; the web app has no admin routes. The admin gate in apps/admin/src/routes/_authenticated.tsx checks admin.accessStatus, and lib/impersonation.ts builds the handoff URL to the web origin from VITE_WEB_APP_URL. The repository's docs/separate-admin-app.md covers configuration, deployment and impersonation.
Server and data access
All application logic is in Convex functions under apps/database/convex/, one file per domain (users.ts, workspaces.ts, members.ts, invitations.ts, projects.ts, notifications.ts, billing.ts, feedback.ts, marketing.ts, and so on). Define new functions with the wrappers from lib/functions.ts so authentication, membership, admin and deletion-barrier checks are applied. Throw user-facing errors with appError from lib/errors.ts.
Admin-only functions are in admin.ts, adminUsers.ts, adminWorkspaces.ts, adminBilling.ts, adminAudit.ts and impersonation.ts. Public HTTP endpoints (health, Stripe webhook, API key routes, AI stream) are in http.ts, which also mounts the MCP and OAuth routes from mcp/http.ts.
Both apps read Convex through @convex-dev/react-query (see each router.tsx) and convex/react hooks. Keep TanStack server functions for HTML, headers and web-runtime concerns, not for a second data API.
Schema and migrations
Tables are split by area in apps/database/convex/schema/ and combined in schema.ts, together with the MCP tables from mcp/schema.ts. Shared validators (roles, statuses) are in lib/validators.ts. After a schema change run pnpm db:codegen for _generated/ and pnpm db:types for lib/database.types.ts; CI fails if _generated/ is out of date.
Convex applies schema changes when you deploy. Data migrations use @convex-dev/migrations in migrations.ts (for example backfillUsersAggregate); run them with pnpm convex run migrations:<name>. Aggregates (lib/aggregates.ts) stay consistent through triggers in lib/triggers.ts, which is why mutations must go through the wrapped mutation functions.
Authentication
Providers in auth.ts, redirect allowlist in lib/auth_redirect.ts, JWT domain in auth.config.ts, admin claim in admin.ts, impersonation in impersonation.ts. Frontend pages are login.tsx, sign-up.tsx (web only), forgot-password.tsx, update-password.tsx and the components in components/auth/ of each app. See the authentication page.
Emails
Templates are React Email components in apps/email/emails/, exported from apps/email/package.json. Rendering and Resend delivery happen in apps/database/convex/emailDelivery.tsx (a Node action). Add a template, export it, then add a delivery action and an externalJobs kind for it. Convex Auth emails (verification, reset, magic link) are configured in auth.ts, not in apps/email.
Background jobs and workers
There is no separate worker process. workflow.ts wraps @convex-dev/workflow; enqueueExternalJob writes an externalJobs row with an idempotency key and starts a durable workflow for kinds such as invitation.email, account.delete, workspace.delete, stripe.event and stripe.catalog_sync. Rate limits are in rateLimits.ts. Scheduled work uses Convex crons in crons.ts (currently the hourly MCP cleanup).
Billing
Stripe webhook verification is in http.ts; event handling in stripe.ts; products, prices, subscriptions, invoices, usage and credits in billing.ts and schema/billing.ts. The workspace billing page is _authenticated.workspace.$workspaceSlug.billing.tsx in the web app; the admin views are the payment-gateway.* routes in the admin app.
MCP
Everything server-side is in apps/database/convex/mcp/ plus mcp.test.ts. The user UI is in the web app (components/mcp-connections.tsx, components/mcp-consent.tsx, routes/mcp.authorize.tsx, routes/_authenticated.user.settings.connections.tsx); the admin UI is the same set of files in the admin app plus routes/_authenticated.app-admin.connections.tsx. The agent connections page explains how to add a tool.
Tests
- Convex functions: Vitest with
convex-test, files next to the code (apps/database/convex/*.test.ts) and inapps/database/tests/. - Web and admin unit tests: Vitest with jsdom in
src/**/*.test.ts(x)of each app. - End to end: Playwright specs in
apps/web/e2e/, configured byapps/web/playwright.config.ts(Chromium desktop and Pixel 7 projects). The admin app has no e2e suite.
Run pnpm test for unit tests and pnpm test:e2e for Playwright. pnpm lint, pnpm typecheck, pnpm format:check and pnpm agents:check complete the gates CI runs (.github/workflows/ci.yml).