Project: Multi-Tier SaaS Subscriptions with Data Sync
In this project, we will implement multi-tier SaaS memberships. Users can select between monthly or yearly subscription options, sign up via hosted checkouts, and trigger database role updates automatically via webhooks.
1. Webhook Role Mapping Rules
When mapping Lemon Squeezy variants to user roles inside your database, use a configuration object:
// src/config/subscriptionTiers.ts
export const subscriptionTiers = {
"variant_11111": { name: "Pro Monthly", role: "pro", limit: 100 },
"variant_22222": { name: "Pro Yearly", role: "pro", limit: 100 },
"variant_33333": { name: "Enterprise Monthly", role: "enterprise", limit: 1000 },
};2. Ingesting Upgrades and Plan Changes
When the subscription_updated webhook hits your route:
- Identify the new variant ID.
- Update the user role status in PostgreSQL to unlock relevant feature boundaries.
// app/api/webhooks/lemonsqueezy/route.ts (Subscription update snippet)
import { prisma } from "../../../lib/prisma";
import { subscriptionTiers } from "../../../config/subscriptionTiers";
export async function handleSubscriptionUpdate(event: any) {
const data = event.data;
const attributes = data.attributes;
const subscriptionId = data.id;
const variantId = String(attributes.variant_id);
// 1. Find the mapped role configuration
const tierConfig = subscriptionTiers[variantId as keyof typeof subscriptionTiers];
if (!tierConfig) {
console.error("Unknown variant ID received:", variantId);
return;
}
// 2. Update the user account limits and roles
await prisma.user.updateMany({
where: { lemonSubscriptionId: subscriptionId },
data: {
pricingTier: tierConfig.role,
projectLimit: tierConfig.limit,
subscriptionActive: attributes.status === "active",
},
});
console.log(`Updated user membership to: ${tierConfig.name}`);
}3. Frontend Integration Validation
To verify the integration:
- Open your billing settings page.
- Select a pricing plan card.
- After completing payment, confirm that your database user profile
pricingTierswitches to the upgraded state and the user is redirected to their Dashboard.
Published on Last updated: