Pick a SaaS Boilerplate Based on Your Business Model
TL;DR
Most developers pick a boilerplate based on the tech stack. The better approach: pick it based on your business model. A B2B SaaS with organizations and seats needs Supastarter's multi-tenancy. A B2C subscription app with a landing page and PaymentSheet needs ShipFast's simplicity. A marketplace needs Stripe Connect — and almost no boilerplate ships with that. Match the boilerplate to the billing and user model, not just the framework.
Key Takeaways
- B2C subscriptions (individual users paying monthly): ShipFast, Next SaaS Starter, T3 Stack
- B2B SaaS (companies buying seats): Supastarter, Makerkit, Bedrock — multi-tenancy required
- Usage-based billing: no boilerplate ships this pre-built — add Stripe Meters manually
- Marketplace / two-sided: Stripe Connect — you'll customize any boilerplate significantly
- Freemium: any starter with feature flags — add PostHog or LaunchDarkly
- Open core (free OSS + paid features): T3 Stack or Epic Stack as the base
The Business Model Matrix
Business Model → Required Features → Best Boilerplate
─────────────────────────────────────────────────────────────────────
B2C subscription → User auth, Stripe → ShipFast, Next SaaS Starter
B2B per-seat → Orgs, members, RLS → Supastarter, Makerkit
B2B usage-based → Stripe Meters, usage tracking → T3 Stack (add meters)
Marketplace → Stripe Connect, escrow → Build custom (or fork Epic Stack)
Freemium → Feature flags, plan limits → Any + PostHog
One-time purchase → Simple checkout → ShipFast (simpler billing)
Open core → Public repo + auth gate → T3 Stack or Epic Stack
Agency/services → Multi-project dashboard → Custom (no boilerplate fits)
Model 1: B2C Subscription
Characteristics:
- Individual users (not companies) pay you directly
- Credit card on file, monthly/annual billing
- Users don't invite teammates
- Support volume is critical (one bad experience = churn)
What you need from a boilerplate:
- Clean auth flow (magic link, Google are highest converting)
- Stripe subscriptions + webhooks
- Email notifications
- Fast landing page (conversion matters)
- Basic dashboard
What you DON'T need:
- Organizations or team management
- Role-based permissions
- Multi-tenant database isolation
- Seat counting
Best choices:
// ShipFast: Stripe checkout in ~50 lines
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// ShipFast's apiCheckout.ts — already written for you:
export const createCheckout = async (priceId: string, userId: string) => {
const session = await stripe.checkout.sessions.create({
line_items: [{ price: priceId, quantity: 1 }],
mode: 'subscription',
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
client_reference_id: userId,
customer_email: userEmail,
});
return session.url;
};
Recommendation: ShipFast for simplicity, Next SaaS Starter for free.
Model 2: B2B Per-Seat SaaS
Characteristics:
- Companies (organizations) are your customers, not individuals
- Admins invite team members (seats)
- Billing is per seat × per month
- Users have different roles (admin, member, viewer)
What you need:
- Organization model (orgs own resources, not individual users)
- Invitation system (invite by email, accept invite flow)
- Role-based permissions (admin vs member)
- Per-seat billing with Stripe
- Row Level Security (data isolation between orgs)
What's wrong with B2C boilerplates for B2B:
// ❌ B2C pattern (data owned by user):
// Resources belong to individual users
const todos = await db.todo.findMany({
where: { userId: session.user.id }
});
// ✅ B2B pattern (data owned by organization):
// Resources belong to the org, users are members
const todos = await db.todo.findMany({
where: {
organizationId: session.user.activeOrganizationId,
// AND: user must be a member of this org (enforced by RLS or application logic)
}
});
Building this from a B2C boilerplate takes 2-3 weeks. Supastarter ships it:
// Supastarter's organization model (pre-built):
// app/dashboard/settings/members/page.tsx
// Invite a member:
const { error } = await supabase.functions.invoke('send-invitation', {
body: {
email: inviteeEmail,
organizationId: currentOrg.id,
role: 'member', // 'admin' | 'member' | 'viewer'
}
});
// RLS policy (pre-written by Supastarter):
// CREATE POLICY "members can read org resources"
// ON resources FOR SELECT
// USING (
// organization_id IN (
// SELECT organization_id FROM memberships
// WHERE user_id = auth.uid()
// )
// );
Recommendation: Supastarter ($299) or Makerkit ($299+) — the multi-tenancy foundation saves weeks.
Model 3: Usage-Based Billing
Characteristics:
- Customers pay per API call, per GB processed, per message sent, per seat-hour
- Complex billing that varies month to month
- Need accurate usage tracking + metering
The bad news: no boilerplate ships usage-based billing pre-built. Stripe Meters (the modern Stripe solution) is too new and too specific to be in generic starters.
What to do:
// Add Stripe Meters to any boilerplate:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// Track usage event:
export async function trackApiCall(customerId: string) {
// Report usage to Stripe Billing Meter:
await stripe.billing.meterEvents.create({
event_name: 'api_call',
payload: {
stripe_customer_id: customerId,
value: '1', // 1 API call
},
});
}
// Create usage-based subscription:
await stripe.subscriptions.create({
customer: customerId,
items: [{
price: 'price_api_calls_per_unit', // Price with billing_scheme=per_unit
}],
});
// In your API route:
export async function POST(req: Request) {
const user = await getUser(req);
await trackApiCall(user.stripeCustomerId);
// ... handle actual API request
}
Starting boilerplate for usage-based: T3 Stack — clean foundation, add meters yourself. Don't pay $299 for Supastarter's multi-tenancy if you don't need it.
Model 4: Marketplace / Two-Sided
Characteristics:
- Buyers and sellers (or creators and consumers)
- You take a platform fee
- Payments flow through you to sellers
- Stripe Connect required for payouts
The reality: No major boilerplate ships Stripe Connect pre-built. It's complex, highly variable, and requires understanding of connected accounts vs standard accounts.
// Stripe Connect setup (you'll add this yourself):
const account = await stripe.accounts.create({
type: 'express', // or 'standard', 'custom'
country: 'US',
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
});
// Payment with platform fee:
const paymentIntent = await stripe.paymentIntents.create({
amount: 5000, // $50.00 total
currency: 'usd',
application_fee_amount: 500, // $5.00 platform fee (10%)
transfer_data: {
destination: seller.stripeAccountId,
},
});
Recommendation: Start with T3 Stack or Epic Stack (clean, customizable), add Connect yourself. Budget 2-3 weeks for the Connect integration — it's complex regardless of boilerplate.
Model 5: Freemium
Characteristics:
- Free tier with feature limits (3 projects, 100 API calls/month)
- Upgrade prompt when limits hit
- Need to enforce limits reliably
// Feature gate pattern in any boilerplate:
// middleware or server action check
export async function checkProjectLimit(userId: string) {
const user = await db.user.findUnique({
where: { id: userId },
include: { projects: true, subscription: true },
});
const plan = user?.subscription?.plan ?? 'free';
const limits = {
free: 3,
pro: 100,
enterprise: Infinity,
};
if (user!.projects.length >= limits[plan as keyof typeof limits]) {
throw new Error('PLAN_LIMIT_EXCEEDED');
}
}
// In your tRPC/API route:
export const createProject = protectedProcedure
.input(createProjectSchema)
.mutation(async ({ ctx, input }) => {
await checkProjectLimit(ctx.session.user.id);
return ctx.db.project.create({ data: input });
});
Any boilerplate supports this pattern. The sophistication is in your limit enforcement logic, not the boilerplate.
Recommendation: ShipFast (add limit checks manually) or Makerkit (has plan configuration built-in).
Quick Reference: Business Model → Boilerplate
You're building... Get this boilerplate:
Simple B2C subscription ShipFast ($199) — fast
Simple B2C, no budget Next SaaS Starter (free)
B2B with teams and orgs Supastarter ($299) — multi-tenancy
B2B, complex features Makerkit ($299+) — plugin system
Usage-based billing T3 Stack (free) + add Stripe Meters
Marketplace/two-sided T3 Stack or Epic Stack (free) + Stripe Connect
Dev tool / API product T3 Stack + tRPC
Content + SaaS hybrid Makerkit (blog built-in) or T3+Contentlayer
Full open source Open SaaS (free, Wasp) or T3 Stack
Need multi-language from day 1: Supastarter (i18n built-in)
Team is Rails/Python: Laravel Spark, Wave, or djaodjin
Team is SvelteKit: SvelteShip or Indie Kit
Enterprise (SOC2, SSO, audit log): Bedrock ($499+)
The 15-Minute Evaluation Framework
Before buying any boilerplate, answer these 5 questions:
-
Who pays? (individual users or companies/teams) → Individual = need simple Stripe subscriptions → Teams = need multi-tenancy
-
How do they pay? (monthly flat / per seat / per usage) → Flat = any boilerplate → Per seat = needs seat counting + billing → Usage = add Stripe Meters manually
-
What's the launch timeline? → < 1 week = ShipFast (fastest) → 1-4 weeks = any major boilerplate → 1+ month = build what you need
-
What's the long-term complexity? → Simple = free boilerplate, grow later → Complex from day 1 = invest in Supastarter/Makerkit foundation
-
What's your team's framework expertise? → Strong Next.js = ShipFast/Supastarter → Strong Remix = Epic Stack/SaaSrock → Strong SvelteKit = SvelteShip/Indie Kit
Find the right boilerplate for your business model at StarterPick.