# How to Add a Budget Planner to Your App (Copy-Paste Prompts Included)

- Tool: App Features
- Last updated: July 2026

## TL;DR

A budget planner needs a monthly income vs expense dashboard with Recharts, a transaction entry form with recurring expense support, a category breakdown chart, savings goals with progress tracking, and CSV import via Papa Parse. With Lovable or V0 you can ship a working budget planner in 4–8 hours for $0–$25/month. The critical detail: Supabase RLS must be applied to every table — missing it on the categories table leaks one user's categories to all other users.

## What a Budget Planner Feature Actually Is

A budget planner tracks every dollar that comes in and goes out, groups spending by category, and shows the user whether they are on track for their savings goals. The feature has three distinct UX modes: a dashboard that summarizes the current month at a glance, a transaction entry flow for logging income and expenses (manually or by importing a bank CSV), and a savings goals tracker that shows progress bars and projected completion dates. The product decisions that define a good budget planner are: how recurring transactions are handled (manually cloned each month vs automatically created), whether bank CSV import maps to categories automatically, and whether RLS is applied to every table so one user can never see another's financial data. That last point is not a nice-to-have — it is a trust-destroying bug if missed.

## Anatomy of the Feature

Six components. Recharts SSR conflicts and the missing RLS on the categories table are where first builds fail in ways that are hard to spot.

- **Budget overview dashboard** (ui): Recharts BarChart for income vs expenses by month. PieChart for category spending breakdown. shadcn/ui Card components for summary KPIs: total income, total expenses, net balance, and savings rate percentage. All charts are Client Components in Next.js.
- **Transaction entry** (ui): react-hook-form with zod validation for the add-transaction form: date, amount, category (combobox), description, type (income/expense), and a recurring toggle. When recurring is enabled, a frequency selector appears (weekly, monthly, yearly). shadcn/ui Command or Combobox for category autocomplete.
- **Category system** (data): Predefined categories (Housing, Food, Transport, Entertainment, Healthcare, Subscriptions, Income) plus user-defined custom categories stored in a Supabase categories table. Each category has a name, color (hex), icon, and budget_limit. Colors are used consistently across all charts.
- **Savings goals** (data): Supabase savings_goals table: id, user_id, name, target_amount, current_amount, target_date, color. The progress bar shows percentage complete. Projected completion date is calculated client-side from current_amount and the average monthly contribution over the last 3 months.
- **CSV import** (backend): Papa Parse runs client-side to parse the uploaded bank CSV. A column mapping UI lets users match their bank's column names to the app's schema (date, amount, description). Keyword-based category matching assigns categories automatically. A Supabase Edge Function handles bulk insert with duplicate detection by hashing (date, amount, description) per user.
- **Budget rules engine** (backend): Supabase pg_cron (Pro plan) or n8n webhook triggers monthly budget reset at the start of each month: creates recurring transactions for the new month and sends a Resend email summarizing the previous month's spending vs budget limits.

## Data model

Four tables for transactions, categories, savings goals, and recurring schedules with Row Level Security on every table. Run in the Supabase SQL editor.

```sql
create table public.categories (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade not null,
  name text not null,
  color text not null default '#6366f1',
  icon text,
  budget_limit numeric,
  is_default bool not null default false,
  created_at timestamptz not null default now()
);

alter table public.categories enable row level security;

create policy "Users can manage their own categories"
  on public.categories for all
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

create table public.transactions (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade not null,
  amount numeric not null,
  type text not null check (type in ('income', 'expense')),
  category_id uuid references public.categories(id) on delete set null,
  description text,
  transaction_date date not null,
  is_recurring bool not null default false,
  recurring_frequency text check (recurring_frequency in ('weekly', 'monthly', 'yearly')),
  import_hash text,
  created_at timestamptz not null default now()
);

alter table public.transactions enable row level security;

create policy "Users can manage their own transactions"
  on public.transactions for all
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

create unique index transactions_import_dedup_idx
  on public.transactions (user_id, import_hash)
  where import_hash is not null;

create index transactions_user_date_idx
  on public.transactions (user_id, transaction_date desc);

create index transactions_user_category_idx
  on public.transactions (user_id, category_id);

create table public.savings_goals (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade not null,
  name text not null,
  target_amount numeric not null,
  current_amount numeric not null default 0,
  target_date date,
  color text not null default '#10b981',
  created_at timestamptz not null default now()
);

alter table public.savings_goals enable row level security;

create policy "Users can manage their own savings goals"
  on public.savings_goals for all
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

create index savings_goals_user_id_idx on public.savings_goals (user_id);
```

