Build a complete single-store e-commerce app using Firestore collections for products and orders. Display products in a responsive GridView with ChoiceChips category filter and Slider price range. Store cart items in persisted App State as a JSON list so the cart survives navigation. Checkout via a Stripe Checkout Session Cloud Function that creates the order document on payment success and returns a confirmation page.
Full single-store e-commerce with product catalog, cart, and Stripe payments
This tutorial builds a complete e-commerce store: a Firestore products collection displayed in a responsive GridView with category and price filters, a product detail page with image PageView and size/color selectors, a persistent cart backed by App State, and Stripe Checkout for payments. Orders are recorded in Firestore with status tracking. This is a single-seller store — for multi-seller marketplaces, see the marketplace tutorial.
Prerequisites
- A FlutterFlow project with Firebase/Firestore connected
- Stripe account with test API keys (pk_test and sk_test)
- Cloud Functions enabled (Blaze plan on Firebase)
- Basic familiarity with App State and Backend Queries in FlutterFlow
- Product images uploaded to Firebase Storage or external URLs
Step-by-step guide
Set up the products and orders Firestore collections
Set up the products and orders Firestore collections
Create a products collection with fields: name (String), description (String), price (Double), images (List of Strings — image URLs), category (String), sizes (List of Strings), colors (List of Strings), and inStock (Boolean, default true). Create an orders collection with fields: userId (String), items (List of Maps — each with productId, productName, qty, price, size, color), total (Double), status (String — pending, confirmed, shipped, delivered), and timestamp (Timestamp). Add 5-10 test products in the Firebase Console with real image URLs and varied categories like Clothing, Accessories, Electronics.
Expected result: Firestore has products and orders collections with test data ready for querying.
Build the product catalog with GridView filters
Build the product catalog with GridView filters
Create a ShopPage with a Column layout. At the top, add a ChoiceChips widget with options All, Clothing, Accessories, Electronics — bind the selected value to a Page State variable categoryFilter (String, default All). Below it, add a RangeSlider (or two Slider widgets) for min/max price bound to Page State priceMin and priceMax. Add a GridView with crossAxisCount 2 (responsive: 3 on tablet) bound to a Backend Query on products. Apply query filters: if categoryFilter != All, where category == categoryFilter; where price >= priceMin AND price <= priceMax; where inStock == true. Each grid cell is a Container with a Column: Image (first item from images array), Text (name), and Text (price formatted as currency).
Expected result: Products display in a responsive grid. Selecting a category or adjusting the price slider filters the results in real time.
Create the product detail page with image carousel and variant selectors
Create the product detail page with image carousel and variant selectors
Create ProductDetailPage that receives a productRef parameter. Add a Backend Query for the product document. Build the layout: a PageView widget at the top bound to the product's images array showing each image in full width with dot indicators. Below, add a Text for the product name (headlineMedium), a Text for price (headlineSmall, primary color), a short description Text. Add ChoiceChips for size options bound to the product's sizes list, and another ChoiceChips for color options. At the bottom, place an Add to Cart ElevatedButton. On tap, the button triggers a Custom Function that serializes the selection (productId, name, qty: 1, price, selectedSize, selectedColor) and appends it to the App State cartItems list.
Expected result: Users can browse product images, select size and color, and add items to the cart.
Implement the persistent cart with App State
Implement the persistent cart with App State
In App State, create a variable cartItems of type List of JSON (persisted: ON). This stores cart entries as JSON objects with productId, productName, qty, price, size, and color. Create a CartPage with a ListView bound to cartItems. Each row shows: Image (product thumbnail), Text (name + size + color), Row with IconButton minus and plus to decrement/increment qty (update the cartItems list via Custom Function), and Text (line total = qty * price). At the bottom, display the cart total calculated by a Custom Function that sums all line totals. Add a Badge widget on the AppBar cart icon showing cartItems.length. The persisted App State ensures the cart survives page navigation and app restarts.
Expected result: Cart persists across pages. Users can adjust quantities, see running totals, and the AppBar badge shows item count.
Integrate Stripe Checkout for payment processing
Integrate Stripe Checkout for payment processing
Deploy a Cloud Function createCheckoutSession that accepts the cartItems array, creates Stripe line items from each cart entry (name, unit_amount in cents, quantity), calls stripe.checkout.sessions.create with mode: payment and success/cancel URLs, and returns the session URL. In FlutterFlow, add a Checkout button on the CartPage that calls this Cloud Function via an API Call action, then opens the returned URL with Launch URL action. Deploy a second Cloud Function handleStripeWebhook listening for checkout.session.completed: it creates an order document in Firestore with the cart items, total, status: confirmed, and the authenticated userId. After payment, the success URL returns the user to an OrderConfirmationPage. Clear the cartItems App State on successful return.
1// Cloud Function: createCheckoutSession2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);34exports.createCheckoutSession = async (req, res) => {5 const { cartItems, userId } = req.body;6 const lineItems = cartItems.map(item => ({7 price_data: {8 currency: 'usd',9 product_data: { name: `${item.productName} (${item.size}, ${item.color})` },10 unit_amount: Math.round(item.price * 100),11 },12 quantity: item.qty,13 }));14 const session = await stripe.checkout.sessions.create({15 payment_method_types: ['card'],16 line_items: lineItems,17 mode: 'payment',18 success_url: 'https://yourapp.com/order-success?session_id={CHECKOUT_SESSION_ID}',19 cancel_url: 'https://yourapp.com/cart',20 metadata: { userId },21 });22 res.json({ url: session.url });23};Expected result: Clicking Checkout opens Stripe's hosted payment page. After payment, an order is created in Firestore and the cart is cleared.
Build the order history and status tracking page
Build the order history and status tracking page
Create an OrdersPage with a ListView bound to a Backend Query on orders where userId == currentUser.uid, ordered by timestamp descending. Each order card shows: order date (formatted timestamp), status with a color-coded Container badge (green for delivered, blue for shipped, yellow for confirmed, grey for pending), item count, and total amount. Tap an order to navigate to OrderDetailPage showing the full items list, delivery status stepper (Column of status steps with active/completed indicators), and a Support button. For admin use, create a separate AdminOrdersPage where staff can update the status field via a DropDown selector triggering an Update Document action.
Expected result: Customers see their order history with color-coded statuses. Admins can update order status from a management page.
Complete working example
1Firestore Data Model:2├── products/{productId}3│ ├── name: String ("Classic Cotton T-Shirt")4│ ├── description: String5│ ├── price: Double (29.99)6│ ├── images: List<String> ([url1, url2, url3])7│ ├── category: String ("Clothing")8│ ├── sizes: List<String> (["S", "M", "L", "XL"])9│ ├── colors: List<String> (["Black", "White", "Navy"])10│ └── inStock: Boolean (true)11└── orders/{orderId}12 ├── userId: String13 ├── items: List<Map> ([{productId, productName, qty, price, size, color}])14 ├── total: Double (89.97)15 ├── status: String ("pending" | "confirmed" | "shipped" | "delivered")16 └── timestamp: Timestamp1718App State:19└── cartItems: List<JSON> (persisted: ON)20 └── [{productId, productName, qty, price, size, color}, ...]2122ShopPage:23├── ChoiceChips (All | Clothing | Accessories | Electronics)24├── RangeSlider (price: $0 - $500)25└── GridView (crossAxisCount: 2, query: products filtered)26 └── Container → Column27 ├── Image (images[0])28 ├── Text (name)29 ├── Text (price, formatted currency)30 └── On Tap → Navigate To: ProductDetailPage3132ProductDetailPage:33├── PageView (product images with dot indicators)34├── Text (name, headlineMedium)35├── Text (price, primary color)36├── ChoiceChips (sizes)37├── ChoiceChips (colors)38└── ElevatedButton ("Add to Cart")39 └── On Tap → Custom Function: addToCart(cartItems, product, size, color)4041CartPage:42├── ListView (cartItems from App State)43│ └── Row: Image + Name + Qty controls + Line total44├── Text (cart total via Custom Function)45└── Button ("Checkout")46 └── On Tap → API Call: createCheckoutSession → Launch URLCommon mistakes when creating an E-Commerce Store Using FlutterFlow
Why it's a problem: Storing cart in Page State instead of persisted App State
How to avoid: Use App State with persistence ON for cartItems. Persisted App State survives navigation and even app restarts, just like a real shopping cart.
Why it's a problem: Calculating order total only on the client side
How to avoid: Recalculate the total inside the Cloud Function from the actual product prices in Firestore. Never trust the client-sent total for payment processing.
Why it's a problem: Not creating a Firestore composite index for category + price queries
How to avoid: Check the Firebase Console logs for the auto-generated index creation URL when the query first fails, or proactively create a composite index on products for category (ascending) + price (ascending).
Best practices
- Use persisted App State for the cart so items survive navigation and app restarts
- Always recalculate totals server-side in the Cloud Function — never trust client-sent amounts
- Create composite Firestore indexes for multi-field product queries (category + price + inStock)
- Use Stripe Checkout Sessions instead of handling card data directly — PCI compliance handled by Stripe
- Show product image placeholders while images load to prevent layout jumps in the GridView
- Implement an addToCart function that merges duplicate items instead of creating separate entries
- Test the full checkout flow with Stripe test card 4242 4242 4242 4242 before going live
- Add Firestore Security Rules that prevent customers from modifying order status — only admin Cloud Functions should update status
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Design a Firestore data model for a single-store e-commerce app. I need a products collection with name, price, images, category, sizes, colors, and stock status. I need an orders collection with userId, line items array, total, status enum, and timestamp. Also write the Stripe Checkout Session Cloud Function that creates a session from the cart items.
Create a product catalog page with a GridView showing product images, names, and prices. Add ChoiceChips at the top for category filtering (All, Clothing, Accessories, Electronics) and a price range slider. Connect the grid to my Firestore products collection with the selected filters applied.
Frequently asked questions
How do I handle product variants like size and color combinations?
Store sizes and colors as List fields on the product document. Display them as ChoiceChips on the product detail page. When adding to cart, include the selected size and color in the cart entry. For inventory tracking per variant, use a variants subcollection with sku, size, color, and stockCount fields.
Can I add a search bar to the product catalog?
Yes. Add a TextField above the GridView and bind its value to a Page State searchQuery variable. Filter the Backend Query with a where name >= searchQuery clause for prefix matching. For full-text search, integrate Algolia or Typesense via a Cloud Function.
How do I handle out-of-stock products?
Add an inStock Boolean field to products. Filter the Backend Query to only show inStock == true products. On the product detail page, use Conditional Visibility to show a Sold Out badge and disable the Add to Cart button when inStock is false.
What happens if the Stripe payment fails?
Stripe Checkout handles all error states on its hosted page — declined cards, expired cards, insufficient funds. The user stays on the Stripe page until payment succeeds. Your cancel_url brings them back to the cart with all items intact in App State.
How do I send order confirmation emails?
In the handleStripeWebhook Cloud Function, after creating the order document, use a service like SendGrid or Firebase Extensions (Trigger Email) to send a confirmation email with the order details, items, and total.
Can RapidDev help build a production e-commerce app with inventory management?
Yes. A production e-commerce app needs inventory sync, webhook-based order fulfillment, shipping integration, tax calculation, refund handling, and admin dashboards. RapidDev can architect the full system beyond what the visual builder handles alone.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation