Vibestacks LogoVibestacks
IntegrationsStripe & Payments

Plans & Pricing

Configure subscription plans, pricing tiers, features, and limits in Vibestacks. Learn how to create products in Stripe and sync them with your app.

All subscription plans are defined in config/app.ts. This single source of truth powers your pricing page, checkout flow, and feature limits.

What's Configurable

Everything in the Stripe integration is controlled from config/app.ts. No need to touch implementation code.

FeatureHow to ConfigureDefault
Enable/disable StripestripeConfig.enabledtrue
Auto-create Stripe customerstripeConfig.createCustomerOnSignUptrue
Number of plansAdd/remove from plans[]3 plans
Free tierSet priceId: null on a planEnabled
Annual billingAdd annualPriceId to planEnabled
Free trialsAdd trial: { enabled: true, days: 14 }Pro plan only
Popular badgeSet popular: true on one planPro plan
Plan limitsCustomize limits objectprojects, aiCredits
Redirect URLsCustomize urls object/dashboard/billing

Zero Code Changes

Want to disable trials? Set trial: null. Want 5 plans instead of 3? Add them to the array. Want to disable Stripe entirely? Set enabled: false. The UI and logic adapt automatically.

Quick Examples

config/app.ts
// Disable Stripe entirely (maybe you're not ready for payments yet)
export const stripeConfig = {
  enabled: false,  // Everything Stripe-related is disabled
  // ...
}

// Disable trials on all plans
plans: [
  { name: "starter", trial: null, /* ... */ },
  { name: "pro", trial: null, /* ... */ },
]

// Add a 4th "Enterprise" plan
plans: [
  { name: "free", /* ... */ },
  { name: "starter", /* ... */ },
  { name: "pro", /* ... */ },
  { name: "enterprise", priceId: "price_xxx", /* ... */ },  // Just add it!
]

// Change trial from 14 days to 7 days
{ name: "pro", trial: { enabled: true, days: 7 }, /* ... */ }

Plan Structure

Each plan in stripeConfig.plans has this structure:

config/app.ts
{
  name: "pro",                    // Plan identifier (lowercase)
  priceId: "price_xxx",           // Stripe price ID for monthly billing
  annualPriceId: "price_xxx",     // Stripe price ID for annual billing (optional)
  displayPrice: {                 // Prices shown in UI (in cents)
    monthly: 4900,                // $49/month
    annual: 49000,                // $490/year
  },
  popular: true,                  // Show "Popular" badge
  trial: { enabled: true, days: 14 },  // Free trial config (optional)
  features: [                     // Marketing copy for pricing page
    "Unlimited projects",
    "Priority support",
  ],
  limits: {                       // Programmatic limits for your app
    projects: -1,                 // -1 = unlimited
    aiCredits: -1,
  },
}

Creating Products in Stripe

Before configuring plans, create the corresponding products and prices in Stripe.

Go to Products

Navigate to dashboard.stripe.com/products and click Add product.

Create a Product

  • Name: "Pro Plan" (or your plan name)
  • Description: Optional description
  • Click Add product

Add Monthly Price

In the product page, click Add price:

  • Price: $49.00
  • Billing period: Monthly
  • Price ID: Note this price_xxx value

Add Annual Price (Optional)

Click Add another price:

  • Price: $490.00 (or your discounted annual rate)
  • Billing period: Yearly
  • Price ID: Note this price_xxx value

Update Your Config

Copy the price IDs to your config:

config/app.ts
{
  name: "pro",
  priceId: "price_1234567890",        // Monthly price ID
  annualPriceId: "price_0987654321",  // Annual price ID
  // ...
}

Keep Prices in Sync

The displayPrice values in your config are for UI display only. The actual charge is determined by Stripe via the priceId. Always keep these in sync when you change prices.

Default Plans

Vibestacks comes with three example plans:

config/app.ts
plans: [
  {
    name: "free",
    priceId: null,                    // No Stripe price = free tier
    annualPriceId: null,
    displayPrice: { monthly: 0, annual: 0 },
    popular: false,
    trial: null,
    features: [
      "1 project",
      "100 AI credits/month",
      "Community support",
    ],
    limits: {
      projects: 1,
      aiCredits: 100,
    },
  },
  {
    name: "starter",
    priceId: "price_starter_monthly",     // TODO: Replace
    annualPriceId: "price_starter_annual", // TODO: Replace
    displayPrice: { monthly: 1900, annual: 19000 },
    popular: false,
    trial: null,
    features: [
      "Everything in Free",
      "5 projects",
      "1,000 AI credits/month",
      "Priority email support",
      "Custom domains",
    ],
    limits: {
      projects: 5,
      aiCredits: 1000,
    },
  },
  {
    name: "pro",
    priceId: "price_pro_monthly",     // TODO: Replace
    annualPriceId: "price_pro_annual", // TODO: Replace
    displayPrice: { monthly: 4900, annual: 49000 },
    popular: true,
    trial: { enabled: true, days: 14 },
    features: [
      "Everything in Starter",
      "Unlimited projects",
      "Unlimited AI credits",
      "Priority support",
      "Advanced analytics",
      "API access",
      "Team collaboration",
    ],
    limits: {
      projects: -1,
      aiCredits: -1,
    },
  },
],

Free Tier

To create a free tier, set priceId: null:

{
  name: "free",
  priceId: null,        // This makes it a free tier
  annualPriceId: null,
  // ...
}

Free tier users won't go through Stripe checkout. They simply use your app with the defined limits.

Features vs Limits

There's an important distinction:

FieldPurposeUsed By
featuresHuman-readable marketing copyPricing page UI
limitsMachine-readable constraintsYour application code

Features are what you show users on the pricing page. They can be anything:

features: [
  "Unlimited projects",           // Capability
  "Priority support",             // Service level
  "99.9% uptime SLA",            // Guarantee
  "Custom branding",              // Feature
]

Limits are what your code checks to enforce restrictions:

limits: {
  projects: -1,      // -1 = unlimited
  aiCredits: 5000,   // Hard limit
  teamMembers: 10,   // Hard limit
  storage: 50,       // e.g., GB
}

Using Limits in Your App

Import the helper functions to check limits:

import { getPlan } from "@/config/app";

// Get a user's plan
const userPlan = getPlan("pro");

// Check a specific limit
const projectLimit = userPlan?.limits.projects ?? 0;
const isUnlimited = projectLimit === -1;

// Example: Check if user can create more projects
function canCreateProject(currentCount: number, planName: string): boolean {
  const plan = getPlan(planName);
  if (!plan) return false;
  
  const limit = plan.limits.projects;
  if (limit === -1) return true; // Unlimited
  
  return currentCount < limit;
}

Adding Custom Limits

You can add any limits you need:

limits: {
  projects: 5,
  aiCredits: 1000,
  teamMembers: 3,      // Custom
  storage: 10,         // Custom (GB)
  apiCalls: 10000,     // Custom (per month)
  customDomains: 1,    // Custom
}

Just ensure all plans have the same limit keys for type safety.

Pricing Display

The displayPrice field is in cents (like Stripe):

displayPrice: {
  monthly: 4900,   // $49.00
  annual: 49000,   // $490.00 (save ~17%)
}

Use the formatPrice helper to display prices:

import { formatPrice } from "@/config/app";

formatPrice(4900);   // "$49"
formatPrice(4999);   // "$49.99"
formatPrice(0);      // "$0"

Annual Discounts

To offer annual discounts:

  1. Create a separate annual price in Stripe with the discounted amount
  2. Set annualPriceId in your config
  3. The pricing page automatically shows a toggle
{
  name: "pro",
  priceId: "price_monthly_xxx",
  annualPriceId: "price_annual_xxx",  // Enables annual billing
  displayPrice: {
    monthly: 4900,    // $49/month
    annual: 39000,    // $390/year ($32.50/month = ~33% off)
  },
}

The pricing UI will show:

  • Monthly view: "$49 / mo"
  • Annual view: "$32.50 / mo" with "Billed annually" subtitle

Mark one plan as popular to highlight it:

{
  name: "pro",
  popular: true,  // Shows "Popular" badge
  // ...
}

Only one plan should have popular: true.

Helper Functions

Vibestacks provides these helpers in config/app.ts:

import { 
  getPlan, 
  getPaidPlans, 
  formatPrice 
} from "@/config/app";

// Get a specific plan by name
const pro = getPlan("pro");

// Get all paid plans (excludes free tier)
const paidPlans = getPaidPlans();

// Format cents to currency
const display = formatPrice(4900); // "$49"

Next Steps

  • Trials — Add free trial periods to your plans
  • Webhooks — Handle subscription events