Create a personalized shopping experience by tracking user behavior in a Firestore user_behavior document that records viewed products, purchase history, and search terms. The homepage dynamically renders sections like Recently Viewed, Because You Bought X, and Trending in Your Category using Backend Queries filtered by the user's behavior data. A Cloud Function generates recommendations and sends abandoned cart reminder emails. New users with no behavior data see trending and popular items as a fallback.
Building a Personalized Shopping Experience in FlutterFlow
Generic storefronts show every user the same content. Personalization transforms the shopping experience by surfacing products based on individual browsing and purchase history. This tutorial builds user behavior tracking, a dynamic personalized homepage with recently viewed items and category-based recommendations, a hero banner that adapts to user preferences, and an abandoned cart recovery system.
Prerequisites
- A FlutterFlow project with an e-commerce product catalog already set up
- Firebase project with Firestore and Cloud Functions enabled
- A products collection with fields: name, category, price, imageUrl, and popularity score
- Basic familiarity with Backend Queries and App State in FlutterFlow
Step-by-step guide
Create the user behavior tracking schema
Create the user behavior tracking schema
Create a `user_behavior` collection where each document ID matches the user's UID. Add fields: recentViews (Array of Maps, each with productId, category, and viewedAt), purchaseCategories (Array of Strings — categories the user has bought from), searchTerms (Array of Strings — recent search queries), topCategory (String — most frequent category from views and purchases), and interactionCount (int — total tracked interactions). On every product detail page load, add a Custom Action that appends to the recentViews array using FieldValue.arrayUnion with the product details. Cap the array at 20 items by using a Cloud Function that trims older entries.
Expected result: User browsing behavior is tracked in Firestore automatically as they view products, search, and make purchases.
Build the Recently Viewed horizontal carousel
Build the Recently Viewed horizontal carousel
On the homepage, add a 'Recently Viewed' section header with a horizontal ListView. Read the current user's user_behavior document and extract the recentViews array. For each entry, query the products collection by productId to get the full product details. Display each product in a compact Container card within the horizontal list showing the product image, name, and price. If recentViews is empty (new user), hide the entire section using Conditional Visibility that checks recentViews length > 0. Order items by viewedAt descending so the most recently viewed product appears first.
Expected result: Returning users see their recently viewed products in a horizontal scrollable carousel. New users with no history do not see this section.
Implement category-based product recommendations
Implement category-based product recommendations
Create a Cloud Function named generateRecommendations triggered on a schedule (hourly) or on user_behavior document update. The function reads the user's purchaseCategories and topCategory fields. It queries the products collection for items in those categories that the user has NOT already viewed (exclude productIds from recentViews). Rank by popularity score descending and write the top 10 product IDs to a `users/{uid}/recommendations` document. In FlutterFlow, add a 'Recommended For You' section on the homepage with a horizontal ListView bound to a Backend Query that fetches products by IDs from the recommendations document. The section title can be personalized: 'Because You Bought [category]'.
1// Cloud Function: generateRecommendations2exports.generateRecommendations = functions.firestore3 .document('user_behavior/{userId}')4 .onWrite(async (change, context) => {5 const userId = context.params.userId;6 const behavior = change.after.data();7 if (!behavior) return;89 const viewedIds = (behavior.recentViews || [])10 .map(v => v.productId);11 const categories = behavior.purchaseCategories || [];12 const topCat = behavior.topCategory;1314 const targetCats = topCat15 ? [topCat, ...categories] : categories;16 const uniqueCats = [...new Set(targetCats)]17 .slice(0, 3);1819 if (uniqueCats.length === 0) return;2021 const products = await db22 .collection('products')23 .where('category', 'in', uniqueCats)24 .orderBy('popularity', 'desc')25 .limit(20)26 .get();2728 const recommended = products.docs29 .filter(d => !viewedIds.includes(d.id))30 .slice(0, 10)31 .map(d => d.id);3233 await db.doc(`users/${userId}/recommendations/main`)34 .set({35 productIds: recommended,36 category: uniqueCats[0],37 updatedAt: admin.firestore.FieldValue38 .serverTimestamp(),39 });40 });Expected result: Users see product recommendations based on their purchase and browsing categories. Products they have already viewed are excluded.
Create a dynamic hero banner based on user preferences
Create a dynamic hero banner based on user preferences
Create a `banners` collection with documents for each promotional banner containing: imageUrl (String), title (String), targetCategory (String), linkProductId or linkCategory (String), and priority (int). On the homepage, query banners ordered by priority descending. Filter the results using a Custom Function that prioritizes banners matching the user's topCategory from user_behavior. If the user's top category is 'Electronics', show the Electronics sale banner first. For new users with no topCategory, show the default highest-priority banner. Display the banner as a full-width Container with the image, overlaid title text, and a tap action that navigates to the linked product or category page.
Expected result: The hero banner dynamically shows promotions relevant to the user's shopping interests, with a fallback to popular promotions for new users.
Add a Trending in Your Category section with fallback for new users
Add a Trending in Your Category section with fallback for new users
Add a third homepage section titled 'Trending in [category]' where [category] is the user's topCategory. Query products where category equals the user's topCategory, ordered by a trendingScore or recentSalesCount descending, limited to 10. Display in a horizontal ListView like the other sections. For new users (interactionCount < 5 or topCategory is null), show a 'Popular Right Now' fallback section instead. This queries products ordered by popularity descending across all categories. Use Conditional Visibility to switch between the personalized and fallback sections based on the interactionCount threshold.
Expected result: Established users see trending products in their preferred category. New users see globally popular products until enough behavior data is collected.
Set up abandoned cart email reminders via Cloud Function
Set up abandoned cart email reminders via Cloud Function
Create a scheduled Cloud Function named checkAbandonedCarts that runs every 6 hours. The function queries users with non-empty cart App State (stored in a `carts` collection with userId, items array, and lastUpdatedAt). For carts not updated in 24 hours where no matching order exists, compose and send a reminder email using SendGrid or Firebase Extensions with a personalized message listing the cart items, their images, and a deep link back to the checkout page. Record the reminder in a `cart_reminders` collection to avoid sending duplicate emails. Limit to one reminder per abandoned cart.
1// Cloud Function: checkAbandonedCarts2exports.checkAbandonedCarts = functions.pubsub3 .schedule('every 6 hours')4 .onRun(async () => {5 const cutoff = new Date();6 cutoff.setHours(cutoff.getHours() - 24);78 const carts = await db.collection('carts')9 .where('lastUpdatedAt', '<',10 admin.firestore.Timestamp.fromDate(cutoff))11 .where('itemCount', '>', 0)12 .get();1314 for (const cartDoc of carts.docs) {15 const cart = cartDoc.data();16 // Check if reminder already sent17 const existing = await db18 .collection('cart_reminders')19 .where('cartId', '==', cartDoc.id)20 .limit(1).get();21 if (!existing.empty) continue;2223 // Send email via SendGrid24 // ... email sending logic with cart items2526 await db.collection('cart_reminders').add({27 cartId: cartDoc.id,28 userId: cart.userId,29 sentAt: admin.firestore.FieldValue30 .serverTimestamp(),31 });32 }33 });Expected result: Users with abandoned carts for 24+ hours receive a single reminder email with their cart items and a link to complete the purchase.
Complete working example
1// Cloud Functions: Personalized Shopping System2const functions = require('firebase-functions');3const admin = require('firebase-admin');4admin.initializeApp();5const db = admin.firestore();67// Generate recommendations on behavior change8exports.generateRecommendations = functions.firestore9 .document('user_behavior/{userId}')10 .onWrite(async (change, context) => {11 const userId = context.params.userId;12 const behavior = change.after.data();13 if (!behavior) return;14 const viewedIds = (behavior.recentViews || []).map(v => v.productId);15 const categories = behavior.purchaseCategories || [];16 const topCat = behavior.topCategory;17 const targetCats = [...new Set(18 [topCat, ...categories].filter(Boolean)19 )].slice(0, 3);20 if (targetCats.length === 0) return;21 const snapshot = await db.collection('products')22 .where('category', 'in', targetCats)23 .orderBy('popularity', 'desc').limit(20).get();24 const recommended = snapshot.docs25 .filter(d => !viewedIds.includes(d.id))26 .slice(0, 10).map(d => d.id);27 await db.doc(`users/${userId}/recommendations/main`).set({28 productIds: recommended,29 category: targetCats[0],30 updatedAt: admin.firestore.FieldValue.serverTimestamp(),31 });32 });3334// Track product view35exports.trackProductView = functions.https36 .onCall(async (data, context) => {37 if (!context.auth) return;38 const { productId, category } = data;39 const userId = context.auth.uid;40 const ref = db.doc(`user_behavior/${userId}`);41 await ref.set({42 recentViews: admin.firestore.FieldValue.arrayUnion({43 productId, category, viewedAt: new Date().toISOString(),44 }),45 interactionCount: admin.firestore.FieldValue.increment(1),46 }, { merge: true });47 // Update topCategory from view frequency48 const doc = await ref.get();49 const views = doc.data()?.recentViews || [];50 const catCounts = {};51 views.forEach(v => { catCounts[v.category] = (catCounts[v.category] || 0) + 1; });52 const topCategory = Object.entries(catCounts)53 .sort((a, b) => b[1] - a[1])[0]?.[0];54 if (topCategory) await ref.update({ topCategory });55 });5657// Abandoned cart check (every 6 hours)58exports.checkAbandonedCarts = functions.pubsub59 .schedule('every 6 hours')60 .onRun(async () => {61 const cutoff = new Date();62 cutoff.setHours(cutoff.getHours() - 24);63 const carts = await db.collection('carts')64 .where('lastUpdatedAt', '<',65 admin.firestore.Timestamp.fromDate(cutoff))66 .where('itemCount', '>', 0).get();67 for (const doc of carts.docs) {68 const sent = await db.collection('cart_reminders')69 .where('cartId', '==', doc.id).limit(1).get();70 if (!sent.empty) continue;71 // Send email reminder via SendGrid here72 await db.collection('cart_reminders').add({73 cartId: doc.id, userId: doc.data().userId,74 sentAt: admin.firestore.FieldValue.serverTimestamp(),75 });76 }77 });Common mistakes when building a Personalized Shopping Experience in FlutterFlow
Why it's a problem: Personalizing for new users with no behavior data
How to avoid: Show trending and popular items as a fallback when interactionCount is below 5. Gradually transition to personalized content as more behavior data is collected. Always hide empty sections with Conditional Visibility.
Why it's a problem: Tracking behavior data without capping array sizes
How to avoid: Cap recentViews at 20-30 items. Use a Cloud Function triggered on update to trim the array, keeping only the most recent entries. Similarly, limit searchTerms to the last 10 queries.
Why it's a problem: Generating recommendations synchronously on every page load
How to avoid: Pre-compute recommendations via a Cloud Function triggered by behavior changes or on a schedule. Store the recommendation product IDs in a separate document. The homepage simply reads the pre-computed list.
Best practices
- Hide empty personalization sections with Conditional Visibility instead of showing 'No items yet' messages
- Use a minimum interaction threshold (5+ interactions) before showing personalized content to avoid poor recommendations
- Pre-compute recommendations in Cloud Functions rather than calculating them on every page load
- Cap all behavior tracking arrays (recentViews, searchTerms) to prevent unbounded document growth
- Show the user's top category in section headers like 'Trending in Electronics' for a personal touch
- Limit abandoned cart reminders to one email per cart to avoid spamming users
- Track recommendation clicks as a behavior signal to improve future recommendations
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a personalized e-commerce app in FlutterFlow. I need to track user behavior (views, purchases, searches), generate product recommendations based on purchase categories, create a dynamic homepage with Recently Viewed and recommended sections, and set up abandoned cart email reminders. Show me the Firestore schema and Cloud Functions.
Create a personalized homepage with three horizontal product carousels: Recently Viewed (from user behavior), Recommended For You (based on purchase categories), and Trending in Your Category. Add a dynamic hero banner that changes based on user preferences. For new users with no data, show popular products instead.
Frequently asked questions
How many interactions should I wait before personalizing?
Start showing personalized content after 5 product views or 1 purchase. Below that threshold, the data is too sparse for meaningful recommendations. Show popular and trending items as the fallback for new users.
Can I use machine learning for better recommendations?
Yes. For advanced personalization, use Google Cloud Recommendations AI or a custom ML model deployed as a Cloud Function. The category-matching approach in this tutorial is a strong baseline that works well for most apps.
How do I handle users who browse many categories?
Use the topCategory field calculated from the most frequently viewed category. For users with diverse interests, the recommendation function uses up to 3 top categories to provide variety.
Does the abandoned cart email comply with privacy regulations?
You need user consent to send marketing emails. Add a checkbox during signup for email communication preferences. Only send abandoned cart reminders to users who opted in. Include an unsubscribe link in every email.
Can I personalize search results too?
Yes. Boost products in the user's preferred categories in search results by adding a relevance score that weights category match. Products matching the user's topCategory appear higher in results.
Can RapidDev help build an advanced personalization engine?
Yes. RapidDev can implement collaborative filtering, content-based recommendation systems, A/B testing frameworks for personalization strategies, and real-time behavior analytics dashboards.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation