Skip to main content
RapidDev - Software Development Agency

How to Build a Budgeting Tool with Lovable

Build an envelope budgeting app in Lovable where every dollar gets assigned to a named envelope before you spend it. Features a zero-sum constraint that warns when allocations exceed income, auto-updating spent amounts via a Supabase trigger, and visual progress bars that turn red as envelopes fill up.

What you'll build

  • Budget periods (monthly or custom date range) that reset each cycle
  • Envelope creation with a target allocation amount per period
  • Zero-sum constraint that shows unallocated income and warns when over-allocated
  • Transaction entry linked to a specific envelope
  • Progress bars on each envelope Card that turn green to yellow to red as spending increases
  • Auto-updating spent_amount on envelopes via a Supabase database trigger
  • Summary Card showing total income, total allocated, and unallocated remainder
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner13 min read1.5–2 hoursLovable Free or higherApril 2026RapidDev Engineering Team
TL;DR

Build an envelope budgeting app in Lovable where every dollar gets assigned to a named envelope before you spend it. Features a zero-sum constraint that warns when allocations exceed income, auto-updating spent amounts via a Supabase trigger, and visual progress bars that turn red as envelopes fill up.

What you're building

Envelope budgeting is a cash-flow management method where you divide your income into named envelopes before spending. You allocate $500 to Groceries, $200 to Entertainment, and $1,200 to Rent at the start of each month. As you spend, money comes out of the specific envelope. When an envelope is empty, spending in that category stops.

The zero-sum constraint means the sum of all envelope allocations must equal your period income. The app tracks this in real time: if you have $3,000 in income and allocate $2,800 across envelopes, the summary Card shows $200 unallocated. If allocations exceed $3,000, a warning Banner appears.

Spent amounts update automatically. When a transaction is inserted with an envelope_id, a Supabase AFTER INSERT trigger on the transactions table runs a function that adds the transaction amount to envelopes.spent_amount. Similarly, deleting a transaction subtracts from spent_amount. This keeps spent amounts accurate without any frontend logic.

Final result

A clean envelope budgeting app where users plan their spending at the start of each period and track it in real time with auto-updating progress bars.

Tech stack

LovableFull-stack app generation
SupabaseDatabase, Auth, triggers
shadcn/uiCards, Progress, Badge, Dialog
react-hook-form + zodTransaction and envelope forms
date-fnsPeriod date calculations

Prerequisites

  • Lovable account (Free tier is sufficient for this build)
  • Supabase project with URL and anon key saved to Cloud tab → Secrets
  • Your monthly income amount to set up the first budget period

Build steps

1

Create the budgeting schema with trigger for spent_amount

Prompt Lovable to set up the database tables and the trigger that auto-updates spent_amount. The trigger is the key piece that keeps envelope balances accurate without frontend logic.

prompt.txt
1Build an envelope budgeting app. Create these Supabase tables:
2
3- budget_periods: id, user_id, name (e.g. 'June 2024'), start_date (date), end_date (date), income_amount (numeric), is_active (bool default false), created_at
4- envelopes: id, user_id, period_id (FK budget_periods), name, allocated_amount (numeric), spent_amount (numeric default 0), color (hex), icon (emoji), sort_order (int), created_at
5- transactions: id, user_id, envelope_id (FK envelopes), amount (numeric, always positive), description, transaction_date (date), created_at
6
7Create a PostgreSQL trigger function update_envelope_spent() that:
8- On transactions INSERT: UPDATE envelopes SET spent_amount = spent_amount + NEW.amount WHERE id = NEW.envelope_id
9- On transactions DELETE: UPDATE envelopes SET spent_amount = spent_amount - OLD.amount WHERE id = OLD.envelope_id
10- Attach as AFTER INSERT OR DELETE trigger on transactions table
11
12RLS: all tables require user_id = auth.uid().
13
14Create a computed column view envelope_summary: all envelope columns plus remaining_amount (allocated_amount - spent_amount) and percent_used ((spent_amount / NULLIF(allocated_amount, 0)) * 100).

Pro tip: Ask Lovable to add a 'Create Period from Template' feature: copying envelopes from the previous period with the same names, colors, and allocated amounts. This saves users from re-creating envelopes every month.

Expected result: All three tables are created with the trigger function attached. Inserting a transaction in the Supabase SQL editor updates the envelope's spent_amount automatically.

2

Build the envelope Cards with progress bars

Create the main budget view showing envelope Cards with color-coded progress bars. The Cards grid is the primary interface users will use daily.

