Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Set Up a Subscription Model for Premium Content in FlutterFlow

Create a subscription paywall by storing a subscriptionTier field on each user document. Display premium content with a lock overlay and blurred preview for non-subscribers. Tapping locked content opens a Bottom Sheet with pricing tiers. The Subscribe button redirects to Stripe Checkout, and a Cloud Function webhook updates the user tier on successful payment. Conditional Visibility gates all premium content on both the UI and Firestore Security Rules level.

What you'll learn

  • How to design a Firestore data model for subscription tiers and premium content
  • How to build a paywall Bottom Sheet with pricing comparison
  • How to connect Stripe Checkout for subscription payments via Cloud Function
  • How to gate premium content using Conditional Visibility and Firestore Security Rules
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner7 min read25-35 minFlutterFlow Free+March 2026RapidDev Engineering Team
TL;DR

Create a subscription paywall by storing a subscriptionTier field on each user document. Display premium content with a lock overlay and blurred preview for non-subscribers. Tapping locked content opens a Bottom Sheet with pricing tiers. The Subscribe button redirects to Stripe Checkout, and a Cloud Function webhook updates the user tier on successful payment. Conditional Visibility gates all premium content on both the UI and Firestore Security Rules level.

Building a Content Subscription Paywall in FlutterFlow

Subscription models let you monetize content by offering free teasers and locking full access behind a paywall. This tutorial walks through creating a complete subscription system in FlutterFlow with Stripe payments, content gating at both the UI and database level, and a polished paywall experience for non-subscribers.

Prerequisites

  • A FlutterFlow project with Firestore and Firebase Authentication configured
  • A Stripe account with API keys stored in Cloud Function environment variables
  • A content collection in Firestore with at least a few test documents
  • Basic familiarity with FlutterFlow Action Flows

Step-by-step guide

1

Set up the Firestore data model for subscriptions and content

Add a subscriptionTier field (String: 'free', 'monthly', 'annual') and subscriptionExpiry (Timestamp) to your users collection. In your content collection, add an isPremium boolean field. Create a subscription_plans collection with documents for each tier containing: name, priceMonthly, priceAnnual, features (String Array), and stripePriceId. This structure lets you manage plans from Firestore without redeploying the app.

Expected result: Firestore has users with subscriptionTier, content with isPremium, and subscription_plans with Stripe Price IDs.

2

Build the premium content card with lock overlay

Create a ContentCard Component with parameters: title, thumbnailUrl, isPremium, isSubscribed. Use a Stack widget. The bottom layer shows the content thumbnail and title. Add a second layer with Conditional Visibility set to isPremium == true AND isSubscribed == false. This overlay layer is a Container with a semi-transparent black background, a lock Icon centered, and a gradient fade from transparent to dark at the bottom. Below the Stack, show a teaser Text (first 100 characters of content) with a 'Subscribe to continue reading' Text below it, both conditionally visible for non-subscribers.

Expected result: Premium content shows a lock overlay and blurred teaser for non-subscribers, while subscribers see the full content.

3

Create the paywall Bottom Sheet with pricing tiers

Create a PaywallSheet Component. Use a Column with: a headline Text 'Unlock Premium Content', a subtitle Text with the value proposition, then a Row of two Container cards side by side. The left card shows Monthly pricing and the right shows Annual pricing with a 'Save 40%' badge Container positioned in the top-right corner. Each card has: tier name Text, price Text (large headlineMedium), features ListView from subscription_plans query, and a Subscribe Button. Use a Page State variable selectedPlan to track which tier is tapped. Highlight the selected card with a Primary border.

Expected result: A polished pricing Bottom Sheet appears with monthly and annual options, feature lists, and subscribe buttons.

4

Connect Stripe Checkout via Cloud Function

Create a Cloud Function createCheckoutSession that receives userId and stripePriceId, creates a Stripe Checkout Session in subscription mode with success and cancel URLs, and returns the session URL. In FlutterFlow, on the Subscribe Button tap: call the Cloud Function via API Call with the selected plan stripePriceId, then use Launch URL action to open the returned Checkout URL. Create a second Cloud Function as a Stripe webhook listener for checkout.session.completed that reads the customer email, finds the user document, and updates subscriptionTier and subscriptionExpiry.

createCheckoutSession.js
1// Cloud Function: createCheckoutSession
2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
3
4exports.createCheckoutSession = async (req, res) => {
5 const { userId, priceId } = req.body;
6 const session = await stripe.checkout.sessions.create({
7 mode: 'subscription',
8 payment_method_types: ['card'],
9 line_items: [{ price: priceId, quantity: 1 }],
10 success_url: 'https://yourapp.com/success',
11 cancel_url: 'https://yourapp.com/cancel',
12 metadata: { userId },
13 });
14 res.json({ url: session.url });
15};

Expected result: Tapping Subscribe opens Stripe Checkout. After payment, the webhook updates the user subscriptionTier in Firestore.

5

Gate content with Conditional Visibility and Firestore Security Rules

On every page or Component that displays premium content, wrap the full content in a Container with Conditional Visibility: isPremium == false OR currentUserDocument.subscriptionTier != 'free'. This ensures non-subscribers only see the teaser. Critically, also add Firestore Security Rules that block reads on premium content fields for users without a valid subscription. In rules, check get(/databases/$(database)/documents/users/$(request.auth.uid)).data.subscriptionTier != 'free' before allowing reads on premium documents. UI gating alone is not secure because users can query Firestore directly.

Expected result: Premium content is gated at both the FlutterFlow UI level and the Firestore Security Rules level.

6

Add a subscription badge and manage renewals

