Project Structure
This guide explains the folder structure and organization of the Warp boilerplate project.
Overview
your-app/
├── app/ # Next.js app router
│ ├── api/ # API routes
│ │ └── webhooks/ # Whop webhook handlers
│ ├── dashboard/ # Protected dashboard
│ ├── discover/ # Discover/browse page
│ ├── experiences/ # Experience pages
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ └── globals.css # Global styles
├── components/ # React components
│ └── example-components.tsx # Example UI components
├── db/ # Database (Drizzle ORM)
│ ├── schema.ts # Database schema
│ └── index.ts # Database client
├── lib/ # Utility libraries
│ ├── whop-sdk.ts # Whop SDK setup
│ └── whop/ # Whop API utilities
│ ├── access.ts
│ ├── checkout.ts
│ ├── companies.ts
│ ├── experiences.ts
│ ├── members.ts
│ ├── memberships.ts
│ ├── moderation.ts
│ ├── notifications.ts
│ ├── payments.ts
│ ├── plans.ts
│ ├── products.ts
│ ├── promo-codes.ts
│ ├── users.ts
│ ├── webhooks.ts
│ └── websockets.ts
├── biome.json # Biome linter config
├── drizzle.config.ts # Drizzle ORM config
├── next.config.ts # Next.js config
├── package.json # Dependencies
├── tailwind.config.ts # Tailwind CSS config
└── tsconfig.json # TypeScript configNote: The actual structure depends on which UI variant you choose (coss-ui, frosted-ui, or headless). The structure above shows the shared foundation.
Key Directories
/app
Next.js 15 App Router directory containing all pages and routes.
Subdirectories:
/api/webhooks- Whop webhook handlers for events/dashboard- Protected dashboard page/discover- Discover/browse experiences/experiences- Individual experience pages
Key Files:
layout.tsx- Root layout with providerspage.tsx- Home/landing pageglobals.css- Global CSS and Tailwind imports
/components
Reusable React components. The structure varies by UI variant:
coss-ui variant:
- Includes shadcn/ui components in
/uifolder components.jsonfor shadcn configuration
frosted-ui variant:
example-components.tsx- Pre-built example components
headless variant:
- Minimal structure, bring your own components
Adding shadcn/ui components (coss-ui):
npx shadcn@latest add button/db
Database schema and client configuration using Drizzle ORM.
Files:
-
schema.ts- Database table definitionsexport const users = pgTable('users', { id: text('id').primaryKey(), email: text('email').notNull(), // ... }); -
index.ts- Database client initializationimport { drizzle } from 'drizzle-orm/postgres-js'; export const db = drizzle(client);
Usage:
import { db } from '@/db';
import { users } from '@/db/schema';
const allUsers = await db.select().from(users);/lib
Core utility functions and SDK configurations.
Key Files:
whop-sdk.ts- Whop SDK setupimport { WhopSDK } from '@whop/sdk'; export const whop = new WhopSDK({ apiKey: process.env.WHOP_API_KEY });
/whop Subfolder:
Contains all Whop API utility functions organized by module:
lib/whop/
├── access.ts # Access control
├── checkout.ts # Checkout sessions
├── companies.ts # Company management
├── experiences.ts # Experience management
├── members.ts # Member utilities
├── memberships.ts # Subscription management
├── moderation.ts # User moderation
├── notifications.ts # Push notifications
├── payments.ts # Payment processing
├── plans.ts # Pricing plans
├── products.ts # Product management
├── promo-codes.ts # Promo code management
├── users.ts # User utilities
├── webhooks.ts # Webhook management
└── websockets.ts # WebSocket utilitiesConfiguration Files
next.config.ts
Next.js configuration including:
- Build settings
- Environment variables
- Redirects and rewrites
- Image optimization
const config: NextConfig = {
// Your configuration
};drizzle.config.ts
Drizzle ORM configuration for database migrations:
export default {
schema: './db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!
}
};tailwind.config.ts
Tailwind CSS configuration:
const config: Config = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}'
],
theme: {
extend: {
// Custom theme
}
}
};biome.json
Biome linter and formatter configuration:
{
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"linter": {
"enabled": true,
"rules": {
// Linting rules
}
}
}components.json
shadcn/ui configuration (only in coss-ui variant):
{
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "zinc"
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}Note: This file only exists in the coss-ui variant. The frosted-ui and headless variants do not include it.
Path Aliases
The project uses TypeScript path aliases for cleaner imports:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}Usage:
// ✅ Good - using alias
import { db } from '@/db';
import { getCurrentUser } from '@/lib/whop/users';
import { whop } from '@/lib/whop-sdk';
// ❌ Avoid - relative paths
import { db } from '../../db';Environment Variables
Store environment variables in .env.local:
# Whop
WHOP_API_KEY=your_api_key
WHOP_CLIENT_ID=your_client_id
WHOP_CLIENT_SECRET=your_client_secret
# Database
DATABASE_URL=postgresql://...
# App
NEXT_PUBLIC_APP_URL=http://localhost:3000Access in code:
// Server-side
const apiKey = process.env.WHOP_API_KEY;
// Client-side (must start with NEXT_PUBLIC_)
const appUrl = process.env.NEXT_PUBLIC_APP_URL;Adding New Features
1. Add a New Page
Create a file in /app:
// app/pricing/page.tsx
export default function PricingPage() {
return <div>Pricing</div>;
}Accessible at: /pricing
2. Add a New Component
Create in /components:
// components/pricing-card.tsx
export function PricingCard({ plan }) {
return <div>{plan.name}</div>;
}3. Add a New API Route
Create in /app/api:
// app/api/my-route/route.ts
import { getCurrentUser } from '@/lib/whop/users';
import { headers } from 'next/headers';
export async function GET() {
const user = await getCurrentUser(await headers());
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
return Response.json({ message: 'Hello!' });
}Accessible at: /api/my-route
4. Add Database Tables
Update /db/schema.ts:
export const subscriptions = pgTable('subscriptions', {
id: text('id').primaryKey(),
userId: text('user_id').notNull(),
planId: text('plan_id').notNull(),
status: text('status').notNull(),
createdAt: timestamp('created_at').defaultNow()
});Generate and run migration:
pnpm db:generate
pnpm db:migrate5. Add Utility Functions
Create in /lib/whop:
// lib/whop/custom.ts
import { whop } from '../whop-sdk';
export async function myCustomFunction(userId: string) {
// Your custom logic using Whop SDK
const user = await whop.users.get(userId);
return user;
}Import and use:
import { myCustomFunction } from '@/lib/whop/custom';Best Practices
File Organization
// ✅ Good - organized by feature
app/
dashboard/
page.tsx
analytics/
page.tsx
settings/
page.tsx
// ❌ Avoid - flat structure
app/
dashboard.tsx
dashboard-analytics.tsx
dashboard-settings.tsxComponent Structure
// ✅ Good - component with types
type PricingCardProps = {
plan: Plan;
onSelect: (id: string) => void;
};
export function PricingCard({ plan, onSelect }: PricingCardProps) {
return <div onClick={() => onSelect(plan.id)}>{plan.name}</div>;
}
// ❌ Avoid - no types
export function PricingCard({ plan, onSelect }) {
return <div>{plan.name}</div>;
}Import Organization
// ✅ Good - organized imports
// 1. External dependencies
import { useState } from 'react';
// 2. Internal utilities
import { db } from '@/db';
import { getCurrentUser } from '@/lib/whop/users';
import { whop } from '@/lib/whop-sdk';
// 3. Types
import type { User } from '@/types';
// 4. Relative imports
import { MyComponent } from './my-component';Related
- Introduction - Getting started guide
- Installation - Setup instructions
- API Utilities - API helper functions
