# How to Build a Finance Tracker with Lovable

- Tool: How to Build with Lovable
- Difficulty: Intermediate
- Compatibility: Lovable Pro or higher
- Last updated: April 2026

## TL;DR

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.

## Before you start

- 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

## Step-by-step guide

### 1. 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.

```
Build a personal finance tracker. Create these Supabase tables:

- categories: id, user_id, name, type (income|expense), icon (text, emoji), color (hex string), created_at
- 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_at
- 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)
- 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_at

Create two Supabase views:
1. category_spending: user_id, category_id, category_name, category_color, month (first day), total_amount — groups transactions by user+category+month
2. monthly_summary: user_id, month, income_total, expense_total, net — groups by user+month with conditional sums

RLS: 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.

### 2. 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.

```
Build the transaction management UI:

1. Transaction list page at src/pages/Transactions.tsx:
   - Fetch transactions for the current user with category join, ordered by transaction_date DESC
   - DataTable columns: Date, Category (Badge with category color), Description, Amount (green for income, red for expense), Actions (edit/delete)
   - Filter row above table: month picker (Select showing last 12 months), type filter (All/Income/Expense), category Select
   - Summary row at top: total income (green), total expenses (red), net (positive=green, negative=red)

2. Add Transaction Dialog (opened by a floating Add Button):
   - Form fields: Type toggle (Income/Expense), Category Select (filtered by selected type), Amount Input (numeric), Description Input, Date DatePicker (default today), Is Recurring Switch
   - If Is Recurring is on, show Frequency Select (Daily/Weekly/Monthly)
   - On submit: insert into transactions. If recurring, also insert into recurring_transactions with next_due_date = transaction_date + frequency interval
   - Zod schema validates: amount > 0, date is not in future, category is required
```

> Pro 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.

### 3. 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.

```
// src/components/charts/SpendingPieChart.tsx
import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts'
import { useQuery } from '@tanstack/react-query'
import { supabase } from '@/integrations/supabase/client'

interface Props {
  userId: string
  month: string
}

export function SpendingPieChart({ userId, month }: Props) {
  const { data } = useQuery({
    queryKey: ['category-spending', userId, month],
    queryFn: async () => {
      const { data, error } = await supabase
        .from('category_spending')
        .select('category_name, category_color, total_amount')
        .eq('user_id', userId)
        .eq('month', month)
      if (error) throw error
      return data ?? []
    },
  })

  if (!data?.length) return <p className="text-muted-foreground text-sm">No expense data for this month.</p>

  return (
    <ResponsiveContainer width="100%" height={320}>
      <PieChart>
        <Pie
          data={data}
          dataKey="total_amount"
          nameKey="category_name"
          cx="50%"
          cy="50%"
          outerRadius={100}
          label={({ category_name, percent }) =>
            `${category_name} ${(percent * 100).toFixed(0)}%`
          }
        >
          {data.map((entry, index) => (
            <Cell key={index} fill={entry.category_color} />
          ))}
        </Pie>
        <Tooltip formatter={(value: number) => `$${value.toFixed(2)}`} />
        <Legend />
      </PieChart>
    </ResponsiveContainer>
  )
}
```

**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.

### 4. 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.

```
Build a budgets page at src/pages/Budgets.tsx:

1. Budget Cards grid (2 columns on desktop, 1 on mobile):
   - Each Card shows: category icon + name, month label, budget_amount (limit), spent_amount (from category_spending view), remaining amount
   - Progress bar: width = (spent_amount / budget_amount) * 100, capped at 100%
   - Progress bar color: green if < 75%, yellow if 75-99%, red if >= 100%
   - Show 'Over budget by $X' text in red if spent > budget
   - Cards sorted by: over-budget first, then by percentage used descending

2. Set Budget Dialog (Button on each card or Add Budget Button):
   - Category Select (expense categories only)
   - Month picker (Select)
   - Budget amount Input
   - On submit: upsert into budgets (update if category+month exists, insert if new)

