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

How to Build a Custom Coupon and Discount System in FlutterFlow

Build a coupon engine using a Firestore coupons collection storing code, type (percentage or fixed), value, minimum order amount, max uses, current uses, and expiration date. Users enter a coupon code in a TextField, a Cloud Function validates it against all rules, and the discount is applied to the checkout total via Page State. An admin CRUD page lets you create, edit, and deactivate coupons with real-time usage statistics.

What you'll learn

  • How to design a Firestore coupon collection with validation rules
  • How to validate coupon codes server-side to prevent client bypass
  • How to calculate and apply percentage and fixed-amount discounts at checkout
  • How to build an admin panel for creating and managing coupons
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read20-25 minFlutterFlow Free+ (Cloud Function recommended for validation)March 2026RapidDev Engineering Team
TL;DR

Build a coupon engine using a Firestore coupons collection storing code, type (percentage or fixed), value, minimum order amount, max uses, current uses, and expiration date. Users enter a coupon code in a TextField, a Cloud Function validates it against all rules, and the discount is applied to the checkout total via Page State. An admin CRUD page lets you create, edit, and deactivate coupons with real-time usage statistics.

Coupon engine with server-side validation and admin management

Discount codes drive conversions, but a poorly built coupon system is easy to exploit. This tutorial builds a secure coupon engine in FlutterFlow: a Firestore collection stores coupon rules (type, value, limits, expiry), a Cloud Function validates codes server-side so users cannot bypass checks, and an admin panel lets you create, edit, and monitor coupon usage. The system supports both percentage and fixed-amount discounts with minimum order thresholds.

Prerequisites

  • A FlutterFlow project with Firebase/Firestore connected
  • A checkout or cart page where the discount will be applied
  • Basic understanding of Action Flows and Page State variables
  • Firebase Cloud Functions enabled for server-side validation

Step-by-step guide

1

Create the Firestore coupons collection

In Firestore, create a coupons collection with fields: code (String, uppercase, unique), type (String: 'percentage' or 'fixed'), value (Double: e.g., 15.0 for 15% or 10.0 for $10 off), minOrderAmount (Double: minimum cart total required), maxUses (Integer: total redemptions allowed, 0 for unlimited), currentUses (Integer, default 0), expiresAt (Timestamp), isActive (Boolean, default true), and applicableCategories (List of Strings, empty means all categories). Set Firestore rules: read allowed for authenticated users (needed for admin panel), write restricted to admin role users only. The code field must be stored uppercase and matched uppercase during validation.

Expected result: A coupons collection is ready in Firestore with all validation fields defined.

2

Build the coupon input UI on the checkout page

On your checkout page, add a Row below the order summary containing a TextField (hint text: 'Enter coupon code', text capitalization: Characters) and a Button labeled 'Apply'. Add three Page State variables: appliedCouponCode (String), discountAmount (Double, default 0), and couponError (String). Below the Row, add a Text widget for couponError (red color, Conditional Visibility: couponError is not empty) and a Container showing the applied discount (green background, showing 'Coupon SAVE20 applied: -$15.00', Conditional Visibility: discountAmount > 0). Include a small 'Remove' IconButton in the discount Container that resets all three Page State variables to their defaults. Update the order total Text to subtract discountAmount from the cart subtotal.

Expected result: The checkout page has a coupon code input field, Apply button, error message area, and a discount display that updates the total.

3

Create a Cloud Function to validate coupon codes

Create a callable Cloud Function named validateCoupon that receives the coupon code and the current order total. The function performs these checks in order: (1) look up the coupon document by code (document ID), return error if not found; (2) check isActive is true; (3) check expiresAt is in the future; (4) check currentUses < maxUses (or maxUses == 0 for unlimited); (5) check order total >= minOrderAmount. If all checks pass, calculate the discount: for percentage type, multiply order total by value/100 and cap at the order total; for fixed type, use the value directly but cap at the order total. Return the discount amount and coupon details. This server-side validation prevents users from bypassing checks by manipulating the client.

