Skip to content
supastack
Browse documentation
WebTanStack Start

For AI agents: fetch the complete documentation index at /llms.txt, the full documentation corpus at /llms-full.txt, the agent skill at /skill.md, or this page as Markdown at /docs/tanstack/first-feature.md (or send Accept: text/markdown).

Build your first feature

An end-to-end walk from schema to shipped screen.

Beginner walkthrough

Prerequisites: Read the two beginner guides below, finish Web local setup, and open this monorepo at its root in your editor. Web settings belong in apps/web-tanstack/.env.local; TanStack Start does not read the root .env. /dashboard in the default fake mode is a no-database demo; /auth needs a migrated database.

Quick terms: a workspace is one team's separate users, data, and settings. The server is the trusted backend; keep provider keys out of browser and phone code. A migration is a versioned database change that creates or updates tables. An AI model is the engine that answers; the model catalog is the server's list of available model IDs and prices. A provider is an outside service such as an AI, email, or billing service. An entitlement is a grant that lets a workspace use a feature. A webhook is a message one service sends to another service's URL.

1. Open VS Code at the repository root and ask your coding agent to create apps/web-tanstack/src/routes/reports.tsx and packages/web-core/src/reports.ts. Give it the report fields and allowed roles; require server-side membership checks and a query filtered by workspaceId.

2. Open /reports as a signed-in member and confirm only that workspace's records appear.

3. Try a signed-out and a second-workspace account; if either can read the records, stop and fix the server check.

For custom code, start with the supplied prompts/README.md and copy the closest prompt: prompts/new-app-screen.md for an app screen or prompts/new-landing-page.md for a public page. For server, database, or provider work, give your coding agent this guide's task and ask it to read AGENTS.md, .ai/PROJECT_CONTEXT.md, and matching .ai/ mirrors; inspect the exact source files named here; list files and checks before editing; preserve workspace ownership and server-only secrets; and show you the diff. Tell it the behavior you want, input and output fields, allowed roles, expected success and errors, and the test account, provider, or device. Use sample values; never paste a real secret.

Create the route

Add a route file beneath apps/web-tanstack/src/routes/ — it calls createFileRoute to publish the page. Start from the dashboard or content route so you reuse its session and active-workspace resolution; shared page sources (page.load.ts and page.view.tsx) live under packages/web-core/src/pages/ and mount through generated bindings. Use the route's loader for protected reads.

apps/web-tanstack/src/routes/reports.tsx
packages/web-core/src/reports.ts
.ai/apps/web-tanstack/src/routes/reports.md
.ai/packages/web-core/src/reports.md

Resolve ownership on the server

Read the signed-in identity and active workspace through the existing auth/workspace helpers. Verify membership before loading records. Filter every query by the resolved workspaceId; never take an owner identifier from a form or model output.

Check owner/admin permissions for management actions. When a feature is paid, check its entitlement at the read or action boundary before returning protected data; a hidden button does not enforce access.

Verify access and recovery

Test anonymous access, a valid member, a member of another workspace, a revoked session, and the required role. Add empty and failed-load states with a recovery action. Keep the route out of the public sitemap and add its prefix to robots disallows.

Model the record

Every product record starts in the database schema. Add a table in packages/db with a workspaceId column, generate the migration, and write the repository helpers that resolve the workspace before reading or writing.

// packages/db/src/schema.ts
export const ideas = pgTable("idea", {
  id: uuid().primaryKey(),
  workspaceId: uuid().notNull().references(() => workspaces.id),
  title: text().notNull(),
  createdAt: timestamp().defaultNow(),
});

corepack pnpm --filter @supastack/db db:generate

Authorize the action

Create a server function that resolves the session, the workspace, and the entitlement in that order — the handler lives in packages/web-core and reaches the app through a createServerFn binding under apps/web-tanstack/src/actions/ (regenerate with corepack pnpm gen:actions). Validate input with a zod schema, run the mutation through the repository, and return a typed result the interface can render.

The order matters: a mutation that writes before checking workspace ownership has already crossed the boundary. Derive ownership on the server — never trust a workspace id sent by the browser.

Compose the surface

Build the page from shared components and tokens — the kit's blocks and ui-web primitives cover the common patterns. Handle loading, error, and empty states; a feature that only renders its happy path is not done.