Customization Recipes
Recipes tell buyers how to turn the foundation into their own SaaS while preserving source-of-truth code, tests, and operational safety.
On this page
Recipe: add a dashboard page
Add a dashboard surface without mixing route entrypoint code, UI state, server reads, and access checks.
Inspect
- src/lib/auth/guards/route.ts
- src/lib/auth/rbac/nav-config.ts
- src/features/settings/ui/section.tsx
Edit
- src/app/(dashboard)/<route>/page.tsx
- src/features/<domain>/ui/<page>.tsx
- src/features/<domain>/server/queries/<query>.ts
- src/lib/auth/rbac/nav-config.ts
Steps
- Create a thin route page that checks access and delegates UI.
- Put screen composition in feature UI.
- Put server reads in feature queries or services.
- Add navigation only after the route is protected.
- Add focused tests for access and server behavior when practical.
Verify
- pnpm run test:features
- pnpm run quality
Recipe: add an admin module
Add admin-only work with fail-closed access, auditability, and predictable navigation.
Inspect
- src/lib/auth/guards/route.ts
- src/lib/auth/rbac/features.ts
- src/lib/auth/rbac/nav-config.ts
- src/lib/audit/log.ts
Edit
- src/app/(dashboard)/admin/<module>/page.tsx
- src/features/<domain>/server/actions/<action>.ts
- src/features/<domain>/ui/<module>.tsx
- src/lib/auth/rbac/nav-config.ts
Steps
- Define the feature key and route access before adding UI.
- Gate the route on the server.
- Keep admin actions server-owned and validate inputs.
- Log important changes with actor and target context.
- Add navigation after access is correct.
Verify
- pnpm run test:features
- pnpm run quality
Recipe: add an analytics card
Add dashboard analytics without creating provider calls in render loops or unbounded database scans.
Inspect
- src/features/analytics/server/queries/platform-metrics.ts
- src/features/analytics/server/queries/revenue-dashboard-data.ts
- src/features/analytics/shared/chart-config.ts
- src/features/analytics/ui/platform-dashboard.tsx
Edit
- src/features/analytics/server/queries/<metric>.ts
- src/features/analytics/shared/chart-config.ts
- src/features/analytics/ui/<card>.tsx
Steps
- Define the metric and date range.
- Aggregate on the server with bounded reads.
- Return only the fields the chart needs.
- Render empty and loading states.
- Add tests for range behavior and empty data.
Verify
- pnpm run test:features
- pnpm run quality
Recipe: add a database table
Add durable product data without leaving schema, generated types, RLS, grants, and feature persistence out of sync.
Inspect
- supabase/migrations/20260426000000_init.sql
- src/lib/supabase/server.ts
- src/lib/supabase/types.ts
Edit
- supabase/migrations/<timestamp>_<table>.sql
- src/lib/supabase/types.ts
- src/features/<domain>/server/persistence/<table>.ts
- src/features/<domain>/server/schemas/<table>.ts
Steps
- Define ownership, uniqueness, status, timestamps, and foreign keys before writing the migration.
- Add the table, constraints, RLS policies, and grants in a migration.
- Regenerate generated Supabase types.
- Keep reads and writes in feature-owned persistence or query files.
- Add tests for invalid input, auth failures, and duplicate writes when relevant.
Verify
- pnpm run contract:migrations
- pnpm run supabase:types
- pnpm run test:features
- pnpm run quality
Common mistakes
- Do not expose a new table before deciding its RLS policy.
- Do not put one-off seed or repair data into durable schema migrations.
Recipe: add a protected API route
Add an API boundary that validates input, fails closed on auth, delegates real work, and returns a stable response shape.
Inspect
- src/lib/auth/guards/api.ts
- src/app/api/billing/checkout/route.ts
- src/features/billing/server/checkout/service.ts
- src/features/contact/shared/schemas.ts
- src/features/contact/server/services.ts
Edit
- src/app/api/<feature>/route.ts
- src/features/<domain>/server/services/<use-case>.ts
- src/features/<domain>/server/schemas/<payload>.ts
Steps
- Keep the route handler thin: parse request, call the auth guard, validate payload, delegate to a service, and return the response.
- Put business rules in the service, not the route.
- Validate request bodies with a feature-owned schema.
- Return safe error copy without leaking provider payloads or secrets.
- Add API or feature tests for unauthenticated, invalid, and successful requests.
Verify
- pnpm run test:api
- pnpm run test:features
- pnpm run quality
Common mistakes
- Do not trust user ids from request JSON when server identity is available.
- Do not call provider SDKs directly from generic route glue.
Recipe: add an email template
Add a transactional message without bypassing event definitions, runtime rendering, delivery registration, or outbox retry behavior.
Inspect
- src/features/notifications/server/notification-events.ts
- src/features/notifications/server/templates/shared/defaults.ts
- src/features/notifications/server/templates/services/runtime.ts
- src/features/notifications/server/templates/actions/management.ts
- src/features/notifications/server/delivery/registry.ts
Edit
- src/features/notifications/server/notification-events.ts
- src/features/notifications/server/templates/shared/defaults.ts
- src/features/notifications/server/templates/shared/types.ts
Steps
- Add or select the notification event before writing template content.
- Define default subject, body, and required variables.
- Confirm runtime rendering validates variables and produces the expected output.
- Send through the existing delivery and outbox path.
- Add focused tests for missing variables, rendered content, and delivery registration.
Verify
- pnpm run test:features
- pnpm run quality
Common mistakes
- Do not send directly from feature code when the outbox should own retries.
- Do not log rendered messages if they can contain user data.
Recipe: add a billing product
Add a product or price without breaking provider setup, checkout eligibility, customer projection, or webhook handling.
Inspect
- src/features/billing/server/runtime/provider.ts
- src/features/billing/server/catalog/configuration.ts
- src/features/billing/server/runtime/readiness.ts
- src/features/billing/server/checkout/service.ts
- src/app/api/webhooks/stripe/billing/route.ts
Edit
- BILLING_CATALOG_JSON
- BILLING_ENABLED_MODELS
- src/features/billing/server/catalog/configuration.ts
Steps
- Choose the billing model and provider before editing catalog data.
- Add provider price ids and product metadata to the catalog.
- Confirm setup checks understand the new item.
- Run a test checkout for the new price.
- Confirm webhook projection updates the local customer billing state.
Verify
- pnpm run test:features
- pnpm run quality
- Open /billing/setup and complete a provider test checkout.
Common mistakes
- Do not mix provider price ids across providers.
- Do not skip webhook verification after checkout succeeds.
Recipe: add a settings field
Add configurable product behavior without splitting validation, defaults, normalization, persistence, and UI state.
Inspect
- src/features/settings/server/actions/website-settings.ts
- src/features/settings/server/schemas/settings.ts
- src/features/settings/server/normalizer/settings-normalizer.ts
- src/features/settings/server/config/defaults.ts
- src/features/settings/server/persistence/settings.ts
- src/features/settings/ui/website-form.tsx
Edit
- src/features/settings/server/schemas/settings.ts
- src/features/settings/server/config/defaults.ts
- src/features/settings/server/normalizer/settings-normalizer.ts
- src/features/settings/ui/website-form.tsx
Steps
- Add the field to the server schema and define its safe default.
- Normalize persisted values before returning settings to UI.
- Update the settings form with loading, invalid, and saved states.
- Persist through the existing settings action.
- Add tests for defaulting, invalid values, and updates.
Verify
- pnpm run test:features
- pnpm run quality
Common mistakes
- Do not rely on UI-only validation.
- Do not introduce fallback env names for settings that should be database-backed.
Recipe: add a waitlist field
Capture a new lead attribute while preserving validation, duplicate handling, settings, notifications, and rate limits.
Inspect
- src/features/waitlist/server/signup/schemas/payload.ts
- src/features/waitlist/server/signup/actions/submit-route.ts
- src/features/waitlist/server/signup/services/submit.ts
- src/features/waitlist/server/signup/persistence/signups.ts
- src/features/waitlist/server/settings.ts
- src/lib/security/rate-limit/policy.ts
Edit
- supabase/migrations/<timestamp>_waitlist_field.sql
- src/features/waitlist/server/signup/schemas/payload.ts
- src/features/waitlist/server/signup/services/submit.ts
- src/features/waitlist/server/signup/persistence/signups.ts
Steps
- Decide whether the field is required, optional, or admin-only.
- Add durable storage and regenerate generated types if the field is persisted.
- Update payload validation and persistence mapping together.
- Preserve duplicate signup handling and notification side effects.
- Add tests for missing, invalid, duplicate, and successful submissions.
Verify
- pnpm run contract:migrations
- pnpm run supabase:types
- pnpm run test:features
- pnpm run quality
Common mistakes
- Do not store unbounded free-form text without validation.
- Do not bypass rate limits when adding a new public field.
Recipe: remove a module safely
Remove an unwanted module without leaving routes, nav items, schema, jobs, webhooks, env vars, tests, or docs pointing at dead behavior.
Inspect
- src/lib/auth/rbac/nav-config.ts
- src/lib/auth/rbac/features.ts
- src/features/settings/server/actions/website-settings.ts
- src/features/notifications/server/notification-events.ts
- supabase/migrations/20260426000000_init.sql
Edit
- src/app/<route>
- src/features/<domain>
- src/lib/auth/rbac/nav-config.ts
- src/lib/auth/rbac/features.ts
- supabase/migrations/<timestamp>_<removal>.sql
Steps
- Inventory public routes, dashboard routes, API routes, feature code, settings, jobs, webhooks, env vars, database tables, tests, and docs references.
- Disable navigation and access before deleting shared data or provider behavior.
- Remove or migrate durable data with an explicit migration plan.
- Delete tests only after replacing them with coverage for the intended absence when the boundary is risky.
- Run search for the module name and route paths before shipping.
Verify
- pnpm run test:features
- pnpm run test:contracts
- pnpm run quality
Common mistakes
- Do not remove only the UI and leave API routes or jobs active.
- Do not drop tables without a buyer-owned data export or migration decision.