3. Month selector at page top to switch which month's budgets are shown (default: current month)

Query: 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.

### 5. 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.

```
Create a Supabase Edge Function at supabase/functions/process-recurring/index.ts that:

1. 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$$);

2. Function logic:
   - Use SUPABASE_SERVICE_ROLE_KEY from Deno.env.get()
   - Query all active recurring_transactions WHERE next_due_date <= today
   - For each due recurring transaction:
     a. Insert a new row into transactions (copying category_id, amount, type, description, user_id)
     b. Set transaction_date = next_due_date
     c. Update next_due_date: daily = +1 day, weekly = +7 days, monthly = +1 month (use date-fns addDays/addWeeks/addMonths)
   - Return JSON summary: { processed: number, errors: string[] }

3. Add CORS headers and handle OPTIONS preflight
```

**Expected 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 example

File: `src/components/charts/MonthlyBarChart.tsx`

```typescript
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
import { useQuery } from '@tanstack/react-query'
import { supabase } from '@/integrations/supabase/client'
import { format, subMonths, startOfMonth } from 'date-fns'

interface MonthlySummary {
  month: string
  income_total: number
  expense_total: number
  net: number
}

interface Props {
  userId: string
  monthsBack?: number
}

export function MonthlyBarChart({ userId, monthsBack = 12 }: Props) {
  const { data, isLoading } = useQuery({
    queryKey: ['monthly-summary', userId, monthsBack],
    queryFn: async () => {
      const since = format(startOfMonth(subMonths(new Date(), monthsBack - 1)), 'yyyy-MM-dd')
      const { data, error } = await supabase
        .from('monthly_summary')
        .select('month, income_total, expense_total, net')
        .eq('user_id', userId)
        .gte('month', since)
        .order('month', { ascending: true })
      if (error) throw error
      return (data ?? []).map((row: MonthlySummary) => ({
        ...row,
        label: format(new Date(row.month), 'MMM yy'),
      }))
    },
  })

  if (isLoading) return <div className="h-64 animate-pulse bg-muted rounded-lg" />
  if (!data?.length) return <p className="text-muted-foreground text-sm">No data yet. Add some transactions to see your monthly summary.</p>

  return (
    <ResponsiveContainer width="100%" height={300}>
      <BarChart data={data} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
        <CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
        <XAxis dataKey="label" tick={{ fontSize: 12 }} />
        <YAxis tickFormatter={(v) => `$${v}`} tick={{ fontSize: 12 }} />
        <Tooltip formatter={(value: number) => `$${value.toFixed(2)}`} />
        <Legend />
        <Bar dataKey="income_total" name="Income" fill="#22c55e" radius={[4, 4, 0, 0]} />
        <Bar dataKey="expense_total" name="Expenses" fill="#ef4444" radius={[4, 4, 0, 0]} />
      </BarChart>
    </ResponsiveContainer>
  )
}
```

## Common mistakes

- **Storing negative amounts for expenses** — Mixing positive and negative amounts in the same column makes SQL aggregation confusing and error-prone. SUM() returns unexpected results when you mix signs. Fix: 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.
- **Not setting RLS on the budgets table** — Without RLS, any authenticated user can read or modify any other user's budgets. This is a data privacy issue even if your app is not widely used. Fix: 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.
- **Using the client-side date for recurring transaction generation** — If a user doesn't open the app on the expected day, recurring transactions never get created. Client-side generation is unreliable. Fix: 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.
- **Not indexing transaction_date in the transactions table** — The app frequently queries transactions filtered by month. Without an index, these queries do a full table scan and slow down significantly as the transaction count grows. Fix: 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.

## 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.

---

Source: https://www.rapidevelopers.com/how-to-build-lovable/finance-tracker
© RapidDev — https://www.rapidevelopers.com/how-to-build-lovable/finance-tracker
