shipanysaas
Documentation
Back
  • Getting started
    • Install and run
    • Project structure
    • Configuration
    • Commands
  • Coding agents
    • Agent skills
  • Authentication
    • Email sign-in
    • OAuth providers
    • Two-step sign-in and passkeys
  • Database
    • Migrations
    • Row-level security
    • Database tests
    • Reading and writing data
  • Features
    • Teams and invitations
    • Email
    • File uploads
    • Blog, docs and changelog
  • Billing
    • Stripe and Lemon Squeezy
    • Pricing plans
    • Webhooks
  • Live demo
  • Pricing plans

    Define products, plans and line items in apps/web/config/billing.config.ts, and the rules the config schema enforces.

    Your plans live in apps/web/config/billing.config.ts. Out of the box it re-exports billing.sample.config.ts, whose Starter, Pro and Enterprise products use sample price ids and sample features. Copy the sample into billing.config.ts and replace the ids, prices and features with your own.

    The schema is createBillingSchema in packages/billing/core/src/create-billing-schema.ts. A config that breaks a rule fails with a message saying which one.

    Shape

    import { BillingProviderSchema, createBillingSchema } from '@repo/billing';
    
    const provider = BillingProviderSchema.parse(
      process.env.NEXT_PUBLIC_BILLING_PROVIDER,
    );
    
    export default createBillingSchema({
      provider,
      products: [
        {
          id: 'pro',
          name: 'Pro',
          description: 'For growing teams',
          currency: 'USD',
          features: ['Unlimited projects', 'Priority email support'],
          plans: [
            {
              id: 'pro-monthly',
              name: 'Pro Monthly',
              paymentType: 'recurring',
              interval: 'month',
              trialDays: 14,
              lineItems: [
                {
                  id: 'price_123', // Stripe price id or Lemon Squeezy variant id
                  name: 'Base',
                  cost: 29,
                  type: 'flat',
                },
              ],
            },
          ],
        },
      ],
    });
    

    Fields

    LevelFields
    Productid, name, description, currency, features (at least one), plans, and optional badge, highlighted, hidden, enableDiscountField
    Planid, name, paymentType (recurring or one-time), lineItems, and optional interval (month or year), trialDays, custom, label, buttonLabel, href
    Line itemid, name, cost, type (flat, per_seat or metered), and optional description, unit, tiers, setupFee

    Rules the schema enforces

    • Recurring plans need an interval; one-time plans must not have one, and can only contain flat items.
    • A plan has at most one flat and one per_seat item.
    • metered items need a unit and tiers, and a cost of 0 (the tiers carry the price).
    • A custom plan has no line items. Use label, buttonLabel and href for something like "Contact us".
    • Plan ids are unique within a product, and line item ids within a plan.
    • With Lemon Squeezy, a plan has exactly one line item.

    Per-seat plans

    For a per_seat item on a team plan, the kit keeps the quantity equal to the team's member count: it updates when someone accepts an invitation or a member is removed (account-per-seat-billing.service.ts in packages/features/team-accounts).

    Showing the plans

    The billing pages in the app read this config. For a public pricing page, PricingTable from @repo/billing-gateway/marketing renders the same plans. The kit's own /pricing page currently shows the kit's offer (apps/web/config/kit-offer.config.ts) instead; mount PricingTable there when you replace it with your product.