The unique partial index on (user_id, import_hash) WHERE import_hash IS NOT NULL enables duplicate detection during CSV import: generate a hash of date+amount+description before inserting, and use INSERT ... ON CONFLICT DO NOTHING. This prevents re-importing the same bank statement twice.

## Build paths

### Lovable — fit 4/10, 4–7 hours

Strong path: Lovable's Supabase Cloud integration provisions all tables, and it generates Recharts dashboards, react-hook-form transaction entry, and Papa Parse CSV import well. The main risk is non-performant queries — be explicit about filtering by month and user_id in the prompt.

1. Create a new Lovable project and connect Lovable Cloud so all four tables and auth are provisioned automatically
2. Paste the prompt below into Agent Mode and let it build the dashboard, transaction form, category manager, savings goals, and CSV import
3. Verify RLS on the categories table immediately — open the Supabase Dashboard under Authentication → Policies and confirm a policy exists for categories
4. Test CSV import with a real bank export from your bank to verify the column mapper works and duplicates are detected on re-upload
5. Add Resend in the Cloud tab under Secrets to activate the monthly budget summary email

Starter prompt:

```
Build a personal budget planner. Supabase tables: categories (id, user_id, name, color hex, icon, budget_limit numeric), transactions (id, user_id, amount numeric, type CHECK IN ('income','expense'), category_id FK, description, transaction_date date, is_recurring bool, recurring_frequency CHECK IN ('weekly','monthly','yearly'), import_hash text), savings_goals (id, user_id, name, target_amount numeric, current_amount numeric, target_date date, color hex). Add RLS to ALL three tables restricting SELECT/INSERT/UPDATE/DELETE to auth.uid() = user_id. Dashboard page: Recharts BarChart showing income vs expenses grouped by month for the last 6 months (filter by user_id and current month in SQL); Recharts PieChart for spending by category for current month; shadcn/ui KPI cards for total income, total expenses, net balance (income minus expenses), and savings rate percentage. Transaction entry form: react-hook-form with zod, fields: date (date picker), amount, type (income/expense toggle), category (shadcn/ui Combobox autocomplete), description, recurring toggle with frequency selector. On submit, insert into transactions. Filter transactions WHERE transaction_date >= date_trunc('month', NOW()) for current month calculations — add an index on (user_id, transaction_date). Savings goals: progress bar showing current_amount/target_amount percentage; project completion date based on average monthly contribution over last 3 months; allow manual update of current_amount. CSV import: file upload, parse with Papa Parse client-side, show column mapping UI where user assigns CSV columns to date/amount/description/type fields; generate import_hash = md5(transaction_date + '|' + amount + '|' + description) per row; call a Supabase Edge Function that bulk inserts using INSERT ... ON CONFLICT (user_id, import_hash) DO NOTHING. Budget limits: show warning badge when a category's spending exceeds 80% of budget_limit for the current month. Monthly comparison: show this month vs last month percentage change per category. Handle edge cases: zero income month (show $0, not divide-by-zero), expenses exceeding income (show deficit in red), user with no transactions (show empty state with 'Add your first transaction' CTA), recurring transactions for current month (auto-create at dashboard load if not yet created for this month).
```

Limitations:

- Lovable may generate queries that fetch all transactions and filter client-side — verify that WHERE clauses include transaction_date and user_id filters to avoid loading the entire transaction history
- Complex category matching for CSV import (mapping bank descriptions to categories by keyword) often needs 1–2 correction prompts to generate accurately
- Monthly budget reset for recurring transactions needs explicit prompting — Lovable may not generate the auto-creation logic without it

### V0 — fit 4/10, 4–6 hours

Clean Next.js dashboard with shadcn/ui and Recharts. Server Components for initial data fetching and Server Actions for transaction mutations work naturally. Best when the budget planner is part of a larger Next.js financial tool.

1. Prompt V0 with the spec below to generate the dashboard page, transaction form, and chart components
2. Move all Recharts chart components to Client Components with 'use client' — V0 sometimes generates them in Server Components causing window errors
3. Add NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY in the V0 Vars panel
4. Run the SQL schema from this page in the Supabase SQL editor
5. Verify that Server Actions for transaction mutations include a user_id check via Supabase getUser() before every write

Starter prompt:

