How to Build a Recurring Revenue Product on Bubble.io: The Subscription SaaS Guide
A subscription SaaS built on Bubble.io is one of the highest-value products a non-technical founder can launch. The specific architecture decisions that separate a hobby project from a commercial product: subscription state management, access gating, trial mechanics, and the failed payment handling that protects MRR.
The Commercial Architecture
A Bubble.io application becomes a subscription SaaS product when it has four commercial architecture components in place: subscription state management (the User record knows whether the user is on a free trial, an active paid plan, or a cancelled/lapsed subscription); access gating (features and data are only accessible to users whose subscription state entitles them to access); trial mechanics (a defined trial period during which users can access the product before committing to payment); and failed payment handling (an automated process that responds when a payment fails, sends the user a notification, retries the payment, and eventually restricts access if the payment is not resolved). A Bubble.io application without all four of these components is a demo, not a subscription SaaS product.
How to Track and Enforce Subscription Status
The subscription status field
Add a subscription_status field (option set with values: Trial, Active, Past Due, Cancelled, Expired) to the User or Account data type. This field is the single source of truth for the user’s commercial relationship with the product. It is set by Stripe webhook handlers when subscription events occur and is checked by access-gating conditions throughout the application.
The subscription tier field
Add a subscription_tier field (option set with values matching your pricing tiers: Starter, Professional, Scale) to the User or Account data type. This field determines which features the user has access to within their subscription. It is set when the subscription is created and updated when the user upgrades or downgrades.
The trial end date field
Add a trial_end_date field (date type) to the User or Account data type. Set this field when the trial is created (trial_end_date = Current date/time + 14 days). Use this field to display the trial countdown in the product UI and to determine when to restrict trial access.
Access gating with subscription status checks
Every feature, page, or data operation that is restricted to paid subscribers should check the user’s subscription_status before allowing access. In Bubble.io, implement this as a conditional redirect on restricted pages: ‘When this page is loaded, if Current User’s subscription_status is not Active and is not Trial, redirect to the upgrade page.’ For feature-level gating (a specific button or data operation), use conditional visibility: ‘This element is visible when Current User’s subscription_status is Active OR subscription_status is Trial.’
🔗 Related reading on sasolutionspk.com
Bubble.io Stripe Integration: The Complete Guide for SaaS Founders
The complete Stripe integration guide that implements the subscription state machine described in this post — the webhook handlers, the subscription creation workflows, and the failed payment handling.
Bubble SaaS Complete Checklist
The full pre-launch checklist for a Bubble.io subscription SaaS — including the subscription state and access gating tests that must pass before going live.
The Dunning System
Day 0: Payment fails — webhook received
When Stripe sends an invoice.payment_failed webhook, the Bubble.io API Workflow handler sets the user’s subscription_status to Past Due and triggers Email 1 of the dunning sequence: a personal-style email from the founder that acknowledges the payment failed, provides a direct link to update the payment method, and does not yet restrict access.
Day 3: First retry fails — second email
Stripe’s Smart Retry automatically retries the failed payment. If the retry fails (and Stripe sends another invoice.payment_failed webhook), the Bubble.io handler triggers Email 2: a more urgent message noting that access will be restricted in [X] days if the payment is not resolved, with a prominent link to the billing page.
Day 7: Grace period expires — access restricted
After 7 days of Past Due status, a Bubble.io scheduled workflow runs daily to check for users whose subscription_status has been Past Due for more than 7 days. For these users, the workflow sets subscription_status to Expired, which triggers the access gating conditions throughout the application to redirect these users to a specific ‘Your access has expired’ page rather than to the main product.
Day 14: Final outreach — win-back email
14 days after the initial failure, a personalised email from the founder is sent to all Expired users: ‘We have been holding your account data. If you would like to reactivate, here is a direct link.’ Providing a one-click reactivation link (that takes the user directly to the billing update page with their account pre-selected) consistently recovers 15-25% of lapsed subscribers in SA’s experience.
Q: How do I let users downgrade their subscription on Bubble.io?
Build a subscription management page in the product with a Downgrade option that uses Stripe’s Update Subscription action with the new (lower) Price ID. Configure the update to take effect at the end of the current billing period (set proration_behavior to ‘none’ and set the update to schedule at period end) so the user retains access to their current tier until the period ends and then automatically moves to the lower tier. Update the subscription_tier field in Bubble.io via the customer.subscription.updated webhook when the downgrade takes effect.
Q: Should I restrict access immediately when a trial expires or offer a grace period?
SA recommends a 3-day grace period after trial expiry before restricting access. During the grace period, display a prominent in-app banner (‘Your trial ended 2 days ago — upgrade to keep your access and your data’) but do not redirect users away from the product. Users who are still using the product during the grace period are the most likely to convert; restricting access immediately removes the opportunity to convert during the peak engagement window.
Q: How do I display the correct pricing page based on the user’s current subscription status?
Build a dynamic pricing page that shows different content based on the current user’s subscription_status: Trial users see the upgrade options with a ‘Your trial ends in X days’ countdown; Active users see their current plan with upgrade and downgrade options; Past Due users see a ‘Resolve your payment issue’ prompt with a link to the billing page; Cancelled users see a win-back offer with a reactivation link. A single dynamic pricing page that adapts to the user’s subscription state is significantly more effective than separate pages for each state.
Ready to Build Your MVP?
SA Solutions builds MVPs in weeks using Bubble.io. Start with a free audit or scope your build in 48 hours with a Discovery Sprint.