prompt.txt
1Build the main budget view at src/pages/Budget.tsx:
2
31. Period selector at top: Select dropdown showing all budget_periods for the user, 'New Period' Button
42. Zero-sum summary Banner below period selector:
5 - Show: Period Income: $X | Allocated: $Y | Unallocated: $Z
6 - If sum of allocated_amount > income_amount: show red Banner 'Over-allocated by $Z. Reduce envelope amounts to match your income.'
7 - If unallocated > 0: show blue Banner 'You have $Z to allocate.'
8 - If exactly zero: show green Banner 'Your budget is balanced.'
93. Envelope Cards grid (2-3 columns):
10 - Each Card: envelope icon + name, allocated amount, spent amount, remaining amount
11 - shadcn/ui Progress component: value = percent_used from envelope_summary view
12 - Progress color: green if percent_used < 75, yellow if 75-99, red if >= 100
13 - 'Add Transaction' Button on each Card
14 - Overflow indicator: 'Over by $X' text in red if spent > allocated
15 - Card drag handle for reordering (updates sort_order)
164. 'Add Envelope' Button (floating, bottom right) opens a Dialog with: name Input, icon Emoji picker (simple Select of common emojis), color Select, allocated amount Input

Expected result: Envelope Cards show with progress bars. Adding a test transaction via the Supabase dashboard updates the progress bar instantly on the next page load.

3

Build the transaction entry and history

Create the transaction entry dialog for each envelope and a transaction history page. Transactions are the daily input that drives the progress bars.

src/components/budgeting/AddTransactionDialog.tsx
1// src/components/budgeting/AddTransactionDialog.tsx
2import { useState } from 'react'
3import { useForm } from 'react-hook-form'
4import { zodResolver } from '@hookform/resolvers/zod'
5import { z } from 'zod'
6import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
7import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
8import { Input } from '@/components/ui/input'
9import { Button } from '@/components/ui/button'
10import { supabase } from '@/integrations/supabase/client'
11import { useQueryClient } from '@tanstack/react-query'
12
13const schema = z.object({
14 amount: z.coerce.number().positive('Amount must be greater than 0'),
15 description: z.string().min(1, 'Description is required'),
16 transaction_date: z.string().min(1, 'Date is required'),
17})
18
19type FormValues = z.infer<typeof schema>
20
21interface Props {
22 envelopeId: string
23 envelopeName: string
24 userId: string
25 open: boolean
26 onOpenChange: (open: boolean) => void
27}
28
29export function AddTransactionDialog({ envelopeId, envelopeName, userId, open, onOpenChange }: Props) {
30 const queryClient = useQueryClient()
31 const [isSubmitting, setIsSubmitting] = useState(false)
32
33 const form = useForm<FormValues>({
34 resolver: zodResolver(schema),
35 defaultValues: {
36 amount: undefined,
37 description: '',
38 transaction_date: new Date().toISOString().split('T')[0],
39 },
40 })
41
42 async function onSubmit(values: FormValues) {
43 setIsSubmitting(true)
44 const { error } = await supabase.from('transactions').insert({
45 user_id: userId,
46 envelope_id: envelopeId,
47 amount: values.amount,
48 description: values.description,
49 transaction_date: values.transaction_date,
50 })
51 setIsSubmitting(false)
52 if (!error) {
53 queryClient.invalidateQueries({ queryKey: ['envelopes'] })
54 form.reset()
55 onOpenChange(false)
56 }
57 }
58
59 return (
60 <Dialog open={open} onOpenChange={onOpenChange}>
61 <DialogContent className="sm:max-w-md">
62 <DialogHeader>
63 <DialogTitle>Add to {envelopeName}</DialogTitle>
64 </DialogHeader>
65 <Form {...form}>
66 <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
67 <FormField control={form.control} name="amount" render={({ field }) => (
68 <FormItem>
69 <FormLabel>Amount</FormLabel>
70 <FormControl><Input type="number" step="0.01" placeholder="0.00" {...field} /></FormControl>
71 <FormMessage />
72 </FormItem>
73 )} />
74 <FormField control={form.control} name="description" render={({ field }) => (
75 <FormItem>
76 <FormLabel>Description</FormLabel>
77 <FormControl><Input placeholder="Coffee, groceries..." {...field} /></FormControl>
78 <FormMessage />
79 </FormItem>
80 )} />
81 <FormField control={form.control} name="transaction_date" render={({ field }) => (
82 <FormItem>
83 <FormLabel>Date</FormLabel>
84 <FormControl><Input type="date" {...field} /></FormControl>
85 <FormMessage />
86 </FormItem>
87 )} />
88 <Button type="submit" className="w-full" disabled={isSubmitting}>
89 {isSubmitting ? 'Adding...' : 'Add Transaction'}
90 </Button>
91 </form>
92 </Form>
93 </DialogContent>
94 </Dialog>
95 )
96}

Expected result: The Add Transaction dialog opens from each envelope Card. Submitting the form inserts a transaction and the envelope's progress bar updates on the next render.

4

Add budget period management

Build the period creation flow and the ability to copy envelopes from a previous period. This is the key workflow users follow at the start of each month.

prompt.txt
1Build budget period management at src/pages/Periods.tsx:
2
31. Periods list: show all budget_periods for the user as Cards with: period name, date range, income amount, total allocated, total spent, is_active Badge
42. 'New Period' Dialog:
5 - Period name Input (default: 'Month Year' from current date)
6 - Start date and end date DatePickers (default: first and last day of next month)
7 - Income amount Input
8 - 'Copy envelopes from' Select: show previous periods. If selected, will copy envelope names, colors, icons, allocated amounts (not spent amounts) from that period
9 - On submit: create budget_period, optionally copy envelopes with spent_amount = 0
103. 'Set Active' Button on each period Card: sets is_active = true for this period and false for all others. The main Budget page shows the active period by default.
114. Period detail view: clicking a period Card shows a read-only summary of that period's envelopes and total spending (for reviewing past periods)
125. Delete period Button: only allowed if period has no transactions. Show a Tooltip explaining this restriction.

Pro tip: Ask Lovable to auto-create the next month's period at the end of each month using a Supabase Edge Function scheduled with pg_cron. Copy envelopes from the current active period automatically so the new period is ready on the first of the month.

Expected result: The Periods page lists all budget periods. Creating a new period with 'Copy from previous' duplicates envelope names and allocations with zero spending. Setting a period active switches the main budget view.

Complete code

src/components/budgeting/ZeroSumBanner.tsx
1import { Alert, AlertDescription } from '@/components/ui/alert'
2import { CheckCircle, AlertCircle, Info } from 'lucide-react'
3
4interface Props {
5 incomeAmount: number
6 totalAllocated: number
7}
8
9export function ZeroSumBanner({ incomeAmount, totalAllocated }: Props) {
10 const diff = incomeAmount - totalAllocated
11 const absFormatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(Math.abs(diff))
12
13 if (diff < 0) {
14 return (
15 <Alert variant="destructive" className="mb-4">
16 <AlertCircle className="h-4 w-4" />
17 <AlertDescription>
18 Over-allocated by {absFormatted}. Reduce envelope amounts so they total your period income of{' '}
19 {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(incomeAmount)}.
20 </AlertDescription>
21 </Alert>
22 )
23 }
24
25 if (diff > 0.01) {
26 return (
27 <Alert className="mb-4 border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950">
28 <Info className="h-4 w-4 text-blue-600" />
29 <AlertDescription className="text-blue-700 dark:text-blue-300">
30 {absFormatted} unallocated. Assign it to an envelope to complete your zero-sum budget.
31 </AlertDescription>
32 </Alert>
33 )
34 }
35
36 return (
37 <Alert className="mb-4 border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950">
38 <CheckCircle className="h-4 w-4 text-green-600" />
39 <AlertDescription className="text-green-700 dark:text-green-300">
40 Budget balanced. Every dollar is assigned.
41 </AlertDescription>
42 </Alert>
43 )
44}

Customization ideas

Transfer between envelopes

Add an envelope transfer feature: move money from one envelope to another mid-period. Store transfers in an envelope_transfers table (from_envelope_id, to_envelope_id, amount, note). The trigger on this table adjusts allocated_amount on both envelopes. Show a transfer history per envelope.

Rollover unused funds

When creating a new period, add an option to roll over unspent envelope amounts. If the Groceries envelope has $50 remaining at month end, the new period's Groceries allocation is automatically $550 (the usual $500 plus $50 rollover). Store rolled_over_amount per envelope for transparency.

Recurring income entries

Add an income_sources table for recurring income (salary, freelance, dividends). A pg_cron Edge Function adds income automatically on scheduled dates, which increases the period's income_amount. Show income entries alongside expense transactions in the history view.

Shared household budget

Add a households table and member linking. Both partners can add transactions to shared envelopes. RLS is updated to allow household members to read and write envelopes for their household. Show who added each transaction using the created_by column.

Common pitfalls

Pitfall: Storing spent_amount without a trigger

How to avoid: Use the AFTER INSERT OR DELETE trigger on the transactions table. The trigger runs on the database server side and is always accurate regardless of how transactions are created or deleted.

Pitfall: Allowing allocated_amount to be zero or negative

How to avoid: Add a check constraint: CHECK (allocated_amount > 0). Also add validation in the Zod schema for the envelope form: z.number().positive('Allocation must be greater than 0'). Prevent saving an envelope form if the value is zero.

Pitfall: Not handling the period end date when filtering transactions

How to avoid: Add a check constraint on transactions: validate that transaction_date is between the linked envelope's period start_date and end_date. Do this with a trigger that fetches the period dates via the envelope FK chain before allowing the insert.

Pitfall: Loading all transactions into the budget view

How to avoid: Use the envelope_summary view which has pre-aggregated spent_amount (maintained by the trigger). The budget Cards only need one query to the view — no need to load transactions for the summary page.

