Subscriptions
Learn how to manage subscriptions in your application.
For the complete documentation index, see llms.txt. Prefer markdown by appending.mdto documentation URLs or sendingAccept: text/markdown.
TurboStarter supports subscription billing (recurring payments) on the web across providers like Stripe, Lemon Squeezy, and Polar.
Subscriptions are configured in your billing config using:
- plans: what you sell (Free, Premium, Enterprise, etc.)
- variants: how you sell it (monthly, yearly, trials, etc.)
Configuration
Subscriptions are represented as variants with model: BillingModel.RECURRING.
The example below shows a standard flat recurring subscription. Per-seat and metered subscriptions use the same recurring model, but add extra fields such as type, meterId, or tier configuration. See Per-seat and Metered usage for those setups.
export const config = billingConfigSchema.parse({
plans: [
{
id: BillingPlan.PREMIUM,
name: "Premium",
description: "Become a power user and gain benefits",
badge: "Bestseller",
features: [
"Unlimited projects",
"Priority support",
"Advanced integrations",
"Team collaboration",
"Analytics dashboard",
],
variants: [
// Monthly
{
id: "price_monthly_or_variant_id",
cost: 1900,
currency: "usd",
type: BillingType.FLAT,
model: BillingModel.RECURRING,
interval: RecurringInterval.MONTH,
trialDays: 7,
},
// Yearly
{
id: "price_yearly_or_variant_id",
cost: 8900,
currency: "usd",
type: BillingType.FLAT,
model: BillingModel.RECURRING,
interval: RecurringInterval.YEAR,
trialDays: 7,
},
],
},
],
}) satisfies BillingConfig;Breaking down the fields:
id: Provider identifier for this recurring price/variant/product.cost: Amount in the smallest currency unit (e.g. cents). Used for UI; provider charges the real amount.currency: Currency code (defaults tousd).type: UsuallyBillingType.FLATfor a standard subscription. Other recurring billing types are available.model: Must beBillingModel.RECURRING.interval: Required for recurring variants (RecurringInterval.MONTH,RecurringInterval.YEAR, etc.).trialDays: Optional trial length in days.
Match IDs exactly
The variant.id value must match what your billing provider expects (Stripe price ID, Lemon Squeezy variant ID, Polar product ID, etc.). A mismatch is the #1 reason why a checkout can't be created.
Provider notes
- Stripe:
variant.idshould match a Stripe Price ID (price_...). Webhook events used for subscriptions includecustomer.subscription.*. See Stripe setup. - Lemon Squeezy:
variant.idshould match a Lemon Squeezy Variant ID. See Lemon Squeezy setup. - Polar:
variant.idshould match a Polar Product ID (Polar treats each “variant” as a separate product). Subscription events includesubscription.created/subscription.updated. See Polar setup.
How is this guide?
Last updated on