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

How to Set Up a Custom Analytics Platform in FlutterFlow

Build a full self-hosted analytics platform in FlutterFlow that goes beyond simple dashboards. You will create an SDK-style event collection Custom Action, Cloud Functions that aggregate raw events into hourly and daily rollup tables, configurable dashboard widgets where admins pick metrics and chart types, funnel analysis showing step-by-step conversion rates, and cohort retention tables grouped by signup week. This is a mini-Mixpanel built entirely in FlutterFlow.

What you'll learn

  • How to build an SDK-style event logging Custom Action that captures events with properties throughout your app
  • How to design Cloud Function aggregation pipelines that roll raw events into hourly and daily metric tables
  • How to create configurable dashboard widgets where admins select a metric, chart type, and date range
  • How to implement funnel analysis and cohort retention tracking with Firestore queries
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner12 min read40-55 minFlutterFlow Pro+ (Custom Widgets and Cloud Functions required)March 2026RapidDev Engineering Team
TL;DR

Build a full self-hosted analytics platform in FlutterFlow that goes beyond simple dashboards. You will create an SDK-style event collection Custom Action, Cloud Functions that aggregate raw events into hourly and daily rollup tables, configurable dashboard widgets where admins pick metrics and chart types, funnel analysis showing step-by-step conversion rates, and cohort retention tables grouped by signup week. This is a mini-Mixpanel built entirely in FlutterFlow.

Build a configurable analytics platform with funnels, cohorts, and rollup aggregation

Most FlutterFlow apps rely on third-party analytics tools, but sometimes you need full control over your data pipeline. This tutorial walks you through building a self-hosted analytics platform inside FlutterFlow. You will create a lightweight event collection system, Cloud Functions that aggregate raw events into rollup collections for fast querying, an admin-configurable dashboard where each widget can display a different metric with a chosen chart type, funnel analysis that calculates conversion between defined steps, and cohort retention tables. The result is a mini-Mixpanel that lives entirely within your FlutterFlow project and Firestore backend.

Prerequisites

  • A FlutterFlow project with Firebase/Firestore connected
  • Firebase Blaze plan enabled for Cloud Functions
  • FlutterFlow Pro plan for Custom Widgets and Custom Functions
  • Basic understanding of Firestore collections and Backend Queries
  • Familiarity with the FlutterFlow Action Flow editor

Step-by-step guide

1

Design the Firestore data model for events, rollups, funnels, and cohorts

Create four Firestore collections. First, raw_events with fields: eventName (String), userId (String), properties (Map), timestamp (Timestamp), sessionId (String). Second, metrics_hourly with fields: metricName (String), hour (Timestamp), value (Double), count (Integer). Third, metrics_daily with the same structure but at day granularity. Fourth, funnels with fields: funnelName (String), steps (Array of Strings representing event names), createdAt (Timestamp). Add sample documents to raw_events with event names like page_view, button_click, signup_complete, and purchase_complete. Create composite indexes on raw_events for eventName + timestamp queries and on metrics_daily for metricName + day queries. These rollup collections are the key to performance: dashboards query rollups, never raw events.

Expected result: Four Firestore collections exist with sample data. Composite indexes are created for efficient querying by event name and timestamp.

2

Build an SDK-style event logging Custom Action

Create a Custom Action called logAnalyticsEvent with parameters: eventName (String), properties (JSON, optional). Inside the action, generate a sessionId from App State (set on app launch if empty, using a Custom Function that returns a UUID). Write a document to raw_events with eventName, the current authenticated user ID from currentUserUid, the properties map, the sessionId from App State, and FieldValue.serverTimestamp() for the timestamp field. Now wire this action throughout your app: on page loads, add an Action Flow trigger On Page Load that calls logAnalyticsEvent with eventName set to page_view and properties containing the page name. On key buttons like Sign Up or Purchase, add logAnalyticsEvent as an additional action in the Action Flow. This gives you complete event coverage without any third-party SDK.

log_analytics_event.dart
1// Custom Action: logAnalyticsEvent
2// Parameters: eventName (String), properties (JSON, optional)
3
4import 'package:cloud_firestore/cloud_firestore.dart';
5
6Future<void> logAnalyticsEvent(
7 String eventName, {
8 Map<String, dynamic>? properties,
9}) async {
10 final user = FirebaseAuth.instance.currentUser;
11 await FirebaseFirestore.instance.collection('raw_events').add({
12 'eventName': eventName,
13 'userId': user?.uid ?? 'anonymous',
14 'properties': properties ?? {},
15 'sessionId': FFAppState().currentSessionId,
16 'timestamp': FieldValue.serverTimestamp(),
17 });
18}

Expected result: The logAnalyticsEvent Custom Action fires on page loads and key button taps, writing structured event documents to the raw_events collection.

3

Deploy Cloud Functions for hourly and daily rollup aggregation

Deploy two scheduled Cloud Functions. The first, aggregateHourly, runs every hour via functions.pubsub.schedule. It queries raw_events from the last hour, groups events by eventName, counts occurrences, sums numeric properties like revenue, and writes one document per metric to metrics_hourly with the hour timestamp, metricName, value, and count. The second function, aggregateDaily, runs once at midnight. It reads the 24 metrics_hourly documents for the previous day, sums them into a single metrics_daily document per metric. Add a third function, archiveOldEvents, that runs weekly and moves raw_events older than 30 days to a raw_events_archive collection or deletes them. This three-tier approach keeps raw_events small, rollups fast, and storage costs controlled.

aggregate_hourly.js
1// Cloud Function: aggregateHourly (simplified)
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4admin.initializeApp();
5const db = admin.firestore();
6
7exports.aggregateHourly = functions.pubsub
8 .schedule('every 1 hours').onRun(async () => {
9 const now = new Date();
10 const hourAgo = new Date(now.getTime() - 3600000);
11 const snapshot = await db.collection('raw_events')
12 .where('timestamp', '>=', hourAgo)
13 .where('timestamp', '<', now).get();
14 const counts = {};
15 snapshot.docs.forEach(doc => {
16 const name = doc.data().eventName;
17 counts[name] = (counts[name] || 0) + 1;
18 });
19 const batch = db.batch();
20 Object.entries(counts).forEach(([metric, count]) => {
21 const ref = db.collection('metrics_hourly').doc();
22 batch.set(ref, {
23 metricName: metric,
24 hour: admin.firestore.Timestamp.fromDate(hourAgo),
25 value: count,
26 count: count,
27 });
28 });
29 await batch.commit();
30 });

Expected result: Cloud Functions run on schedule, populating metrics_hourly every hour and metrics_daily once per day. Raw events older than 30 days are archived automatically.

4

Create configurable dashboard widgets with metric and chart type selectors

Create a new page called AnalyticsPlatform. Add a GridView with crossAxisCount 2 and spacing 16. Each grid child is a Component called DashboardWidget with three Component Parameters: widgetId (String), metricName (String), and chartType (String). Inside the Component, add a Container with white background, rounded corners, and padding. At the top, place a Row with two DropDown widgets: the first lists available metrics (page_view, signup_complete, purchase_complete) and binds to a Page State variable selectedMetric. The second DropDown offers chart types: Line, Bar, Pie. Below the DropDowns, add a DateTimePicker range selector that sets filterStartDate and filterEndDate Page State variables. The main content area holds a Custom Widget that receives the selectedMetric, chartType, and date range, then queries metrics_daily and renders the appropriate fl_chart visualization. When any DropDown value changes, the Backend Query re-executes automatically because it references the Page State variables.

Expected result: Each dashboard widget has working DropDown selectors for metric and chart type. Changing a selector or date range immediately updates the chart visualization below.

5

Build funnel analysis with step-by-step conversion tracking

Create a page called FunnelAnalysis. Add a ListView that displays saved funnels from the funnels Firestore collection. Each funnel document has a funnelName and a steps array of event names (e.g., ['page_view', 'signup_start', 'signup_complete', 'first_purchase']). When a user taps a funnel, navigate to a FunnelDetail page. On this page, run a Backend Query for each step: query metrics_daily where metricName equals the step name, within the selected date range, and sum the values. Display the results in a vertical ListView where each row shows the step name, the count, and the conversion rate from the previous step. Calculate conversion with a Custom Function: (currentStepCount / previousStepCount * 100). Use a Container with a width set proportionally to the conversion rate to create a visual funnel bar that narrows at each step. Color each bar from green (high conversion) to red (low conversion) using Conditional Styles. Add a Create Funnel button that opens a BottomSheet with a TextField for the name and a multi-select for event steps.