```
Build a Next.js personal budget planner. Supabase tables: categories (id, user_id, name, color, icon, budget_limit), transactions (id, user_id, amount numeric, type text CHECK IN ('income','expense'), category_id FK, description, transaction_date date, is_recurring bool, recurring_frequency, import_hash text), savings_goals (id, user_id, name, target_amount, current_amount, target_date, color). All tables have RLS: auth.uid() = user_id on all operations. Dashboard: a Server Component fetches this month's transactions from Supabase (WHERE user_id = auth.uid() AND transaction_date >= date_trunc('month', NOW())) and passes data to client chart components. ChartSection (Client Component with 'use client'): Recharts BarChart for monthly income vs expenses; Recharts PieChart for category breakdown. KPI cards: total income, total expenses, net balance, savings rate. Transaction list: Server Component paginated by month with category filter. Add transaction: Server Action validates with zod (amount > 0, valid date, valid type), inserts into transactions with auth.uid() as user_id. CSV import: Client Component using Papa Parse to parse uploaded file, column mapping UI, calls a Server Action that generates import_hash per row and upserts with ON CONFLICT DO NOTHING. Savings goals: Client Component with progress bars and projected completion date calculation. Budget alerts: show warning when category spending exceeds 80% of budget_limit. Do not use localStorage for any financial data — all state in Supabase with RLS. Use Supabase server client (createServerComponentClient) for Server Components and server client for Server Actions.
```

Limitations:

- Recharts must be in Client Components — V0 may initially generate chart code in Server Components causing 'window is not defined' errors
- V0 sometimes generates localStorage for budget category state instead of Supabase — specify Supabase explicitly in the prompt
- V0 does not provision the Supabase database — run the SQL schema manually in the Supabase dashboard

### Custom — fit 3/10, 1–2 weeks

Custom development is only justified when the budget planner needs automatic bank transaction sync via Plaid or TrueLayer, multi-user household budget with shared access, or tax categorization export for accountants.

1. Plaid or TrueLayer integration for automatic bank transaction import — eliminates the CSV import flow entirely with live bank sync
2. Multi-user household budget with granular permission model: view-only vs edit access per category
3. Tax categorization export formatted for accountants (IRS Schedule C categories, VAT codes for EU users)
4. Compliance audit trail with immutable transaction history for regulated financial use cases

Limitations:

- Plaid production access costs $500+/month (approx) and requires a compliance review — only justified for financial SaaS with bank sync as a core feature
- Multi-user household budgets require a more complex RLS model (shared access tokens, not just user_id equality)

## Gotchas

- **Recharts PieChart crashes with 'window is not defined' in V0/Next.js** — Recharts imports browser globals on load. When Next.js renders a Server Component or performs static generation, there is no browser — the Recharts import throws immediately. This is not a runtime error you will see in development if you test with the browser open; it surfaces only on build or when the page is rendered server-side in production. Fix: Move every Recharts component to a Client Component by adding 'use client' at the top of the file. Alternatively, use next/dynamic with { ssr: false } to defer the import. Never import Recharts components in Server Components or in files that are server-rendered.
- **CSV import duplicates transactions on re-upload** — AI tools generate a straightforward bulk INSERT for CSV import without checking whether the rows have been imported before. When a user re-uploads the same bank statement (common when backfilling history), all transactions are inserted again, doubling the balance and corrupting all budget calculations. Fix: Generate an import_hash for each row by concatenating transaction_date, amount, and description and hashing with MD5 or SHA-256. Store the hash in the import_hash column. Use INSERT ... ON CONFLICT (user_id, import_hash) DO NOTHING with the unique partial index on (user_id, import_hash) WHERE import_hash IS NOT NULL. Show the user a count of 'X new transactions imported, Y duplicates skipped'.
- **Budget calculations are wrong for recurring expenses mid-month** — AI tools commonly calculate the monthly total by summing all transactions in the table without a date filter. This returns the all-time total instead of the current-month total, making the budget summary wildly incorrect for users who have been tracking expenses for more than one month. Fix: Always filter with WHERE transaction_date >= date_trunc('month', NOW()) AND transaction_date < date_trunc('month', NOW()) + interval '1 month' for current-month calculations. Add a composite index on (user_id, transaction_date) to keep this query fast once the table accumulates thousands of rows.
- **RLS missing on the categories table leaks cross-user data** — AI tools reliably add RLS to the transactions table but frequently forget the categories table. Without RLS on categories, every user's category query returns all users' categories — other users' custom category names and budget limits are visible to everyone. For a financial app this is a trust-destroying data leak. Fix: Immediately after generating the schema, verify in the Supabase Dashboard under Authentication → Policies that a policy exists for the categories table restricting ALL operations to auth.uid() = user_id. Run the RLS policy SQL from this page's data model explicitly in your prompt — do not leave it to the AI to remember.
- **Savings goal current_amount drifts from actual transaction data** — AI tools often implement savings goal progress by storing current_amount as a manually-updated numeric field. When users add transactions that contribute to a savings category, current_amount is not automatically updated — it drifts from the real transaction total, showing incorrect progress bars. Fix: Compute savings goal progress from a VIEW or aggregate query: SUM(transactions.amount) WHERE category_id = savings_category_id AND user_id = auth.uid() AND transaction_date in the relevant period. Display this computed total in the progress bar, not the stored current_amount. Alternatively, add a Postgres trigger on transactions INSERT that updates current_amount automatically.

## Best practices

- Apply Supabase RLS to every table in the budget planner — categories, transactions, savings_goals — not just the most obvious one. A missing policy on categories is a user data leak
- Always filter transaction queries by both user_id and transaction_date in SQL; never fetch all transactions and filter client-side — the query will time out once the table reaches thousands of rows
- Generate an import_hash (MD5 of date+amount+description) for every CSV row and use INSERT ON CONFLICT DO NOTHING — re-importing the same bank statement is the most common user error and will corrupt all budget totals without deduplication
- Mark all Recharts chart components as Client Components with 'use client' in Next.js — they use browser globals and will break SSR builds if placed in Server Components
- Compute savings goal progress from actual transaction data using a view or aggregate query rather than a manually-updated current_amount field — the stored field will drift from reality within weeks
- Add a composite index on (user_id, transaction_date desc) immediately — the budget dashboard queries this column on every page load and it becomes a performance bottleneck at a few thousand rows
- Show budget limit warnings at 80% of the category limit, not 100% — users need a warning while they can still act, not after they have already overspent
- Use URL params or React state (not localStorage) for the active month filter in the dashboard — Next.js SSR will crash if localStorage is read at module level

## Frequently asked questions

### Do I need a database for a budget planner?

Yes, if users need to persist their transactions across sessions or devices. A pure client-side calculator with localStorage works for a demo but loses all data when the browser cache is cleared. Supabase free tier covers the transaction, category, and savings goal tables for a personal budget planner at zero cost until you exceed 500MB of data or need concurrent connections at scale.

### How do I import bank statements?

Use Papa Parse to parse the uploaded CSV file client-side — it runs in the browser with zero server cost. Show a column mapping UI where users assign their bank's column headers (often 'Date', 'Amount', 'Description' but varying by bank) to the app's schema. Generate a hash of each row for duplicate detection, then bulk-insert via a Supabase Edge Function using INSERT ON CONFLICT DO NOTHING. Most banks export transactions as CSV from their web portal under 'Statements' or 'Export'.

### Can multiple family members share one budget?

Not with the standard RLS model (user_id = auth.uid()) — it isolates each user's data to themselves. A shared household budget requires a budget_group_id that both users belong to, with RLS policies checking group membership instead of direct user_id equality. This is a meaningful architecture change. For a household MVP, the simplest approach is sharing a single login between family members.

### How do I handle recurring expenses?

Store a is_recurring flag and recurring_frequency on the transactions table. Each month, a pg_cron job (Supabase Pro) or Edge Function creates the current-month transactions from recurring templates. Alternatively, auto-create recurring transactions at dashboard load if they do not yet exist for the current month. Do not pre-populate all future recurring transactions — that fills the table with future-dated rows that are wrong if the amount or category changes.

### What's the best chart type for budget visualization?

Three charts serve different purposes: a BarChart (grouped or stacked) for income vs expenses month-over-month comparison, a PieChart or RadialChart for current-month spending by category, and a AreaChart for cumulative savings balance over time. All three are in Recharts and can be generated from a single Supabase query with GROUP BY month and category. Use the BarChart as the primary hero chart — it is the most scannable for trend detection.

### Can I export my budget data to Excel?

Yes. The xlsx or SheetJS library generates an Excel file client-side from your transaction array with no server cost. Add an export button that queries the current month's transactions from Supabase, formats them into an array, and triggers a browser download. SheetJS supports column formatting, number styling, and multi-sheet workbooks — the whole implementation takes about 30 minutes.

### How do I add currency formatting for my country?

Use the JavaScript Intl.NumberFormat API: new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(value) for Germany, or swap locale and currency code for any supported country. Store amounts as plain numerics in the database — never store formatted strings like '$1,234.56'. Format at display time using the user's locale detected from navigator.language.

### Can I set alerts when approaching a budget limit?

Yes. Query current spending per category against budget_limit for the current month. Show a warning badge in the category list when spending exceeds 80% of the limit. For proactive alerts, send a Resend email when the 80% threshold is crossed — trigger this check in the Edge Function that handles transaction inserts: after every new transaction, recompute the category total and send an alert email if it crosses 80% for the first time that month.

---

Source: https://www.rapidevelopers.com/app-features/budget-planner
© RapidDev — https://www.rapidevelopers.com/app-features/budget-planner