validateCoupon.js
1// Cloud Function: validateCoupon
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4
5exports.validateCoupon = functions.https.onCall(async (data, context) => {
6 const { code, orderTotal } = data;
7 if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');
8
9 const couponRef = admin.firestore().collection('coupons').doc(code.toUpperCase());
10 const couponSnap = await couponRef.get();
11
12 if (!couponSnap.exists) throw new functions.https.HttpsError('not-found', 'Invalid coupon code');
13
14 const coupon = couponSnap.data();
15 if (!coupon.isActive) throw new functions.https.HttpsError('failed-precondition', 'Coupon is no longer active');
16 if (coupon.expiresAt.toDate() < new Date()) throw new functions.https.HttpsError('failed-precondition', 'Coupon has expired');
17 if (coupon.maxUses > 0 && coupon.currentUses >= coupon.maxUses) throw new functions.https.HttpsError('resource-exhausted', 'Coupon usage limit reached');
18 if (orderTotal < coupon.minOrderAmount) throw new functions.https.HttpsError('failed-precondition', `Minimum order $${coupon.minOrderAmount} required`);
19
20 let discount = coupon.type === 'percentage'
21 ? Math.min(orderTotal * (coupon.value / 100), orderTotal)
22 : Math.min(coupon.value, orderTotal);
23
24 discount = Math.round(discount * 100) / 100;
25 return { discount, type: coupon.type, value: coupon.value, code: code.toUpperCase() };
26});

Expected result: The Cloud Function validates all coupon rules server-side and returns the calculated discount amount or an error message.

4

Wire the Apply button to the Cloud Function and update checkout totals

On the Apply button's On Tap Action Flow: first, convert the TextField value to uppercase. Then call the validateCoupon Cloud Function using a Backend Call action, passing the coupon code and the current cart subtotal. On success: set Page State appliedCouponCode to the returned code, discountAmount to the returned discount value, and clear couponError. On error: set couponError to the error message from the Cloud Function response and clear discountAmount. Update the order total display: bind it to a Custom Function that calculates subtotal minus discountAmount. On successful order completion (after Stripe Checkout or payment), call a second action to increment the coupon's currentUses by 1 using a Cloud Function or direct Firestore Update Document with FieldValue.increment(1).

Expected result: Applying a valid coupon updates the discount display and recalculates the order total. Invalid codes show an error message.

5

Build the admin coupon management panel

Create an AdminCouponsPage restricted to admin users (check user role on page load). Add a ListView bound to a Backend Query on the coupons collection ordered by expiresAt descending. Each row shows: code (bold), type badge (percentage or fixed), value, usage as 'currentUses / maxUses', expiresAt formatted date, and an isActive toggle Switch. The Switch On Change action updates the coupon's isActive field. Add a FloatingActionButton that opens a BottomSheet form for creating new coupons with fields: code TextField, type DropDown (percentage/fixed), value TextField (number keyboard), minOrderAmount TextField, maxUses TextField, expiresAt DateTimePicker, and applicableCategories ChoiceChips. The Save button creates the coupon document using the uppercase code as the document ID.

Expected result: Admins can view all coupons with usage stats, toggle them active/inactive, and create new coupons from a form.

Complete working example

Coupon System Architecture
1Firestore Data Model:
2 coupons/{CODE} (document ID = uppercase coupon code)
3 code: String ("SAVE20")
4 type: String ("percentage" | "fixed")
5 value: Double (20.0 for 20% or 10.0 for $10)
6 minOrderAmount: Double (50.0)
7 maxUses: Integer (100, 0 = unlimited)
8 currentUses: Integer (43)
9 expiresAt: Timestamp
10 isActive: Boolean (true)
11 applicableCategories: List<String> (["electronics", "clothing"])
12
13Checkout Page Coupon Section:
14 Row
15 TextField (coupon code input, capitalize: Characters)
16 Button ("Apply")
17 On Tap Call validateCoupon Cloud Function
18 On Success Set Page State: discountAmount, appliedCouponCode
19 On Error Set Page State: couponError
20 Text (couponError, red) [Cond. Vis: error not empty]
21 Container (green, applied coupon display) [Cond. Vis: discount > 0]
22 Text ("SAVE20 applied: -$15.00")
23 IconButton (Remove reset Page State)
24 Order Summary
25 Text ("Subtotal: $75.00")
26 Text ("Discount: -$15.00") [Cond. Vis: discount > 0]
27 Text ("Total: $60.00", bold)
28
29Admin Coupons Page:
30 ListView (coupons, orderBy expiresAt DESC)
31 Row
32 Text (code, bold)
33 Badge (type: percentage/fixed)
34 Text (value)
35 Text ("43/100 used")
36 Text (expires date)
37 Switch (isActive toggle)
38 FAB BottomSheet (create coupon form)
39 TextField (code)
40 DropDown (type: percentage/fixed)
41 TextField (value, number)
42 TextField (minOrderAmount, number)
43 TextField (maxUses, number)
44 DateTimePicker (expiresAt)
45 Button (Save create doc with code as ID)