custom_functions.dart
1// Custom Function: calcConversionRate
2// Return Type: String
3// Parameters: currentCount (int), previousCount (int)
4
5String calcConversionRate(int currentCount, int previousCount) {
6 if (previousCount == 0) return '0.0';
7 final rate = (currentCount / previousCount) * 100;
8 return rate.toStringAsFixed(1);
9}

Expected result: The funnel page shows a visual narrowing bar chart with each step's count and conversion percentage from the prior step. Admins can create new funnels by selecting event sequences.

6

Implement cohort retention tables and CSV export

Create a page called CohortRetention. A cohort groups users by their signup week. Query the raw_events collection for signup_complete events, group by week using a Custom Function that returns the ISO week number from a timestamp. For each cohort week, query how many of those users triggered any event in Week 0 (signup week), Week 1, Week 2, and so on up to Week 8. Display results in a GridView styled as a table: rows are cohort weeks, columns are retention weeks (Week 0 through Week 8). Each cell shows the retention percentage with background color intensity mapped to the value (darker green for higher retention). Use Conditional Styles to set the Container background opacity: retention 80-100 percent gets opacity 1.0, 60-80 gets 0.8, and so on down to below 20 percent at 0.2. For CSV export, create a Custom Action that builds a comma-separated string from the cohort data and uses the url_launcher package to trigger a download or share via the device share sheet.

Expected result: A heatmap-style cohort retention table displays weekly retention percentages with color intensity. A CSV export button downloads the cohort data for external analysis.

Complete working example

Analytics Platform Architecture
1Firestore Data Model:
2 raw_events/{auto-id}
3 eventName: String (page_view, signup_complete, purchase_complete)
4 userId: String
5 properties: Map (page: 'home', amount: 29.99)
6 sessionId: String (UUID from App State)
7 timestamp: Timestamp
8 metrics_hourly/{auto-id}
9 metricName: String
10 hour: Timestamp
11 value: Double
12 count: Integer
13 metrics_daily/{auto-id}
14 metricName: String
15 day: Timestamp
16 value: Double
17 count: Integer
18 funnels/{auto-id}
19 funnelName: String (Signup Flow)
20 steps: Array [page_view, signup_start, signup_complete]
21 createdAt: Timestamp
22 dashboard_configs/{auto-id}
23 widgetId: String
24 metricName: String
25 chartType: String (line, bar, pie)
26 dateRange: String (7d, 30d, 90d)
27
28Cloud Functions:
29 aggregateHourly every 1 hour, raw_events metrics_hourly
30 aggregateDaily every 24 hours, metrics_hourly metrics_daily
31 archiveOldEvents weekly, move raw_events > 30 days archive
32
33Custom Functions:
34 logAnalyticsEvent(eventName, properties) write to raw_events
35 calcConversionRate(current, previous) String percentage
36 getISOWeekNumber(timestamp) int week number
37 formatMetric(value) String with K/M suffix
38
39Page: AnalyticsPlatform (Dashboard)
40 AppBar (title: Analytics Platform)
41 Padding (16px)
42 GridView (crossAxisCount: 2)
43 DashboardWidget Component (repeated)
44 Row: DropDown (metric) + DropDown (chart type)
45 DateTimePicker (date range)
46 Custom Widget (fl_chart: line/bar/pie)
47 Backend Query: metrics_daily filtered by selected metric + date range
48
49Page: FunnelAnalysis
50 ListView (saved funnels from funnels collection)
51 FunnelDetail
52 ListView (funnel steps)
53 Row: step name + count + conversion % + proportional bar
54 Create Funnel BottomSheet
55
56Page: CohortRetention
57 GridView (table layout: rows = cohort weeks, cols = retention weeks)
58 Container (cell: retention % + color-coded background)
59 Button (Export CSV)

Common mistakes

Why it's a problem: Storing raw events forever without rollup aggregation

How to avoid: Aggregate into metrics_hourly and metrics_daily rollup collections via Cloud Functions. Query rollups for dashboard display. Archive or delete raw events after 30 days to keep the collection small.

Why it's a problem: Running funnel queries against raw_events instead of pre-computed rollups

How to avoid: Use metrics_daily rollups for funnel step counts. Each step count is a single document read per day in the range, making funnel queries fast regardless of raw event volume.

Why it's a problem: Not indexing the raw_events collection on eventName + timestamp

How to avoid: Create a composite index in Firebase Console on the raw_events collection for eventName (ascending) and timestamp (descending). Firestore will also prompt you with an auto-generated index link when the query first fails.

Why it's a problem: Building the entire platform before validating event collection works

How to avoid: Build and validate event collection first. Check raw_events in Firestore Console to confirm events are flowing correctly with the right properties. Then build rollups, then dashboards, then funnels and cohorts.

Best practices

  • Aggregate raw events into hourly and daily rollup tables — never query raw events for dashboard display
  • Archive or delete raw events older than 30 days to control Firestore storage costs and query performance
  • Use Component Parameters for DashboardWidget so the same component renders any metric with any chart type
  • Save dashboard widget configurations to Firestore so admin customizations persist across sessions
  • Add an empty state for new dashboards that shows a setup wizard instead of blank charts
  • Use batch writes in Cloud Functions to write multiple rollup documents in a single transaction
  • Test funnel and cohort calculations with known sample data before connecting to live events
  • Add a data freshness indicator showing when rollups were last computed so admins know if data is stale

Still stuck?

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

ChatGPT Prompt

Design a Firestore data model for a self-hosted analytics platform. I need collections for raw_events (eventName, userId, properties, sessionId, timestamp), metrics_hourly and metrics_daily rollup tables, and a funnels collection with step sequences. Write a scheduled Cloud Function that aggregates raw_events into hourly and daily rollups, and a Dart function that calculates funnel conversion rates between steps.

FlutterFlow Prompt

Create an analytics platform page with a 2-column GridView of configurable dashboard widgets. Each widget should have a DropDown for metric selection (page_view, signup_complete, purchase_complete), a DropDown for chart type (Line, Bar, Pie), and a date range picker. Below the dashboard, add a funnel analysis section with a vertical list showing step names, counts, and conversion percentages with proportional colored bars.

Frequently asked questions

How is this different from using Google Analytics or Mixpanel with FlutterFlow?

Third-party tools collect and process data on their servers, giving you limited control over data retention, custom metrics, and privacy. This self-hosted platform stores all events in your own Firestore, lets you define custom funnels and cohorts specific to your business, and avoids per-event pricing from analytics SaaS vendors. The tradeoff is you manage the aggregation infrastructure yourself.

How many raw events can Firestore handle before performance degrades?

Firestore handles millions of documents per collection without issue for indexed queries. The bottleneck is query cost, not performance. A query scanning 100,000 documents costs the same whether the collection has 100,000 or 10 million documents, as long as you use indexed queries with proper where clauses. The rollup pattern keeps dashboard queries to dozens of documents regardless of raw event volume.

Can I add real-time event streaming to the dashboard?

Yes. Use a Firestore snapshot listener on the metrics_hourly collection to update dashboard widgets in real-time as new hourly rollups are written. For true real-time (sub-minute), you could listen to raw_events directly, but this is expensive at scale. A better approach is a Cloud Function that updates a live_metrics document on each event write, and the dashboard listens to that single document.

How do I handle the cost of Cloud Function executions?

The aggregateHourly function runs 24 times per day and aggregateDaily runs once. At Firebase Blaze pricing, this costs pennies per month in function invocations. The real cost savings come from the rollup pattern reducing Firestore read costs. Without rollups, each dashboard view might read 10,000 raw events. With rollups, the same view reads 30 daily documents.

What if I need funnels with branching paths instead of linear steps?

The linear funnel model in this tutorial handles most use cases. For branching funnels, modify the funnels collection to store steps as a tree structure (each step has an array of next steps). The conversion calculation becomes per-branch rather than sequential. This adds complexity to both the Cloud Function logic and the visualization, so start with linear funnels and add branching only if your product requires it.

Can RapidDev help build a production-grade analytics platform?

Yes. A production analytics platform often needs real-time streaming dashboards, custom event schemas, BigQuery export for advanced SQL analysis, role-based dashboard access, and automated anomaly detection alerts. RapidDev can architect the full data pipeline from event collection through aggregation to visualization, beyond what FlutterFlow's visual builder handles alone.

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.