Pitfall: Deleting a period that has transactions

How to avoid: Add a guard: before allowing period deletion, count transactions for all envelopes in that period. If count > 0, show an error message explaining that transactions must be deleted first. Alternatively, only allow archiving periods (is_archived = true) rather than hard deletion.

Best practices

  • Use a trigger for spent_amount maintenance rather than calculating it in the frontend. Database triggers are transactional and accurate even with concurrent writes.
  • Display the zero-sum status prominently at the top of the budget page. Users should immediately see if they've over-allocated or have money left to assign. The ZeroSumBanner component in this guide is the right pattern.
  • Show both allocated_amount and remaining_amount on each envelope Card. Users need to see both the budget and how much is left — just showing percentage is not enough for decision-making.
  • Add a transaction date constraint that ties transactions to their period. This prevents the accidental assignment of future-dated expenses to a closed period.
  • Use soft deletes (is_deleted) for envelopes instead of hard deletes. A deleted envelope with transactions should become inactive and hidden from the active budget, but the transaction history should still show the envelope name.
  • Default the new period's income_amount to the previous period's income_amount. Most users have the same income each month, so pre-filling this field reduces friction.

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building an envelope budgeting app with PostgreSQL. I have an envelopes table with spent_amount and a transactions table. Help me write a PostgreSQL trigger function that updates spent_amount on the envelope when a transaction is inserted or deleted. The trigger should handle: INSERT (add amount), DELETE (subtract amount), and UPDATE (subtract old amount, add new amount). Show the complete CREATE OR REPLACE FUNCTION and CREATE TRIGGER statements.

Lovable Prompt

Add a spending trend chart to the budgeting app. Below the envelope Cards, add a Recharts BarChart showing daily spending totals for the current budget period. X-axis shows each day of the period, Y-axis shows total amount spent that day across all envelopes. Add a reference line showing the 'ideal daily spending rate' (period income / number of days). This helps users see if they're spending ahead of or behind their ideal pace.

Build Prompt

In Supabase, create a view called envelope_health that returns all envelopes for active periods with a computed health_status column: 'healthy' if percent_used < 75, 'warning' if 75-99, 'over_budget' if >= 100, 'empty' if spent_amount = 0. Also add days_remaining (end_date - today) and ideal_daily_remaining (remaining_amount / NULLIF(days_remaining, 0)). Use this view to power dashboard summary metrics.

Frequently asked questions

What is envelope budgeting and how is it different from a regular budget?

Envelope budgeting requires you to allocate all your income to specific categories (envelopes) before spending — every dollar has a job. A regular budget tracks spending after the fact against loose categories. Envelope budgeting enforces the zero-sum constraint: allocations must equal income, so you can't spend money you haven't planned for. It's more proactive and is especially effective for reducing impulse spending.

How does the zero-sum constraint work in this app?

The app calculates the sum of all envelope allocations and compares it to the period income_amount. If allocations exceed income, the ZeroSumBanner shows a red warning. If allocations are less than income, a blue banner shows the unallocated remainder. The constraint is visual — you're not blocked from saving if you're over-allocated, but the warning makes the imbalance obvious.

Can I move money between envelopes mid-month?

Not in the base build. Envelope transfers are the first customization idea in this guide. You'd add an envelope_transfers table and a trigger that adjusts both envelopes' allocated_amount. Ask Lovable to add this feature after the base build is complete.

Why does spent_amount update automatically without me clicking anything?

A Supabase database trigger fires after every transaction insert or delete. The trigger function adds or subtracts the transaction amount from the envelope's spent_amount column. This happens on the database server side, so it's immediate and accurate regardless of which device or user added the transaction.

Can I use this with my partner to manage household finances together?

The base build is single-user. The 'Shared household budget' customization idea in this guide adds a households table and shared envelope access. It requires updating the RLS policies to allow household member access. Prompt Lovable to add this feature as a follow-up step after the base build is working.

What happens to unused envelope money at the end of the period?

Nothing automatically — it stays in the envelope record from the previous period. When you create a new period, you start fresh with new envelopes. The 'Rollover unused funds' customization idea adds a feature to carry over unspent amounts to the next period's allocation. Without rollover, users manually decide how to re-allocate any unspent money in the new period.

Can I track multiple currencies in the same budget?

No, the base build uses a single currency throughout. All amounts in a budget period are implicitly the same currency. If you regularly spend in multiple currencies, add a currency column to transactions and a base_currency to budget_periods. Convert amounts to base currency using stored exchange rates. This is a significant extension that requires an Edge Function for exchange rate fetching.

Where can I get help building more advanced budgeting features?

RapidDev builds production Lovable apps including personal finance tools with bank integrations, multi-currency support, and advanced analytics. Reach out if you need features that go beyond this guide.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

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.