On the user profile page, add a Container badge showing the current subscription tier with a colored background (gold for annual, blue for monthly, grey for free). Display subscriptionExpiry as 'Renews on [date]' Text below the badge. Add a Manage Subscription Button that opens the Stripe Customer Portal via a Cloud Function returning the portal URL. For expiry handling, add an On Page Load action that checks if subscriptionExpiry is in the past and subscriptionTier is not free, then calls a Cloud Function to verify the subscription status with Stripe and updates the tier if expired.

Expected result: Users see their subscription badge on their profile and can manage billing through the Stripe Customer Portal.

Complete working example

FlutterFlow Subscription Setup
1FIRESTORE DATA MODEL:
2 users/{uid}
3 subscriptionTier: "free" | "monthly" | "annual"
4 subscriptionExpiry: Timestamp
5 stripeCustomerId: String
6
7 content/{contentId}
8 title: String
9 body: String
10 thumbnailUrl: String
11 isPremium: Boolean
12 category: String
13
14 subscription_plans/{planId}
15 name: "Monthly" | "Annual"
16 priceMonthly: Number
17 priceAnnual: Number
18 features: [String]
19 stripePriceId: String
20
21CONTENT CARD COMPONENT:
22 Stack
23 Column
24 Image (thumbnailUrl)
25 Text (title)
26 Container (lock overlay)
27 Conditional Visibility: isPremium && !isSubscribed
28 Background: semi-transparent black
29 Icon (lock, white, size 40)
30 Text ("Subscribe to unlock")
31
32PAYWALL BOTTOM SHEET:
33 Column
34 Text ("Unlock Premium Content", headlineMedium)
35 Text ("Get unlimited access to all content")
36 Row
37 Container (Monthly card)
38 Text ("Monthly")
39 Text ("$9.99/mo", headlineLarge)
40 ListView (features)
41 Button ("Subscribe Monthly")
42 Container (Annual card + Save badge)
43 Text ("Annual")
44 Text ("$5.99/mo", headlineLarge)
45 ListView (features)
46 Button ("Subscribe Annual")
47 Text ("Cancel anytime", caption)
48
49ACTION FLOW Subscribe Button:
50 1. API Call: createCheckoutSession(userId, stripePriceId)
51 2. Launch URL: response.url
52
53WEBHOOK checkout.session.completed:
54 1. Read metadata.userId
55 2. Update users/{userId}: subscriptionTier, subscriptionExpiry
56
57FIRESTORE SECURITY RULES:
58 match /content/{doc} {
59 allow read: if !resource.data.isPremium
60 || get(/users/$(request.auth.uid)).data.subscriptionTier != 'free';
61 }

Common mistakes

Why it's a problem: Only hiding premium content in the UI without Firestore Security Rules

How to avoid: Add Firestore Security Rules that check the user subscriptionTier before allowing reads on premium content documents.

Why it's a problem: Crediting the subscription tier on the success redirect URL instead of the webhook

How to avoid: Always update the subscriptionTier in the Stripe webhook handler (checkout.session.completed), which is guaranteed to fire on successful payment.

Why it's a problem: Hardcoding Stripe Price IDs in the FlutterFlow app

How to avoid: Store Stripe Price IDs in the subscription_plans Firestore collection. The app reads them dynamically, so you can update pricing without redeploying.

Best practices

  • Gate premium content at both the UI (Conditional Visibility) and database (Firestore Security Rules) levels
  • Use Stripe webhooks for subscription status updates, never client-side success redirects
  • Store Stripe Price IDs in Firestore for dynamic pricing updates without app redeployment
  • Show a compelling teaser (first paragraph + blurred remainder) to convert free users
  • Display subscription expiry and renewal dates clearly on the user profile
  • Provide a Stripe Customer Portal link for users to manage their own billing
  • Check subscription expiry on app load to handle lapsed subscriptions gracefully
  • Include a free trial option in your Stripe Checkout configuration to reduce conversion friction

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I need to build a subscription paywall in FlutterFlow with Stripe. Show me the Firestore data model for users with subscription tiers, premium content gating with lock overlays, a pricing Bottom Sheet, Stripe Checkout integration via Cloud Function, and Firestore Security Rules that block premium content for non-subscribers.

FlutterFlow Prompt

Create a content feed page where some items have a lock icon overlay. Add a bottom sheet with two pricing cards side by side for monthly and annual subscriptions, each with a subscribe button.

Frequently asked questions

Can I offer a free trial before charging for the subscription?

Yes. When creating the Stripe Checkout Session, add subscription_data.trial_period_days (e.g., 7 or 14). The user enters payment info but is not charged until the trial ends. Update subscriptionTier immediately so they get access during the trial.

How do I handle subscription cancellations?

Listen for the customer.subscription.deleted Stripe webhook event. When received, update the user subscriptionTier to 'free' and clear subscriptionExpiry. Optionally show a 'Your subscription has ended' banner with a resubscribe button.

Can I have more than two subscription tiers?

Yes. Add more documents to subscription_plans in Firestore and create corresponding Stripe Price objects. Adjust your paywall Bottom Sheet layout to accommodate additional plan cards, using a horizontal ListView if you have more than three tiers.

How do I prevent content sharing between subscribers and non-subscribers?

Firestore Security Rules are your enforcement layer. Even if a subscriber shares a direct link, the non-subscriber cannot load the full content because the Security Rules check their subscriptionTier before allowing the read.

Does this work with Apple and Google in-app purchases instead of Stripe?

FlutterFlow supports RevenueCat integration for in-app purchases on iOS and Android. The content gating logic remains the same; only the payment flow changes from Stripe Checkout to the native purchase dialog.

Can RapidDev help implement a full subscription system?

Yes. RapidDev can build the complete subscription flow including Stripe integration, webhook handling, content gating, free trials, upgrade/downgrade logic, and Apple/Google in-app purchase support.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.