Build a personal finance app in FlutterFlow by creating three Firestore collections — accounts, transactions, and budgets — then connecting them to a transaction entry form, a PieChart widget for category breakdowns, and LinearProgressIndicator bars for budget tracking. Avoid storing a running balance as a separate field; always calculate it by summing transactions to prevent data drift.
What You Are Building
A personal finance management tool needs three core features: transaction entry and history, category-level spending analysis, and budget tracking with alerts. In FlutterFlow you build these on top of Firestore, using backend queries to power list views and a PieChart, and LinearProgressIndicator widgets to visualize budget consumption. For recurring transactions (monthly rent, weekly subscriptions) you add a Cloud Scheduler function that clones a transaction template document on a schedule. The key architectural decision is to never store a running account balance — always derive it by summing transactions. This prevents the data drift that occurs when a transaction is edited, deleted, or fails to write.
Prerequisites
- FlutterFlow project with Firebase connected and Firestore enabled
- Firebase Authentication set up so transactions are scoped to the current user
- Basic familiarity with FlutterFlow's widget tree and backend query panel
- Firebase Blaze plan if you want scheduled Cloud Functions for recurring transactions
Step-by-step guide
Design the three-collection Firestore data model
Design the three-collection Firestore data model
Create three Firestore collections in FlutterFlow's Firestore panel. The accounts collection holds documents with fields: userId (String), name (String), type (String — checking/savings/credit), currency (String), and createdAt (Timestamp). The transactions collection holds: userId, accountId, amount (Double — positive for income, negative for expense), category (String), description (String), date (Timestamp), isRecurring (Boolean), and recurringId (String, optional). The budgets collection holds: userId, category (String), limitAmount (Double), periodStart (Timestamp), and periodEnd (Timestamp). Set Firestore security rules so users can only read and write documents where userId == request.auth.uid.
Expected result: Three Firestore collections are visible in FlutterFlow's Firestore panel with the correct field types.
Build the transaction entry form
Build the transaction entry form
Add a new page called AddTransaction. Place a Column containing: a DropdownButton bound to your categories option set, a TextField for description, a NumberTextField for amount, a DatePicker for date, a DropdownButton for accountId (populated from a Firestore query of the user's accounts), and a Toggle for expense vs income. On the Save button's action: set amount to negative if expense is selected, then call Create Document on the transactions collection with all fields. After saving, navigate back to the transactions list. Add form validation: amount must be greater than zero, category and account must be selected.
Expected result: Tapping Save creates a transaction document in Firestore and navigates back to the list page.
Display a category spending PieChart
Display a category spending PieChart
On your Dashboard page add a PieChart widget from FlutterFlow's chart library. The PieChart requires a list of values and a list of labels. Create a Custom Function called getCategoryTotals that accepts a list of transaction documents and returns a map of category to total absolute amount. Query transactions for the current user filtered by the current month using a date range filter. Pass the query results to your custom function, then map the output to the PieChart's value series. Style each slice with a distinct color from your theme. Add a legend Row below the chart that shows category name and total.
1// Custom Function: getCategoryTotals.dart2import 'package:cloud_firestore/cloud_firestore.dart';34Map<String, double> getCategoryTotals(5 List<DocumentSnapshot> transactions) {6 final Map<String, double> totals = {};7 for (final doc in transactions) {8 final data = doc.data() as Map<String, dynamic>;9 final category = data['category'] as String? ?? 'Other';10 final amount = (data['amount'] as num?)?.toDouble() ?? 0.0;11 if (amount < 0) {12 totals[category] = (totals[category] ?? 0) + amount.abs();13 }14 }15 return totals;16}Expected result: The PieChart renders with one slice per spending category, sized proportionally to the month's spending.
Add budget progress bars with overspend alerts
Add budget progress bars with overspend alerts
Create a BudgetProgress page that shows a list of budget documents for the current user. For each budget, display a Card containing the category name, a LinearProgressIndicator, and spent vs limit text. The progress value requires a Custom Function called getBudgetProgress that queries transactions matching the budget's category and current period, sums the amounts, and divides by the limit. Set the indicator color to green below 80%, amber from 80-99%, and red at 100%+. Add a conditional visible Container with a warning icon and text that only shows when spent exceeds limit. Trigger a push notification from a Cloud Function when the ratio crosses 90% — query budgets nightly and send per-user alerts.
1// Custom Function: getBudgetProgress.dart2double getBudgetProgress(3 double spent, double limit) {4 if (limit <= 0) return 0.0;5 return (spent / limit).clamp(0.0, 1.0);6}78// Color helper9import 'package:flutter/material.dart';10Color progressColor(double ratio) {11 if (ratio >= 1.0) return Colors.red;12 if (ratio >= 0.8) return Colors.amber;13 return Colors.green;14}Expected result: Each budget card shows a color-coded progress bar. Cards where spending exceeds the limit display a red warning banner.
Automate recurring transactions with a scheduled Cloud Function
Automate recurring transactions with a scheduled Cloud Function
For monthly rent, weekly subscriptions, or daily coffee budgets, create a recurringTemplates Firestore collection with fields matching transactions plus a nextRunDate (Timestamp) and frequencyDays (Integer). Deploy a Firebase Cloud Function scheduled to run daily. The function queries recurringTemplates where nextRunDate is less than or equal to today, creates a transaction document for each result, then updates nextRunDate by adding frequencyDays. In FlutterFlow add a Create Recurring option on your AddTransaction form that writes to recurringTemplates instead of transactions. Show active recurring templates on a separate Recurring tab so users can pause or delete them.
1// functions/processRecurring.js2const { onSchedule } = require('firebase-functions/v2/scheduler');3const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');45exports.processRecurring = onSchedule('every day 06:00', async () => {6 const db = getFirestore();7 const now = Timestamp.now();8 const snap = await db.collection('recurringTemplates')9 .where('nextRunDate', '<=', now)10 .where('active', '==', true)11 .get();12 const batch = db.batch();13 snap.docs.forEach(doc => {14 const data = doc.data();15 const txRef = db.collection('transactions').doc();16 batch.set(txRef, {17 userId: data.userId,18 accountId: data.accountId,19 amount: data.amount,20 category: data.category,21 description: data.description,22 date: now,23 isRecurring: true,24 recurringId: doc.id,25 });26 const nextRun = new Date(data.nextRunDate.toDate());27 nextRun.setDate(nextRun.getDate() + data.frequencyDays);28 batch.update(doc.ref, { nextRunDate: Timestamp.fromDate(nextRun) });29 });30 await batch.commit();31});Expected result: Recurring transactions appear automatically in users' transaction histories on the correct dates.
Complete working example
1const { onSchedule } = require('firebase-functions/v2/scheduler');2const { onCall } = require('firebase-functions/v2/https');3const { getFirestore, Timestamp } = require('firebase-admin/firestore');4const { getMessaging } = require('firebase-admin/messaging');5const { initializeApp } = require('firebase-admin/app');67initializeApp();89// Process recurring transactions daily10exports.processRecurring = onSchedule('every day 06:00', async () => {11 const db = getFirestore();12 const now = Timestamp.now();13 const snap = await db.collection('recurringTemplates')14 .where('nextRunDate', '<=', now)15 .where('active', '==', true).get();16 const batch = db.batch();17 snap.docs.forEach(doc => {18 const d = doc.data();19 batch.set(db.collection('transactions').doc(), {20 userId: d.userId, accountId: d.accountId,21 amount: d.amount, category: d.category,22 description: d.description, date: now,23 isRecurring: true, recurringId: doc.id,24 });25 const next = new Date(d.nextRunDate.toDate());26 next.setDate(next.getDate() + d.frequencyDays);27 batch.update(doc.ref, { nextRunDate: Timestamp.fromDate(next) });28 });29 await batch.commit();30});3132// Check budgets and send alerts33exports.checkBudgets = onSchedule('every day 08:00', async () => {34 const db = getFirestore();35 const now = new Date();36 const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);37 const budgetsSnap = await db.collection('budgets')38 .where('periodStart', '<=', Timestamp.fromDate(now)).get();39 for (const budgetDoc of budgetsSnap.docs) {40 const b = budgetDoc.data();41 const txSnap = await db.collection('transactions')42 .where('userId', '==', b.userId)43 .where('category', '==', b.category)44 .where('date', '>=', Timestamp.fromDate(startOfMonth)).get();45 const spent = txSnap.docs.reduce((sum, d) => {46 const amt = d.data().amount || 0;47 return sum + (amt < 0 ? Math.abs(amt) : 0);48 }, 0);49 const ratio = spent / b.limitAmount;50 if (ratio >= 0.9) {51 const userSnap = await db.collection('users').doc(b.userId).get();52 const token = userSnap.data()?.fcmToken;53 if (token) {54 await getMessaging().send({55 token,56 notification: {57 title: `Budget Alert: ${b.category}`,58 body: `You've used ${Math.round(ratio * 100)}% of your ${b.category} budget.`,59 },60 });61 }62 }63 }64});Common mistakes when creating a Personal Finance Management Tool in FlutterFlow
Why it's a problem: Storing a running account balance as a separate Firestore field
How to avoid: Always calculate the current balance by summing all transaction amounts for the account. Use a Cloud Function or Custom Function to compute this on demand, and cache it only as a display value — never as the source of truth.
Why it's a problem: Using FlutterFlow's backend query to load all transactions for a PieChart calculation
How to avoid: Use a Cloud Function that runs the aggregation server-side and returns only the summary map, or use Firestore's native sum aggregation query (available in recent SDK versions).
Why it's a problem: Storing amounts as Strings instead of Doubles
How to avoid: Always store amount as a Firestore Number (Double) field. Multiply by 100 and store as an Integer if you need to avoid floating-point rounding (e.g., store 1050 for $10.50).
Why it's a problem: Not filtering transactions by date range in the PieChart query
How to avoid: Add a date range filter to the transactions query: date >= start of current month AND date <= end of current month.
Best practices
- Store amounts as integers in the smallest currency unit (cents) to avoid floating-point precision errors in financial calculations.
- Scope all Firestore queries with a userId filter and enforce this in security rules so users can never read other users' financial data.
- Add a soft-delete flag (isDeleted: true) instead of hard-deleting transactions so users can undo accidental deletions.
- Use Firestore composite indexes on (userId, date) and (userId, category, date) to keep queries fast as the transaction history grows.
- Cache the monthly spending summary in a Firestore monthlyStats document updated by a Cloud Function to avoid re-aggregating on every dashboard open.
- Give users a CSV export option (generated by a Cloud Function) so they can import their data into spreadsheet tools.
- Send budget alerts at a threshold (90%) before the limit is hit — users appreciate the warning more than the 'you overspent' notification.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a personal finance management app in FlutterFlow with Firestore. Explain the best data model for accounts, transactions, and budgets. How should I handle balance calculation, category aggregation for charts, and recurring transactions? What are the key mistakes to avoid with financial data in Firestore?
In my FlutterFlow finance app I need to display a PieChart showing spending by category for the current month. Write a Custom Function in Dart that accepts a list of Firestore transaction documents and returns a Map<String, double> of category to total spent, then explain how to bind this to FlutterFlow's PieChart widget.
Frequently asked questions
How many transactions can Firestore handle before performance degrades?
A single Firestore collection can hold millions of documents without structural degradation. Performance in FlutterFlow depends on your query design. Always filter by userId and date range — never load all transactions. With proper indexes, queries over 100,000+ documents are fast.
How do I handle multiple currencies?
Store each transaction with an explicit currency field and a baseAmount in a single reference currency (e.g., USD). Use a Cloud Function that fetches exchange rates daily from an API like Open Exchange Rates and stores them in Firestore. Calculate displayed totals by multiplying baseAmount by the stored rate.
Can I import bank transactions from CSV in FlutterFlow?
Yes, via a Cloud Function. Build a file upload in FlutterFlow (Storage bucket), then trigger a Cloud Function on file creation that reads the CSV, parses each row, and batch-writes transaction documents to Firestore. FlutterFlow's FilePicker widget handles the client-side file selection.
How do I prevent users from editing transactions that have already been reconciled?
Add an isReconciled boolean field to each transaction. In FlutterFlow, add a condition to the edit button that hides it when isReconciled is true. Enforce this in Firestore security rules: deny update requests where the existing document has isReconciled == true.
What is the best way to show a monthly spending trend chart?
Create a monthlySummaries Firestore collection updated by a Cloud Function at the end of each month. Each document stores userId, month, year, and a map of category totals. Bind a LineChart in FlutterFlow to the last 12 monthly summary documents for the current user, sorted by month.
How do I handle split transactions (e.g., one grocery trip split across Food and Household)?
Store the original transaction with the total amount, then create sub-transaction documents linked by a parentTransactionId field. In your category totals function, skip documents with a parentTransactionId and sum only the sub-transactions. This keeps the transaction history accurate while supporting category splits.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation