The Dodo Supabase Subscription Starter is a production-ready boilerplate that combines authentication, payments, and subscription management into a single, cohesive solution.
Authentication
Google OAuth Integration
Seamless authentication powered by Supabase Auth with Google OAuth provider.
Browser Client Client-side authentication using @supabase/ssr import { createBrowserClient } from "@supabase/ssr" ;
export const createClient = () =>
createBrowserClient (
process . env . NEXT_PUBLIC_SUPABASE_URL ! ,
process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY !
);
Server Client Server-side authentication with cookie management import { createServerClient } from "@supabase/ssr" ;
export async function createClient () {
const cookieStore = await cookies ();
return createServerClient ( ... );
}
Users are automatically redirected to /dashboard after successful authentication. The authentication flow is handled entirely through server actions for enhanced security.
User Management
Automatic user creation and synchronization between Supabase Auth and your database:
const users = pgTable ( "users" , {
supabaseUserId: text ( "supabase_user_id" ). primaryKey (),
dodoCustomerId: text ( "dodo_customer_id" ). notNull (),
currentSubscriptionId: text ( "current_subscription_id" ),
createdAt: timestamp ( "created_at" , { withTimezone: true }),
updatedAt: timestamp ( "updated_at" , { withTimezone: true }),
});
Payment Processing
Dodo Payments Integration
Complete subscription lifecycle management through Dodo Payments API.
import DodoPayments from "dodopayments" ;
export const dodoClient = new DodoPayments ({
bearerToken: process . env . DODO_PAYMENTS_API_KEY ! ,
environment: process . env . DODO_PAYMENTS_ENVIRONMENT !
});
Supported Payment Operations
Subscribe Create new subscriptions with automatic customer creation
Change Plans Seamlessly upgrade or downgrade between subscription tiers
Cancel Cancel subscriptions at the next billing date or immediately
Restore Restore cancelled subscriptions before the end date
Invoice History View complete payment history with detailed billing information
Payment Methods Manage credit cards and payment preferences
Dynamic Product Management
Products are configured in Dodo Payments dashboard with feature lists stored in metadata:
{
"features" : [
"Unlimited projects" ,
"Priority support" ,
"Advanced analytics" ,
"API access"
]
}
The application automatically extracts and displays these features in the pricing UI:
const features = JSON . parse (
currentPlanDetails ?. metadata . features || "[]"
);
Free Tier Support
Built-in free plan configuration for users without active subscriptions:
export const freePlan = {
name: "Hobby Plan" ,
description: "Free plan for Dodo Supabase subscription starter" ,
price: 0 ,
features: [
"Access to basic tools" ,
"Single user support" ,
"5 projects limit" ,
"Community support" ,
"Basic analytics"
]
};
Webhook Integration
Real-Time Event Processing
Supabase Edge Function handles webhook events from Dodo Payments:
// Webhook verification
const webhook = new Webhook ( dodoWebhookSecret );
await webhook . verify ( rawBody , webhookHeaders );
// Event routing
switch ( event . type ) {
case "payment.succeeded" :
case "payment.failed" :
await managePayment ( event );
break ;
case "subscription.active" :
case "subscription.plan_changed" :
await manageSubscription ( event );
await updateUserTier ( event . data );
break ;
case "subscription.cancelled" :
case "subscription.expired" :
await manageSubscription ( event );
await downgradeToHobbyPlan ( event . data );
break ;
}
Supported Events
payment.succeeded - Payment completed successfully
payment.failed - Payment failed or declined
payment.processing - Payment is being processed
payment.cancelled - Payment was cancelled
subscription.active - Subscription activated
subscription.plan_changed - User changed subscription plan
subscription.renewed - Subscription renewed for next period
subscription.cancelled - Subscription cancelled by user
subscription.expired - Subscription ended
subscription.failed - Subscription payment failed
subscription.on_hold - Subscription temporarily paused
Webhook secrets must be configured in both Dodo Payments dashboard and your Supabase environment variables for secure event verification.
Database Management
Drizzle ORM Schema
Type-safe database operations with PostgreSQL:
lib/drizzle/schema.ts
lib/drizzle/schema.ts
export const subscriptions = pgTable ( "subscriptions" , {
subscriptionId: text ( "subscription_id" ). primaryKey (),
userId: text ( "user_id" ). references (() => users . supabaseUserId ),
recurringPreTaxAmount: real ( "recurring_pre_tax_amount" ). notNull (),
status: text ( "status" ). notNull (),
productId: text ( "product_id" ). notNull (),
nextBillingDate: timestamp ( "next_billing_date" ),
cancelAtNextBillingDate: boolean ( "cancel_at_next_billing_date" ),
metadata: jsonb ( "metadata" ),
billing: jsonb ( "billing" ). notNull (),
});
Database Operations
# Push schema changes to Supabase
bun run db:push
# Open Drizzle Studio for database management
bun run db:studio
# Generate migrations
bun run db:generate
Modern UI Components
Tech Stack
Next.js 15 Latest Next.js with App Router and Server Components
React 19 Cutting-edge React features including concurrent rendering
Tailwind CSS 4 Modern utility-first CSS framework
Radix UI Accessible component primitives
Component Library
shadcn/ui : Pre-built, customizable components
Lucide Icons : Beautiful, consistent icon set
next-themes : Dark mode support with theme switching
Sonner : Elegant toast notifications
Motion : Advanced animations and transitions
Comprehensive Dashboard
Dashboard Features
Subscription Overview
View current plan, billing cycle, and next payment date
Plan Management
Upgrade, downgrade, or cancel subscriptions with real-time updates
Invoice History
Complete payment history with downloadable invoices
Account Management
Profile settings and account deletion
Server Actions
All dashboard operations use Next.js server actions for security:
"use server" ;
export async function getUser () : ServerActionRes < User > {
const supabase = await createClient ();
const { data : { user } } = await supabase . auth . getUser ();
if ( ! user ) {
return { success: false , error: "User not found" };
}
return { success: true , data: user };
}
Developer Experience
Quick Start Scripts
# Development with Turbopack
bun run dev
# Production build
bun run build
# Type checking
bun run typecheck
# Deploy webhook function
bun run deploy:webhook --project-ref YOUR_PROJECT
Environment Configuration
# Supabase Configuration
NEXT_PUBLIC_SUPABASE_URL = https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY = your-anon-key
SUPABASE_SERVICE_ROLE_KEY = your-service-role-key
# Database
DATABASE_URL = postgresql://postgres:[email protected] :5432/postgres
# Dodo Payments
DODO_PAYMENTS_API_KEY = your-api-key
DODO_WEBHOOK_SECRET = your-webhook-secret
DODO_PAYMENTS_ENVIRONMENT = test_mode
Type Safety
Full TypeScript support with auto-generated types:
export type SelectUser = typeof users . $inferSelect ;
export type InsertUser = typeof users . $inferInsert ;
export type SelectSubscription = typeof subscriptions . $inferSelect ;
export type InsertSubscription = typeof subscriptions . $inferInsert ;
All database operations are type-safe, with TypeScript interfaces automatically inferred from the Drizzle schema.