Create DaaS Project
Initialize a new Data-as-a-Service application, ready for spec-driven development.
BEFORE generating ANY
.tsxfiles (pages, layouts, components), you MUSTread_filethe Buildpad component reference at.github/skills/buildpad-reference/SKILL.md. This loads the full 40+ component catalog with import paths and usage patterns. Skipping this step leads to raw Mantine usage violations.
Architecture
DaaS apps use a two-tier architecture:
Frontend (Next.js) → DaaS Backend (DaaS-compatible API) → Supabase (PostgreSQL)
Step 0: Verify Prerequisites (MANDATORY)
Before creating any project, automatically check that all required tools are installed:
node --version && pnpm --version && git --version && npx --version
| Tool | Min Version | Install Guide |
|---|---|---|
| Node.js | v24 LTS | https://nodejs.org/en/download — macOS: brew install node@24; Linux: fnm install 24; Windows: installer or winget install OpenJS.NodeJS.LTS |
| pnpm | v10+ | https://pnpm.io/installation — corepack enable && corepack prepare pnpm@latest --activate |
| Git | v2.30+ | https://git-scm.com/downloads — macOS: xcode-select --install; Linux: sudo apt-get install git; Windows: winget install Git.Git |
| npx | (bundled) | Comes with Node.js — reinstall Node if missing |
If any tool is missing, install it before proceeding. See Prerequisites reference for detailed installation instructions per OS.
Do NOT skip prerequisites. Commands like
npx,pnpm dev, andcreate-next-appwill fail without them.
Project Creation (Choose ONE approach)
Recommended: Bootstrap (single command)
mkdir -p /path/to/my-project
npx @buildpad/cli@latest bootstrap --cwd /path/to/my-project
Bootstrap creates: Next.js skeleton + 40+ Buildpad components + installs ALL npm deps.
Alternative: create-next-app + CLI add (empty directory only)
npx create-next-app@latest my-project --typescript --tailwind --eslint --app --src-dir=no --import-alias="@/*" --use-pnpm --turbopack
npx @buildpad/cli@latest add --all --with-api --cwd /path/to/my-project
cd /path/to/my-project && pnpm install
Never mix both approaches.
Spec-Driven Development (MANDATORY)
This skill delivers the project foundation. Every feature after that follows the spec workflow (see the spec-driven-development skill): requirements.md → design.md → tasks.md in .kiro/specs/<feature>/, implemented task by task. In Kiro use native specs; in other IDEs use the /buildpad-spec-* skills.
Foundation Deliverables
- Project scaffolding (Next.js + TypeScript + Mantine v8)
- Environment config (
.env.localwith Supabase + DaaS URLs) - Auth proxy routes (login, logout, user, callback) — installed by CLI
app/(authenticated)/layout.tsxwithDaaSProviderWrapper+ScopeProvider(NOT in root layout)DaaSProviderWrapperusingonAuthStateChange(notgetSession) +getHeaderswired to scope cookie- Logout route clears
daas_resource_uricookie - DaaS CORS configured with explicit origins +
cors_allow_credentials: true+X-Resource-Uriin allowed headers - Base layout and navigation with Mantine provider
- Test infrastructure (Playwright + Vitest)
- README with setup instructions
Post-Bootstrap Steps (MANDATORY — run after bootstrap every time)
Gap 9: If the workspace had an existing
node_modules(e.g. fromnpm install), bootstrap's internal install silently fails. Always run a clean install after bootstrap:
Remove-Item -Recurse -Force node_modules # Windows
rm -rf node_modules # macOS/Linux
pnpm install
DaaSProviderWrapper Pattern (REQUIRED)
Bug 19 + Bug 27: Always use
onAuthStateChange, NEVERgetSession—getSessioncan returnnullbefore cookies are parsed, causing 401 on first mount. Also passtokenas a sync prop AND only setready = truewhen the token is non-null —INITIAL_SESSIONcan fire withsession = nullwhen the access token is expired and Supabase is performing a silent refresh.
Bug 16: Always wiregetHeadersto forward the active scope cookie on every DaaS call.
// components/DaaSProviderWrapper.tsx
"use client";
import { DaaSProvider } from "@/lib/buildpad/services/daas-context";
import { createClient } from "@/lib/supabase/client";
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
export function DaaSProviderWrapper({ children }: { children: ReactNode }) {
const supabase = useMemo(() => createClient(), []);
const [ready, setReady] = useState(false);
const [tokenState, setTokenState] = useState<string | null>(null);
const tokenRef = useRef<string | null>(null);
useEffect(() => {
// onAuthStateChange fires INITIAL_SESSION after cookies are fully parsed —
// never use getSession() which can return null before cookie parsing finishes.
// Only set ready when tok is non-null: INITIAL_SESSION can fire with null
// session when the access token is expired and Supabase is doing a silent refresh.
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
const tok = session?.access_token ?? null;
tokenRef.current = tok;
setTokenState(tok);
if (tok) setReady(true);
});
return () => subscription.unsubscribe();
}, [supabase]);
const config = useMemo(
() => ({
url: process.env.NEXT_PUBLIC_BUILDPAD_DAAS_URL ?? "",
token: tokenState ?? undefined, // sync prop so DaaSProvider has token on first render
getToken: async () => tokenRef.current,
// Forward the active scope cookie on every DaaS call so scoped collections
// return data for the correct tenant (required for scope-based RBAC)
getHeaders: async () => {
const raw = document.cookie
.split("; ")
.find((r) => r.startsWith("daas_resource_uri="))
?.split("=")[1];
return raw ? { "X-Resource-Uri": decodeURIComponent(raw) } : {};
},
}),
[tokenState],
); // re-create config when token refreshes
if (!ready) return null; // block children until auth is fully initialised
return <DaaSProvider config={config}>{children}</DaaSProvider>;
}
Authenticated layout structure (Bug 22 — providers MUST NOT be in root layout):
// app/(authenticated)/layout.tsx
import { DaaSProviderWrapper } from "@/components/DaaSProviderWrapper";
import { ScopeProvider } from "@/lib/contexts/ScopeContext";
export default function AuthenticatedLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<DaaSProviderWrapper>
<ScopeProvider>{children}</ScopeProvider>
</DaaSProviderWrapper>
);
}
CORS Configuration (MANDATORY after project creation)
Bug 25 + Bug 17: The DaaS default
cors_origins: ["*"]+ Buildpad'scredentials: 'include'= every browser request blocked. AlsoX-Resource-Uriis not in the default allowed headers and must be added.
Run immediately after project creation, before wiring any frontend:
// mcp_daas_cors-settings — action: update
{
"cors_origins": [
"http://localhost:3000",
"http://localhost:3001",
"<mainAmplifyUrl from get_project_detail>"
],
"cors_allow_credentials": true,
"cors_allowed_headers": [
"Content-Type",
"Authorization",
"Origin",
"X-Requested-With",
"Accept",
"X-Resource-Uri"
],
"cors_max_age": 0
}
Never leave cors_origins: ["*"] in place — the browser will block every API call.
Buildpad-First Component Rule (MANDATORY — applies to ALL code generated during and after foundation setup)
EVERY form, input, list, and filter component MUST use Buildpad. The CLI bootstrap installs 40+ components — use them. Never fall back to raw Mantine form/input components.
| ❌ NEVER Use Directly | ✅ ALWAYS Use Buildpad Instead |
|---|---|
<TextInput> / <Textarea> from @mantine/core |
Input, Textarea from @/components/ui |
<Select> from @mantine/core |
SelectDropdown from @/components/ui |
<DatePicker> / <DateTimePicker> from @mantine/dates |
DateTime from @/components/ui |
<Switch> / <Checkbox> from @mantine/core |
Toggle, Boolean, SelectMultipleCheckbox |
<Dropzone> from @mantine/dropzone |
Upload, Files, FileImage from @/components/ui |
useForm from @mantine/form |
VForm or CollectionForm from @/components/ui |
Custom <Table> for collection data |
CollectionList from @/components/ui |
| Custom filter builder | FilterPanel from @/components/ui |
<NumberInput> from @mantine/core |
Input (with type="number") from @/components/ui |
<ColorInput> from @mantine/core |
Color from @/components/ui |
Allowed raw Mantine: Stack, Group, Button, Modal, Table (for non-collection data), Tabs, Paper, Text, Title, Badge, ActionIcon, Menu, Loader, Alert.
When generating ANY .tsx file (pages, components, forms, lists), check this table first. If the UI element has a Buildpad equivalent, you MUST use it.
Quick Reference: Common Patterns
// ✅ List page → CollectionList (NEVER custom table)
import { CollectionList } from "@/components/ui";
<CollectionList
collection="tasks"
enableSearch
enableFilter
enableCreate
enableDelete
/>;
// ✅ Form page → CollectionForm (NEVER useForm)
import { CollectionForm } from "@/components/ui/collection-form";
<CollectionForm
collection="tasks"
=> router.push(`/tasks/${item.id}`)}
/>;
// ✅ Individual field → Buildpad component (NEVER raw Mantine input)
import { Input } from "@/components/ui/input";
import { SelectDropdown } from "@/components/ui/select-dropdown";
import { DateTime } from "@/components/ui/datetime";
import { Toggle } from "@/components/ui/toggle";
Post-Generation Validation (MANDATORY)
After generating ANY .tsx files, run this check to catch Buildpad-First violations:
# Scan for forbidden raw Mantine form/input imports
grep -rn "from '@mantine/form'\|from '@mantine/dates'\|from '@mantine/dropzone'\|<TextInput\|<NumberInput\|<Select \|<Switch \|<Checkbox \|<DatePicker\|<Dropzone" app/ components/ 2>/dev/null
If any matches are found, replace them with Buildpad equivalents before proceeding.
Critical Rules
- Buildpad-First: All UI components come from Buildpad CLI — never create custom Input, Select, DateTime, Toggle, File upload, or Relations
- Auth via Proxy: Login MUST call
/api/auth/login, NEVERsupabase.auth.signInWithPassword()directly - No Direct DaaS Calls from Browser: All API calls go through Next.js API routes
- NEVER create files manually in
components/ui/orlib/buildpad/— CLI only - DaaSProvider in authenticated layout only: NEVER place
DaaSProviderWrapperorScopeProviderin the rootapp/layout.tsx. Always place them inapp/(authenticated)/layout.tsxso they fully unmount on logout and remount fresh on next login (Bug 22) - Mantine components require
'use client': ANY page or component that renders Mantine must have'use client'— Mantine compound components (Table.Tbody,Modal,Tabs.Panel) areundefinedin React Server Components (Bug 10)
Required Environment Variables
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
NEXT_PUBLIC_BUILDPAD_DAAS_URL=http://localhost:3000
After the Foundation
Start the first feature spec: /buildpad-discovery "<idea>" or /buildpad-spec-init "<feature description>" (in Kiro, create a spec from the Specs panel).
References
- Prerequisites check
- Project creation details
- Environment setup