Common mistakes when building a Custom Coupon and Discount System in FlutterFlow

Why it's a problem: Validating coupon only on the client side in the Action Flow

How to avoid: Validate all coupon rules in a Cloud Function. The client sends the code and order total; the server checks every rule and returns the discount or an error. Never trust client-calculated discounts.

Why it's a problem: Incrementing currentUses when the coupon is applied instead of after payment

How to avoid: Increment currentUses only inside the payment success handler (Stripe webhook Cloud Function or post-payment Action Flow). This ensures only completed orders consume coupon uses.

Why it's a problem: Storing coupon codes in mixed case without normalization

How to avoid: Store all coupon codes as uppercase in Firestore. Convert user input to uppercase before lookup using toUpperCase() in the Cloud Function and text capitalization Characters on the TextField.

Best practices

  • Use the coupon code as the Firestore document ID for guaranteed uniqueness and fast lookups
  • Validate all coupon rules server-side in a Cloud Function to prevent client bypass
  • Normalize coupon codes to uppercase for case-insensitive matching
  • Increment currentUses only after confirmed payment, not on coupon application
  • Cap discount at the order total to prevent negative amounts on small orders with large fixed discounts
  • Round discount calculations to two decimal places to avoid floating-point penny errors
  • Add rate limiting on the validate endpoint to prevent brute-force code guessing

Still stuck?

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

ChatGPT Prompt

Write a Firebase Cloud Function that validates a coupon code against a Firestore coupons collection. Check if the coupon exists, is active, not expired, under max uses, and meets the minimum order amount. Calculate the discount for both percentage and fixed types.

FlutterFlow Prompt

Create a checkout coupon section with a TextField for code entry, an Apply button, an error text, and a green container showing the applied discount. Add Page State variables for discountAmount and couponError.

Frequently asked questions

How do I support both percentage and fixed-amount discounts?

Store a type field (percentage or fixed) and a value field on the coupon document. In the validation logic, check the type: for percentage, calculate orderTotal * (value / 100); for fixed, use the value directly. Always cap the discount at the order total to prevent negative totals.

How do I prevent users from guessing coupon codes?

Use random alphanumeric codes (8-12 characters) generated in Cloud Functions instead of predictable words. Add rate limiting to the validate function: reject requests if a user tries more than 5 invalid codes in one minute.

Can I restrict coupons to specific product categories?

Yes. Add an applicableCategories list field to the coupon. During validation, compare the cart items' categories against this list. If the list is empty, the coupon applies to all categories. Otherwise, only items in matching categories receive the discount.

How do I create single-use-per-user coupons?

Add a usedBy list field on the coupon document or create a coupon_redemptions subcollection. During validation, check if the current user's UID is already in the usedBy list. On redemption, add their UID. This allows the coupon to be used globally but only once per user.

Should I show the discount in the Stripe Checkout total or apply it before?

Apply the discount before creating the Stripe Checkout session. Pass the discounted total as the amount. This way the customer sees the correct price on the Stripe payment page, and you do not need to configure Stripe-side discounts.

Can RapidDev help build an advanced promotion engine?

Yes. Advanced coupon systems need stackable discounts, tiered pricing, automatic coupon suggestions, A/B testing of offers, and analytics dashboards. RapidDev can build the full promotion engine with Cloud Functions and integrate it with your payment flow.

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.