Build a personal finance tracker in Lovable with income and expense transactions, category-based pie and bar charts, recurring transaction logic, and budget-vs-actual comparisons powered by Supabase database functions. You'll have a full-featured money management app with visual spending insights in about 2.5 hours.
What you're building
A finance tracker has two core data flows: recording money movement and visualizing it. Transactions belong to categories (Food, Rent, Salary, etc.) and each category has a type of income or expense. Budgets are defined per category per month — a budget row stores the limit, and a Supabase view calculates spent_amount by summing transactions in that category for that month.
Recurring transactions are handled with a recurring_transactions table that stores frequency (daily, weekly, monthly) and last_generated_at. A Supabase Edge Function scheduled with pg_cron runs once per day, checks which recurring entries are due, and inserts new transactions automatically. This approach works without any client-side code.
The chart data comes from two Supabase views. The category breakdown view groups transactions by category for a given date range and is used by the pie chart. The monthly summary view groups by year-month and transaction type, powering the bar chart. Both views use SQL aggregation so the frontend only needs simple SELECT queries.
Final result
A working personal finance tracker with transaction recording, category charts, recurring entries, and budget tracking — all synced to Supabase in real time.
Tech stack
Prerequisites
- Lovable Pro account for multi-table schema generation
- Supabase project created and URL + anon key saved to Cloud tab → Secrets
- Basic understanding of income vs expense categorization for your use case
- Optional: a list of your categories and monthly budget amounts ready to enter
Build steps
Set up the financial schema with categories and transactions
Prompt Lovable to create the database schema. Categories are the foundation — every transaction and budget references a category. Getting this schema right saves rewrites later.
1Build a personal finance tracker. Create these Supabase tables:23- categories: id, user_id, name, type (income|expense), icon (text, emoji), color (hex string), created_at4- transactions: id, user_id, category_id (FK categories), amount (numeric, positive always), type (income|expense), description, transaction_date (date), is_recurring (bool default false), created_at5- budgets: id, user_id, category_id (FK categories), month (date, always first day of month e.g. 2024-01-01), budget_amount (numeric), created_at, UNIQUE(user_id, category_id, month)6- recurring_transactions: id, user_id, category_id (FK categories), amount (numeric), type (income|expense), description, frequency (daily|weekly|monthly), next_due_date (date), is_active (bool default true), created_at78Create two Supabase views:91. category_spending: user_id, category_id, category_name, category_color, month (first day), total_amount — groups transactions by user+category+month102. monthly_summary: user_id, month, income_total, expense_total, net — groups by user+month with conditional sums1112RLS: all tables require user_id = auth.uid() for all operations.Pro tip: Ask Lovable to seed 5 default categories for new users (Food, Transport, Housing, Entertainment, Salary) using a Supabase trigger on auth.users INSERT. This means users see example data immediately after signup.
Expected result: All four tables and two views are created with RLS enabled. The app shell loads with a navigation sidebar and the transaction list page is visible.
Build the transaction entry form and list
Create the main transaction interface. The form needs to feel fast to use — users will add transactions frequently. Ask Lovable to build a compact dialog form and a sortable transaction list.
1Build the transaction management UI:231. Transaction list page at src/pages/Transactions.tsx:4 - Fetch transactions for the current user with category join, ordered by transaction_date DESC5 - DataTable columns: Date, Category (Badge with category color), Description, Amount (green for income, red for expense), Actions (edit/delete)6 - Filter row above table: month picker (Select showing last 12 months), type filter (All/Income/Expense), category Select7 - Summary row at top: total income (green), total expenses (red), net (positive=green, negative=red)892. Add Transaction Dialog (opened by a floating Add Button):10 - Form fields: Type toggle (Income/Expense), Category Select (filtered by selected type), Amount Input (numeric), Description Input, Date DatePicker (default today), Is Recurring Switch11 - If Is Recurring is on, show Frequency Select (Daily/Weekly/Monthly)12 - On submit: insert into transactions. If recurring, also insert into recurring_transactions with next_due_date = transaction_date + frequency interval13 - Zod schema validates: amount > 0, date is not in future, category is requiredPro tip: Ask Lovable to add a keyboard shortcut: pressing 'N' anywhere on the page opens the Add Transaction dialog. This makes data entry much faster for daily tracking.
Expected result: The transaction list shows with filters and a summary row. The Add Transaction dialog opens and successfully inserts a new transaction that appears in the list immediately.
Add category charts with Recharts
Build the charts page that gives users visual insight into their spending. A pie chart shows where money goes, and a bar chart shows the income-vs-expense trend over time.
1// src/components/charts/SpendingPieChart.tsx2import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts'3import { useQuery } from '@tanstack/react-query'4import { supabase } from '@/integrations/supabase/client'56interface Props {7 userId: string8 month: string9}1011export function SpendingPieChart({ userId, month }: Props) {12 const { data } = useQuery({13 queryKey: ['category-spending', userId, month],14 queryFn: async () => {15 const { data, error } = await supabase16 .from('category_spending')17 .select('category_name, category_color, total_amount')18 .eq('user_id', userId)19 .eq('month', month)20 if (error) throw error21 return data ?? []22 },23 })2425 if (!data?.length) return <p className="text-muted-foreground text-sm">No expense data for this month.</p>2627 return (28 <ResponsiveContainer width="100%" height={320}>29 <PieChart>30 <Pie31 data={data}32 dataKey="total_amount"33 nameKey="category_name"34 cx="50%"35 cy="50%"36 outerRadius={100}37 label={({ category_name, percent }) =>38 `${category_name} ${(percent * 100).toFixed(0)}%`39 }40 >41 {data.map((entry, index) => (42 <Cell key={index} fill={entry.category_color} />43 ))}44 </Pie>45 <Tooltip formatter={(value: number) => `$${value.toFixed(2)}`} />46 <Legend />47 </PieChart>48 </ResponsiveContainer>49 )50}Expected result: The charts page shows a pie chart with colored segments per category and a bar chart with grouped income/expense bars for each month.
Build the budget tracking Cards
Ask Lovable to create the budgets page where users set monthly limits per category and see their spending progress. Progress bars change color as users approach or exceed the limit.
1Build a budgets page at src/pages/Budgets.tsx:231. Budget Cards grid (2 columns on desktop, 1 on mobile):4 - Each Card shows: category icon + name, month label, budget_amount (limit), spent_amount (from category_spending view), remaining amount5 - Progress bar: width = (spent_amount / budget_amount) * 100, capped at 100%6 - Progress bar color: green if < 75%, yellow if 75-99%, red if >= 100%7 - Show 'Over budget by $X' text in red if spent > budget8 - Cards sorted by: over-budget first, then by percentage used descending9102. Set Budget Dialog (Button on each card or Add Budget Button):11 - Category Select (expense categories only)12 - Month picker (Select)13 - Budget amount Input14 - On submit: upsert into budgets (update if category+month exists, insert if new)15163. Month selector at page top to switch which month's budgets are shown (default: current month)1718Query: join budgets with category_spending view on category_id + month to get both budget_amount and spent_amount in one query.Pro tip: Ask Lovable to add a 'Copy from last month' Button that duplicates all budget amounts from the previous month into the current month. This saves users from re-entering budgets every month.
Expected result: Budget Cards show for all budgeted categories. Progress bars are colored correctly. Setting a new budget via the dialog immediately adds a new Card.
Add the recurring transaction Edge Function
Create a Supabase Edge Function that runs on a schedule and auto-generates transactions from recurring entries. This runs server-side so users never have to trigger it manually.
1Create a Supabase Edge Function at supabase/functions/process-recurring/index.ts that:231. Is triggered by a pg_cron job: SELECT cron.schedule('process-recurring-daily', '0 6 * * *', $$SELECT net.http_post(url:='https://your-project.supabase.co/functions/v1/process-recurring', headers:='{"Authorization": "Bearer YOUR_SERVICE_ROLE_KEY"}'::jsonb) AS request_id$$);452. Function logic:6 - Use SUPABASE_SERVICE_ROLE_KEY from Deno.env.get()7 - Query all active recurring_transactions WHERE next_due_date <= today8 - For each due recurring transaction:9 a. Insert a new row into transactions (copying category_id, amount, type, description, user_id)10 b. Set transaction_date = next_due_date11 c. Update next_due_date: daily = +1 day, weekly = +7 days, monthly = +1 month (use date-fns addDays/addWeeks/addMonths)12 - Return JSON summary: { processed: number, errors: string[] }13143. Add CORS headers and handle OPTIONS preflightExpected result: The Edge Function is deployed. Calling it manually returns a JSON summary showing how many recurring transactions were processed. The pg_cron job runs it automatically each morning.
Complete code
1import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'2import { useQuery } from '@tanstack/react-query'3import { supabase } from '@/integrations/supabase/client'4import { format, subMonths, startOfMonth } from 'date-fns'56interface MonthlySummary {7 month: string8 income_total: number9 expense_total: number10 net: number11}1213interface Props {14 userId: string15 monthsBack?: number16}1718export function MonthlyBarChart({ userId, monthsBack = 12 }: Props) {19 const { data, isLoading } = useQuery({20 queryKey: ['monthly-summary', userId, monthsBack],21 queryFn: async () => {22 const since = format(startOfMonth(subMonths(new Date(), monthsBack - 1)), 'yyyy-MM-dd')23 const { data, error } = await supabase24 .from('monthly_summary')25 .select('month, income_total, expense_total, net')26 .eq('user_id', userId)27 .gte('month', since)28 .order('month', { ascending: true })29 if (error) throw error30 return (data ?? []).map((row: MonthlySummary) => ({31 ...row,32 label: format(new Date(row.month), 'MMM yy'),33 }))34 },35 })3637 if (isLoading) return <div className="h-64 animate-pulse bg-muted rounded-lg" />38 if (!data?.length) return <p className="text-muted-foreground text-sm">No data yet. Add some transactions to see your monthly summary.</p>3940 return (41 <ResponsiveContainer width="100%" height={300}>42 <BarChart data={data} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>43 <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />44 <XAxis dataKey="label" tick={{ fontSize: 12 }} />45 <YAxis tickFormatter={(v) => `$${v}`} tick={{ fontSize: 12 }} />46 <Tooltip formatter={(value: number) => `$${value.toFixed(2)}`} />47 <Legend />48 <Bar dataKey="income_total" name="Income" fill="#22c55e" radius={[4, 4, 0, 0]} />49 <Bar dataKey="expense_total" name="Expenses" fill="#ef4444" radius={[4, 4, 0, 0]} />50 </BarChart>51 </ResponsiveContainer>52 )53}Customization ideas
Multi-currency support
Add a currency column to transactions and a default_currency to user profiles. Use a free exchange rate API (like exchangerate-api.com) in an Edge Function to convert all amounts to the user's home currency for chart display. Store amounts in original currency but display converted amounts.
CSV import for bank transactions
Add an import page where users upload a CSV file from their bank. An Edge Function parses the CSV, maps columns to your transaction schema, deduplicates by amount+date+description, and returns a preview for the user to review before confirming the import.
Savings goals progress
Add a savings_goals table with target_amount and target_date. Users can allocate transactions marked as type 'savings' toward specific goals. Show a progress Card per goal with a Recharts RadialBarChart showing how close they are to the target.
Spending alerts via email
Add a notification trigger: when a user's spending in a category exceeds 80% of their budget, send an email via Resend from an Edge Function. Store the alert threshold and last_notified_at per budget row so users aren't spammed with repeated emails.
Shared household tracking
Add a households table and link users to a household. Transactions can be marked as personal or shared. A shared view shows combined household spending split by member, useful for couples or roommates tracking finances together.
Common pitfalls
Pitfall: Storing negative amounts for expenses
How to avoid: Always store amounts as positive numerics. Use the type column (income|expense) to determine the sign when displaying or calculating totals. Your SQL views handle the sign logic: SUM(CASE WHEN type='income' THEN amount ELSE 0 END) AS income_total.
Pitfall: Not setting RLS on the budgets table
How to avoid: Ensure all five tables have RLS enabled with policies that check user_id = auth.uid(). Ask Lovable to verify RLS is enabled by showing the Cloud tab → Database → Tables and confirming the shield icon is green for each table.
Pitfall: Using the client-side date for recurring transaction generation
How to avoid: Use the Supabase Edge Function + pg_cron approach described in Step 5. Server-side scheduling ensures transactions are generated regardless of whether the user is active.
Pitfall: Not indexing transaction_date in the transactions table
How to avoid: Ask Lovable to add: CREATE INDEX idx_transactions_user_date ON transactions(user_id, transaction_date DESC). This index makes month-filtered queries instant even with thousands of transactions.
Best practices
- Always store financial amounts as numeric (not float) in PostgreSQL. Float types have rounding errors that cause incorrect totals in financial calculations.
- Use Supabase views for aggregated data (monthly summaries, budget vs actual) rather than computing these in the frontend. Views are faster and keep business logic in the database.
- Set budget_amount as a positive non-null constraint in the database. Add a check constraint: CHECK (budget_amount > 0). This prevents invalid data without needing client-side validation.
- Enable Supabase Realtime on the transactions table so the transaction list updates immediately when a new transaction is inserted from the recurring function. This gives users a live feed without polling.
- Add created_at and updated_at timestamps to every table. For the transactions table, never allow updates — only inserts and deletes. This creates an audit trail of all financial entries.
- Use date (not timestamp) for transaction_date. Financial transactions belong to a calendar day, not a specific time. Using date avoids timezone confusion when users in different time zones use the app.
AI prompts to try
Copy these prompts to build this project faster.
I'm building a personal finance tracker with Supabase. I have transactions and budgets tables. Help me write a PostgreSQL view called budget_vs_actual that joins budgets with aggregated transactions grouped by user_id, category_id, and month. The view should return: budget_amount, spent_amount (SUM of transactions for that category+month), remaining_amount (budget_amount - spent_amount), and percentage_used (spent_amount / budget_amount * 100). Handle the case where spent_amount is NULL (no transactions yet) by returning 0.
Add a net worth tracker section to the finance app. Create a new table: accounts (id, user_id, name, account_type: checking|savings|credit|investment|loan, current_balance, currency, created_at). Add a page at /net-worth showing: each account as a Card with balance (red for liabilities like credit/loan, green for assets), total assets sum, total liabilities sum, and net worth (assets - liabilities) in a large prominent number at the top. Add an Edit Balance Dialog per account to update current_balance. Use a Recharts AreaChart to show net worth over time if user logs historical snapshots.
In Supabase SQL editor, create a function get_spending_trend(p_user_id uuid, p_category_id uuid, p_months int) that returns monthly spending totals for a specific category over the last p_months months. Use generate_series to fill in months with zero spending so the chart has no gaps. Return columns: month (date), total_amount (numeric). Use this function in a Recharts LineChart to show spending trends per category.
Frequently asked questions
Can this finance tracker handle multiple currencies?
Not out of the box with this guide — all amounts are stored in a single currency. To add multi-currency support, add a currency column to transactions and use an Edge Function to fetch exchange rates from a free API. Store amounts in original currency and convert to the user's home currency in the views using the exchange rate. See the customization ideas section for details.
How do I prevent other users from seeing my financial data?
Row Level Security (RLS) on all five tables with policies that check user_id = auth.uid() ensures users can only access their own data. Step 1 includes RLS setup. Verify it's working by checking Cloud tab → Database in Lovable — each table should show RLS as enabled.
Can I import my bank statement history?
Yes, through the CSV import customization idea described in this guide. Most banks export transactions as CSV files. An Edge Function can parse the CSV, deduplicate entries by date+amount+description, and bulk-insert them into the transactions table. Ask Lovable to add the import feature as a follow-up prompt after completing the base build.
How accurate are recurring transactions when using the Edge Function approach?
Very accurate. The pg_cron job runs the Edge Function once per day at 6 AM UTC. Any recurring transaction with a next_due_date on or before today gets created. If the function fails one day (rare), it catches up the next day because it processes all entries where next_due_date <= today, not just today exactly.
What happens to chart data if I delete a transaction?
The Supabase views recalculate automatically on every query, so deleting a transaction immediately removes it from all chart data. There is no cache to invalidate. If you use react-query on the frontend (which Lovable generates by default), the chart components refetch after any mutation and update the UI within seconds.
Can I share the finance tracker with a partner or family member?
The base build is single-user. To add shared access, add a households table, link users to a household, and update all RLS policies to allow access based on household membership. See the 'Shared household tracking' customization idea for the approach. Prompt Lovable to add the household feature after completing the base build.
Is this finance data stored securely in Supabase?
Supabase stores data in PostgreSQL on AWS infrastructure with encryption at rest and in transit. RLS ensures application-level isolation between users. Supabase is SOC 2 Type II certified on the Team plan. For a personal finance tracker, the Free or Pro plan is appropriate. Avoid storing sensitive credentials (bank passwords, card numbers) — this app stores only amounts and categories, not account credentials.
Where can I get help if I need more advanced financial features?
RapidDev builds production Lovable apps including finance tools with multi-currency support, bank integrations, and advanced reporting. Reach out if you need features beyond this guide.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation