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.
| Feature | How to Configure | Default |
|---|---|---|
| Enable/disable Stripe | stripeConfig.enabled | true |
| Auto-create Stripe customer | stripeConfig.createCustomerOnSignUp | true |
| Number of plans | Add/remove from plans[] | 3 plans |
| Free tier | Set priceId: null on a plan | Enabled |
| Annual billing | Add annualPriceId to plan | Enabled |
| Free trials | Add trial: { enabled: true, days: 14 } | Pro plan only |
| Popular badge | Set popular: true on one plan | Pro plan |
| Plan limits | Customize limits object | projects, aiCredits |
| Redirect URLs | Customize 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
// 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:
{
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_xxxvalue
Add Annual Price (Optional)
Click Add another price:
- Price: $490.00 (or your discounted annual rate)
- Billing period: Yearly
- Price ID: Note this
price_xxxvalue
Update Your Config
Copy the price IDs to your config:
{
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:
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:
| Field | Purpose | Used By |
|---|---|---|
features | Human-readable marketing copy | Pricing page UI |
limits | Machine-readable constraints | Your 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:
- Create a separate annual price in Stripe with the discounted amount
- Set
annualPriceIdin your config - 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
Popular Badge
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
Configuration
Set up Stripe API keys, webhook secrets, and configure the Stripe integration in Vibestacks. Step-by-step guide for test and production environments.
Free Trials
Configure free trial periods for your subscription plans. Learn how trials work, prevent abuse, and handle trial lifecycle events.