Subscriptions

Learn how to manage subscriptions in your application.

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.

index.ts
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",
          model: BillingModel.RECURRING, 
          interval: RecurringInterval.MONTH,
          trialDays: 7,
        },
        // Yearly
        {
          id: "price_yearly_or_variant_id",
          cost: 8900,
          currency: "usd",
          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 to usd).
  • model: Must be BillingModel.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.id should match a Stripe Price ID (price_...). Webhook events used for subscriptions include customer.subscription.*. See Stripe setup.
  • Lemon Squeezy: variant.id should match a Lemon Squeezy Variant ID. See Lemon Squeezy setup.
  • Polar: variant.id should match a Polar Product ID (Polar treats each “variant” as a separate product). Subscription events include subscription.created / subscription.updated. See Polar setup.

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Get TurboStarter