Building subscription plans in Bubble involves creating a pricing table UI, handling plan selection and upgrade flows, and displaying the current plan status in user settings. This tutorial covers designing a Plan Option Set with pricing tiers, building a responsive pricing table page, connecting plan selection to Stripe for payment, and showing users their current plan with upgrade and downgrade options.
Overview: Subscription Plans in Bubble
This tutorial walks you through building the subscription plan experience in your Bubble app, from the pricing page to account management. You will design plan tiers, build the pricing table, and handle the plan lifecycle.
Prerequisites
- A Bubble app on any plan
- A Stripe account configured with subscription products
- The Stripe plugin installed or API Connector set up
- Basic understanding of Option Sets and Conditional formatting
Step-by-step guide
Design plan tiers using an Option Set
Design plan tiers using an Option Set
Go to the Data tab and create an Option Set called 'SubscriptionPlan'. Add options for each tier: Free, Pro, Business. Add attributes to each option: price_monthly (number), price_yearly (number), features (list of texts — each feature as a string), stripe_price_id_monthly (text), stripe_price_id_yearly (text), is_popular (yes/no — to highlight recommended plan), and display_order (number). This keeps all plan data in a fast-loading Option Set rather than the database.
Expected result: A SubscriptionPlan Option Set contains all plan tiers with pricing and feature information.
Build the pricing table UI
Build the pricing table UI
Create a pricing page with a monthly/yearly toggle at the top using two buttons or a toggle switch that sets a custom state 'billing_period' to 'monthly' or 'yearly'. Below it, add a Repeating Group with type SubscriptionPlan (Option Set), sorted by display_order. Each cell shows the plan name, price (conditional on billing_period state), feature list displayed in a nested Repeating Group of the features attribute, and a Select Plan button. Add conditional styling: when is_popular is yes, add a border color and 'Most Popular' badge. Add conditional on the Select Plan button: when plan = Current User's current_plan, change text to 'Current Plan' and make it not clickable.
Expected result: A professional pricing table displays all plans with monthly/yearly toggle and current plan indication.
Handle plan selection and Stripe checkout
Handle plan selection and Stripe checkout
When the Select Plan button is clicked, start the Stripe checkout flow. If using the Stripe plugin, add the 'Stripe Checkout' action with the appropriate stripe_price_id based on the selected plan and billing period. If using the API Connector, create a Stripe Checkout Session via POST to /v1/checkout/sessions with the price ID, success_url, and cancel_url. After successful payment, Stripe redirects to your success page where you update the User's plan. Add a field to User called 'subscription_plan' (type: SubscriptionPlan Option Set) and 'stripe_customer_id' (text).
Expected result: Clicking a plan button initiates Stripe checkout and updates the user's plan on success.
Display current plan in user settings
Display current plan in user settings
Create an account settings section showing the user's current subscription. Display the plan name, price, next billing date (from Stripe data), and features included. Add an 'Upgrade' button that links to the pricing page, and a 'Cancel Subscription' button that triggers a Stripe subscription cancellation workflow. Show a plan comparison highlighting what the user gains by upgrading. Use conditional visibility to hide upgrade button when on the highest tier and hide cancel when on the Free tier.
Expected result: Users can view their current plan details and access upgrade or cancellation options.
Set up Stripe webhook for subscription events
Set up Stripe webhook for subscription events
Create a backend workflow to receive Stripe webhooks. Enable Backend workflows in Settings → API, then create an exposed workflow called 'stripe_webhook'. Configure Stripe to send events to this URL. Handle key events: checkout.session.completed (set user's plan), customer.subscription.updated (handle plan changes), customer.subscription.deleted (revert to Free plan), and invoice.payment_failed (notify user of failed payment). Each event looks up the user by stripe_customer_id and updates their subscription_plan field accordingly.
Pro tip: Always verify the Stripe webhook signature to prevent spoofed events from modifying user subscriptions.
Expected result: Stripe events automatically update user subscription status in your Bubble database.
Complete working example
1SUBSCRIPTION PLAN SYSTEM SUMMARY2=====================================34OPTION SET:5 SubscriptionPlan: Free, Pro, Business6 Attributes: price_monthly, price_yearly,7 features (list), stripe_price_id_monthly,8 stripe_price_id_yearly, is_popular, display_order910PRICING PAGE:11 Toggle: monthly / yearly (custom state)12 RG: SubscriptionPlan sorted by display_order13 Cell: plan name, price (conditional), features,14 Select button15 Popular plan: border + badge16 Current plan: button shows 'Current Plan'1718CHECKOUT FLOW:19 Select Plan → Stripe Checkout20 price = plan's stripe_price_id21 success_url = /success?session={id}22 cancel_url = /pricing23 Success page:24 Retrieve session → Update User's plan2526USER FIELDS:27 subscription_plan (SubscriptionPlan)28 stripe_customer_id (text)29 subscription_status (text)3031ACCOUNT SETTINGS:32 Current plan display33 Upgrade button → pricing page34 Cancel button → Stripe cancellation35 Next billing date from Stripe3637WEBHOOK HANDLING:38 checkout.session.completed → Set plan39 subscription.updated → Update plan40 subscription.deleted → Revert to Free41 invoice.payment_failed → Notify userCommon mistakes when building Subscription Plans in Bubble
Why it's a problem: Storing plan data in the database instead of an Option Set
How to avoid: Use an Option Set for plan definitions. Only store the user's selected plan reference on the User record.
Why it's a problem: Not handling failed payments and subscription cancellations
How to avoid: Set up Stripe webhooks for invoice.payment_failed and subscription.deleted events to update user status
Why it's a problem: Allowing users to access premium features without checking their plan
How to avoid: Add plan-checking conditions on every premium feature and page, redirecting free users to the pricing page
Best practices
- Use Option Sets for plan definitions for fast, zero-WU loading
- Highlight the most popular plan to guide user choice
- Show a monthly/yearly toggle with savings percentage for yearly plans
- Always handle Stripe webhooks for subscription lifecycle events
- Check user plan status before showing any premium features
- Provide clear upgrade and downgrade paths in account settings
- Test the full subscription flow in Stripe test mode before going live
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a pricing page with three subscription tiers in my Bubble.io app connected to Stripe. How do I design the pricing table and handle plan changes?
Help me build a subscription system with a pricing table showing Free, Pro, and Business plans with monthly/yearly toggle, Stripe checkout integration, and plan management in user settings.
Frequently asked questions
Can I offer a free trial period?
Yes. Configure trial periods in Stripe when creating your subscription prices. Stripe handles the trial automatically and charges after it expires.
How do I gate features by plan?
Add conditions on premium elements and pages: visible or accessible only when Current User's subscription_plan is Pro or higher.
Can users switch between monthly and yearly billing?
Yes. Use Stripe's subscription update API to change the price. Handle prorated charges in your Stripe configuration.
What happens if a payment fails?
Stripe retries failed payments according to your retry settings. Use the invoice.payment_failed webhook to notify the user and optionally restrict access after repeated failures.
Do I need a paid Bubble plan for subscriptions?
The pricing page and Option Sets work on Free, but Stripe webhooks require backend workflows which need a paid Bubble plan.
Can RapidDev help set up subscription billing?
Yes. RapidDev can implement complete subscription billing systems in Bubble including Stripe integration, plan management, feature gating, and revenue analytics dashboards.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation