Skip to main content
RapidDev - Software Development Agency

How to Build a Client Invoicing Tool with Lovable

Build a complete client invoicing tool in Lovable with dynamic line items, a live invoice preview, PDF generation via Edge Function, and automated overdue detection. Supabase handles clients, invoices, and line items with status tracking from draft through paid — giving you a professional billing system without any manual backend setup.

What you'll build

  • Client database with contact details and billing information
  • Invoice creation form with dynamic line item rows (add, remove, reorder)
  • Live invoice preview card that mirrors a real printable invoice
  • DataTable for invoice list with status badges and overdue indicators
  • PDF generation via Supabase Edge Function using a headless approach
  • Automated overdue detection that updates status when due date passes
  • Email sending for invoice delivery to clients
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate16 min read2-3 hoursLovable Pro or higherApril 2026RapidDev Engineering Team
TL;DR

Build a complete client invoicing tool in Lovable with dynamic line items, a live invoice preview, PDF generation via Edge Function, and automated overdue detection. Supabase handles clients, invoices, and line items with status tracking from draft through paid — giving you a professional billing system without any manual backend setup.

What you're building

A client invoicing tool automates the most time-consuming part of freelance or agency billing: creating, formatting, sending, and tracking invoices. Instead of building invoices in Google Docs or Word, your team creates them in a form that auto-calculates totals, generates a professional PDF, and emails it to the client.

The most technically interesting part of this build is the dynamic line items form. Using React Hook Form's useFieldArray, you can add and remove line item rows (description, quantity, unit price) while the subtotal, tax, and total update in real time in a preview panel next to the form. This feels polished and saves the mental math of manual totaling.

PDF generation runs in a Supabase Edge Function on Deno. The function receives the invoice data, builds an HTML string matching the invoice design, and converts it to PDF. The PDF URL is stored in the invoices table and made available for download.

Final result

A professional invoicing system where you can create formatted invoices with live preview, generate PDFs, email clients, and track payment status automatically.

Tech stack

LovableFrontend builder and code generation
Supabase PostgreSQLDatabase for clients, invoices, and line items
Supabase Edge FunctionsPDF generation and invoice email delivery
shadcn/uiForm, DataTable, Card, Badge, Dialog components
React Hook Form + ZodInvoice form validation with dynamic field arrays
ResendTransactional email for invoice delivery

Prerequisites

  • A Lovable account (Pro plan required for Edge Functions)
  • A Supabase project connected to your Lovable project via Cloud tab
  • A Resend account for email delivery (free tier: 100 emails/day, resend.com)
  • Your business name, address, and logo URL ready for the invoice header
  • Optional: a list of your existing clients ready to import as seed data

Build steps

1

Create the database schema for clients, invoices, and line items

The schema has three core tables: clients, invoices, and line_items. The invoice status lifecycle goes from draft to sent, then either paid, overdue, or cancelled. A calculated total column is maintained by a trigger so you never manually sum line items in the app.

supabase-schema.sql
1-- Run in Supabase SQL Editor
2
3CREATE TYPE invoice_status AS ENUM ('draft', 'sent', 'paid', 'overdue', 'cancelled');
4
5CREATE TABLE clients (
6 id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
7 user_id UUID REFERENCES auth.users NOT NULL,
8 company_name TEXT NOT NULL,
9 contact_name TEXT,
10 email TEXT NOT NULL,
11 phone TEXT,
12 address TEXT,
13 city TEXT,
14 country TEXT,
15 tax_number TEXT,
16 notes TEXT,
17 created_at TIMESTAMPTZ DEFAULT now()
18);
19
20CREATE TABLE invoices (
21 id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
22 user_id UUID REFERENCES auth.users NOT NULL,
23 client_id UUID REFERENCES clients(id) NOT NULL,
24 invoice_number TEXT NOT NULL,
25 status invoice_status DEFAULT 'draft',
26 issue_date DATE NOT NULL DEFAULT CURRENT_DATE,
27 due_date DATE NOT NULL,
28 subtotal DECIMAL(12,2) NOT NULL DEFAULT 0,
29 tax_rate DECIMAL(5,2) NOT NULL DEFAULT 0,
30 tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
31 total DECIMAL(12,2) NOT NULL DEFAULT 0,
32 notes TEXT,
33 pdf_storage_path TEXT,
34 sent_at TIMESTAMPTZ,
35 paid_at TIMESTAMPTZ,
36 created_at TIMESTAMPTZ DEFAULT now(),
37 updated_at TIMESTAMPTZ DEFAULT now()
38);
39
40CREATE TABLE line_items (
41 id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
42 invoice_id UUID REFERENCES invoices(id) ON DELETE CASCADE NOT NULL,
43 description TEXT NOT NULL,
44 quantity DECIMAL(10,2) NOT NULL DEFAULT 1,
45 unit_price DECIMAL(12,2) NOT NULL,
46 amount DECIMAL(12,2) GENERATED ALWAYS AS (quantity * unit_price) STORED,
47 position INTEGER NOT NULL DEFAULT 0
48);
49
50-- Trigger to update invoice totals when line_items change
51CREATE OR REPLACE FUNCTION update_invoice_totals()
52RETURNS TRIGGER LANGUAGE plpgsql AS $$
53DECLARE
54 v_subtotal DECIMAL(12,2);
55 v_tax_rate DECIMAL(5,2);
56BEGIN
57 SELECT COALESCE(SUM(quantity * unit_price), 0) INTO v_subtotal
58 FROM line_items WHERE invoice_id = COALESCE(NEW.invoice_id, OLD.invoice_id);
59
60 SELECT tax_rate INTO v_tax_rate FROM invoices
61 WHERE id = COALESCE(NEW.invoice_id, OLD.invoice_id);
62
63 UPDATE invoices SET
64 subtotal = v_subtotal,
65 tax_amount = ROUND(v_subtotal * v_tax_rate / 100, 2),
66 total = v_subtotal + ROUND(v_subtotal * v_tax_rate / 100, 2),
67 updated_at = now()
68 WHERE id = COALESCE(NEW.invoice_id, OLD.invoice_id);
69
70 RETURN NEW;
71END;
72$$;
73
74CREATE TRIGGER line_items_changed
75AFTER INSERT OR UPDATE OR DELETE ON line_items
76FOR EACH ROW EXECUTE FUNCTION update_invoice_totals();
77
78-- RLS
79ALTER TABLE clients ENABLE ROW LEVEL SECURITY;
80ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
81ALTER TABLE line_items ENABLE ROW LEVEL SECURITY;
82
83CREATE POLICY "Users manage own clients" ON clients FOR ALL USING (user_id = auth.uid());
84CREATE POLICY "Users manage own invoices" ON invoices FOR ALL USING (user_id = auth.uid());
85CREATE POLICY "Users manage own line items" ON line_items FOR ALL USING (
86 EXISTS (SELECT 1 FROM invoices WHERE id = invoice_id AND user_id = auth.uid())
87);

Pro tip: The GENERATED ALWAYS AS column on line_items.amount means the database always calculates quantity * unit_price — you can never accidentally store a wrong amount. The trigger then sums these up to keep invoices.total accurate without any application logic.

Expected result: Three tables created with RLS enabled. The trigger function appears in Supabase Dashboard under Database → Functions. Adding a test line item via SQL Editor and checking the invoices table shows the total updated automatically.

2

Build the invoice creation form with dynamic line items

The invoice creation form is the core of the tool. Use React Hook Form's useFieldArray for dynamic line item rows. As users type quantities and prices, the totals update in real time. The form and a live preview card sit side by side.

prompt.txt
1Build the invoice creation page at /invoices/new with a two-column layout:
2
3LEFT COLUMN (form, 55% width):
41. Invoice header fields:
5 - Client select (shadcn Select, fetches from clients table, shows company name + email)
6 - Invoice number (auto-generated: INV-{year}-{sequential number}, editable)
7 - Issue date (shadcn Calendar Popover, default today)
8 - Due date (shadcn Calendar Popover, default 30 days from today)
9 - Tax rate percentage (number input, default 0)
10
112. Line items section with useFieldArray:
12 - Each row: Description (text input, flex-grow), Quantity (number, 80px), Unit Price (number, 120px), Amount (calculated, 100px, read-only), Remove button (X)
13 - "Add Line Item" button adds a new row at the bottom
14 - Minimum 1 line item required (show error if empty on submit)
15 - Amount column = quantity * unit_price calculated in real time
16
173. Totals summary (right-aligned below line items):
18 - Subtotal: sum of all amounts
19 - Tax ({tax_rate}%): subtotal * tax_rate / 100
20 - Total: subtotal + tax (bold, larger text)
21
224. Notes textarea (optional)
235. Submit buttons: "Save as Draft" and "Save and Preview"
24
25Right Column (preview): see next step
26
27Use react-hook-form with zod schema. Validate: client required, at least 1 line item, quantity > 0, unit_price >= 0, due_date after issue_date.

Pro tip: For the auto-generated invoice number, query the count of existing invoices for the current user and format as INV-{currentYear}-{count + 1} padded to 4 digits (e.g., INV-2025-0042). Do this in a useEffect when the form loads.

Expected result: The invoice form renders with one default empty line item row. Adding a quantity and price immediately updates the totals. The Add Line Item button adds new rows. The Remove button on a row removes it and recalculates totals.

3

Build the live invoice preview card

The right column shows a live preview of the invoice as it's being filled in. This preview matches the final PDF layout: your business header, client details, line items table, and totals. It uses shadcn Card and updates reactively as form values change.

prompt.txt
1Build a live invoice preview component that displays in the right column of the invoice form:
2
3Preview layout (styled like a real invoice document, white background, subtle shadow):
41. Header row:
5 - Left: Your business name (bold, large) + address placeholder text
6 - Right: "INVOICE" label (large, primary color) + Invoice Number + Issue/Due dates
7
82. Bill To section:
9 - Client company name (populated from selected client)
10 - Contact name, email, address (populated from client record)
11
123. Line items table:
13 - Column headers: Description, Qty, Unit Price, Amount
14 - One row per line item from the form (live-updating as user types)
15 - Alternating row background: white and gray-50
16
174. Totals section (right-aligned):
18 - Subtotal row
19 - Tax row (hidden if tax_rate = 0)
20 - Bold total row with border-top
21
225. Notes section at bottom (if notes field has content)
236. Footer: payment terms placeholder + "Thank you for your business"
24
25Watch the form values using react-hook-form's watch() function and pass them as props to the preview component. The preview updates on every keystroke without any extra state management.

Expected result: As the user types in the form, the preview panel updates instantly. Selecting a client populates the Bill To section. Adding line items shows them in the preview table. The preview closely matches what the final PDF will look like.

4

Create the PDF generation Edge Function

When the user clicks Send Invoice or Download PDF, an Edge Function receives the invoice data, builds an HTML representation, converts it to PDF using a Deno-compatible library, saves it to Supabase Storage, and returns the public URL.

supabase/functions/generate-invoice-pdf/index.ts
1// supabase/functions/generate-invoice-pdf/index.ts
2import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
3import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
4
5const corsHeaders = {
6 'Access-Control-Allow-Origin': '*',
7 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
8};
9
10serve(async (req) => {
11 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });
12
13 try {
14 const supabase = createClient(
15 Deno.env.get('SUPABASE_URL')!,
16 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
17 );
18
19 const { invoice_id } = await req.json();
20
21 const { data: invoice } = await supabase
22 .from('invoices')
23 .select('*, clients(*), line_items(* order by position)')
24 .eq('id', invoice_id)
25 .single();
26
27 if (!invoice) throw new Error('Invoice not found');
28
29 const lineItemsHtml = invoice.line_items.map((item: Record<string, unknown>) => `
30 <tr style="border-bottom: 1px solid #eee;">
31 <td style="padding: 8px 4px;">${item.description}</td>
32 <td style="padding: 8px 4px; text-align: right;">${item.quantity}</td>
33 <td style="padding: 8px 4px; text-align: right;">$${Number(item.unit_price).toFixed(2)}</td>
34 <td style="padding: 8px 4px; text-align: right;">$${Number(item.amount).toFixed(2)}</td>
35 </tr>
36 `).join('');
37
38 const html = `<!DOCTYPE html><html><head><meta charset="UTF-8">
39 <style>body{font-family:Arial,sans-serif;color:#333;padding:40px;max-width:800px;margin:0 auto;}
40 .header{display:flex;justify-content:space-between;margin-bottom:32px;}
41 table{width:100%;border-collapse:collapse;}
42 th{text-align:left;padding:8px 4px;border-bottom:2px solid #333;font-size:12px;text-transform:uppercase;color:#666;}
43 .totals{margin-top:24px;text-align:right;} .total-row{font-weight:bold;font-size:18px;border-top:2px solid #333;padding-top:8px;}
44 </style></head><body>
45 <div class="header">
46 <div><h2>Your Business Name</h2></div>
47 <div style="text-align:right;"><h1 style="color:#2563eb;margin:0;">INVOICE</h1>
48 <p>Invoice #: ${invoice.invoice_number}</p>
49 <p>Date: ${invoice.issue_date}</p><p>Due: ${invoice.due_date}</p></div>
50 </div>
51 <div><strong>Bill To:</strong><br/>
52 ${invoice.clients.company_name}<br/>
53 ${invoice.clients.contact_name || ''}<br/>
54 ${invoice.clients.email}</div>
55 <table style="margin-top:32px;">
56 <thead><tr><th>Description</th><th style="text-align:right;">Qty</th><th style="text-align:right;">Unit Price</th><th style="text-align:right;">Amount</th></tr></thead>
57 <tbody>${lineItemsHtml}</tbody>
58 </table>
59 <div class="totals">
60 <p>Subtotal: $${Number(invoice.subtotal).toFixed(2)}</p>
61 ${invoice.tax_rate > 0 ? `<p>Tax (${invoice.tax_rate}%): $${Number(invoice.tax_amount).toFixed(2)}</p>` : ''}
62 <p class="total-row">Total: $${Number(invoice.total).toFixed(2)}</p>
63 </div>
64 ${invoice.notes ? `<p style="margin-top:32px;color:#666;">${invoice.notes}</p>` : ''}
65 </body></html>`;
66
67 const pdfResponse = await fetch('https://api.pdfmonkey.io/api/v1/documents', {
68 method: 'POST',
69 headers: {
70 'Authorization': `Bearer ${Deno.env.get('PDFMONKEY_API_KEY')}`,
71 'Content-Type': 'application/json'
72 },
73 body: JSON.stringify({ document: { document_template_id: 'html', payload: { html } } })
74 });
75
76 if (!pdfResponse.ok) {
77 const htmlBlob = new Blob([html], { type: 'text/html' });
78 const storagePath = `invoices/${invoice_id}/invoice-${invoice.invoice_number}.html`;
79 await supabase.storage.from('invoice-exports').upload(storagePath, htmlBlob, { upsert: true });
80 const { data: { publicUrl } } = supabase.storage.from('invoice-exports').getPublicUrl(storagePath);
81 await supabase.from('invoices').update({ pdf_storage_path: storagePath }).eq('id', invoice_id);
82 return new Response(JSON.stringify({ url: publicUrl, format: 'html' }), {
83 headers: { ...corsHeaders, 'Content-Type': 'application/json' }
84 });
85 }
86
87 return new Response(JSON.stringify({ success: true }), {
88 headers: { ...corsHeaders, 'Content-Type': 'application/json' }
89 });
90 } catch (err) {
91 return new Response(JSON.stringify({ error: err.message }), {
92 status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }
93 });
94 }
95});

Pro tip: For a free PDF solution without a third-party API, the Edge Function can return HTML and you can use the browser's window.print() with print-specific CSS on the invoice preview component. This works surprisingly well for simple invoice designs.

Expected result: Clicking 'Download PDF' calls the Edge Function, which generates the PDF content and returns a URL. The browser opens the download. The invoice record in Supabase shows the pdf_storage_path populated.

5

Build the invoice DataTable and add overdue detection

The main invoices list shows all invoices with status badges. A background check on page load marks overdue invoices (due_date has passed, status is 'sent'). Add a send invoice flow that emails the client and updates the status to 'sent'.

prompt.txt
1Build the invoices list page at /invoices:
2
31. Header with "New Invoice" button and stats row (4 cards):
4 - Total Invoiced (sum of all invoice totals)
5 - Paid (sum of paid invoices this month)
6 - Outstanding (sum of sent invoices not yet paid)
7 - Overdue (count of overdue invoices, red badge if > 0)
8
92. shadcn DataTable with columns:
10 - Invoice Number (monospace, clickable)
11 - Client (company name)
12 - Issue Date
13 - Due Date (red if overdue)
14 - Status (shadcn Badge: draft=gray, sent=blue, paid=green, overdue=red, cancelled=gray)
15 - Total Amount (right-aligned)
16 - Actions (View, Send, Download PDF, Mark as Paid, Delete)
17
183. Overdue detection: on page load, run this query in Supabase:
19 UPDATE invoices SET status = 'overdue' WHERE status = 'sent' AND due_date < CURRENT_DATE AND user_id = {currentUserId}
20 Do this as an RPC call or direct .update() with a filter.
21
224. Send Invoice flow (clicking Send action):
23 - Opens a Dialog confirming: "Send invoice {number} to {client email}?"
24 - On confirm: call a Supabase Edge Function 'send-invoice-email' passing invoice_id
25 - Edge Function sends email via Resend with HTML invoice content
26 - Update invoice status to 'sent', set sent_at = now()
27 - Show success toast: "Invoice sent to {email}"
28
295. Mark as Paid: opens a Dialog with paid date selector. Updates status='paid', paid_at=selected_date.
30
31Add a filter row above the table: Status dropdown, Client select, Date range picker.

Expected result: The invoices list shows all invoices with correct status badges. Any sent invoices with past due dates immediately show 'overdue' status on page load. The Send button sends an email and updates the status to 'sent'.

Complete code

src/components/invoices/InvoiceTotals.tsx
1import { Separator } from '@/components/ui/separator';
2
3interface LineItem {
4 quantity: number;
5 unit_price: number;
6}
7
8interface InvoiceTotalsProps {
9 lineItems: LineItem[];
10 taxRate: number;
11 className?: string;
12}
13
14function formatCurrency(amount: number): string {
15 return new Intl.NumberFormat('en-US', {
16 style: 'currency',
17 currency: 'USD',
18 minimumFractionDigits: 2,
19 }).format(amount);
20}
21
22export function InvoiceTotals({ lineItems, taxRate, className = '' }: InvoiceTotalsProps) {
23 const subtotal = lineItems.reduce((sum, item) => {
24 const qty = Number(item.quantity) || 0;
25 const price = Number(item.unit_price) || 0;
26 return sum + qty * price;
27 }, 0);
28
29 const taxAmount = subtotal * (Number(taxRate) || 0) / 100;
30 const total = subtotal + taxAmount;
31
32 return (
33 <div className={`space-y-2 text-sm ${className}`}>
34 <div className="flex justify-between">
35 <span className="text-muted-foreground">Subtotal</span>
36 <span>{formatCurrency(subtotal)}</span>
37 </div>
38 {taxRate > 0 && (
39 <div className="flex justify-between">
40 <span className="text-muted-foreground">Tax ({taxRate}%)</span>
41 <span>{formatCurrency(taxAmount)}</span>
42 </div>
43 )}
44 <Separator />
45 <div className="flex justify-between font-semibold text-base">
46 <span>Total</span>
47 <span>{formatCurrency(total)}</span>
48 </div>
49 </div>
50 );
51}

Customization ideas

Add recurring invoice automation

Add a recurring_schedule column to invoices (none, monthly, quarterly, annually). Create a Supabase scheduled function with pg_cron that runs daily, finds invoices due for recurrence, and auto-creates new draft invoices for the next period.

Integrate Stripe for online payment

Add a 'Pay Online' button to the emailed invoice that links to a Stripe Checkout session. The Edge Function creates the session with the invoice amount. On successful payment, a Stripe webhook updates the invoice status to 'paid'.

Add a client-facing invoice view page

Create a public URL (/invoices/public/{token}) where clients can view their invoice in the browser without logging in. Use a one-time token stored in the invoices table for security. Include a Print button for the client.

Build invoice templates

Add an invoice_templates table with pre-defined line item sets for common service packages. Let users create new invoices from a template that pre-populates the line items, saving time on repeat billing.

Add multi-currency support

Add a currency column to both clients and invoices (default USD). Show the currency symbol next to amounts in the form and preview. Store exchange rates via an Edge Function calling a free currency API.

Build an accounts receivable report

Add a /reports/accounts-receivable page with a Recharts bar chart showing monthly invoice totals vs collected amounts, an aging report (0-30, 31-60, 61-90, 90+ days overdue), and a client payment history summary.

Common pitfalls

Pitfall: Calculating totals in JavaScript instead of relying on the database trigger

How to avoid: After submitting line items, refetch the invoice from Supabase to get the trigger-updated totals. Use the database as the single source of truth for financial amounts.

Pitfall: Forgetting to cascade delete line_items when deleting an invoice

How to avoid: The schema above includes ON DELETE CASCADE on line_items.invoice_id. If you're adding to an existing table, run: ALTER TABLE line_items ADD CONSTRAINT fk_invoice FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE;

Pitfall: Generating sequential invoice numbers using JavaScript count

How to avoid: Use a PostgreSQL sequence: CREATE SEQUENCE invoice_number_seq; then use nextval('invoice_number_seq') in the insert. Or use a trigger that generates the number on insert.

Pitfall: Storing the sent invoice PDF in a public Storage bucket

How to avoid: Use a private Storage bucket for invoice PDFs. Generate signed URLs that expire in 1 hour when users click Download. Never store the invoice in a public bucket.

Best practices

  • Use a database trigger to maintain invoice totals instead of calculating in the application layer
  • Generate sequential invoice numbers using a PostgreSQL sequence for guaranteed uniqueness
  • Store PDF files in a private Supabase Storage bucket and generate short-lived signed URLs for downloads
  • Mark overdue invoices on page load rather than in a background job — it's simpler and accurate enough for most use cases
  • Use DECIMAL(12,2) for all monetary values — never use FLOAT for money
  • Display all amounts using Intl.NumberFormat for correct currency formatting based on locale
  • Add a notes field to invoices for payment terms, bank details, or custom messages
  • Send invoice emails from a domain you control (not a generic Resend email) to avoid spam filters

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building an invoicing system in React with react-hook-form. I have a line items form using useFieldArray where each row has description, quantity, and unit_price fields. I need to calculate and display the subtotal, tax amount (based on a tax rate percentage input), and total in real time as the user types. Write a React component that takes the form's watch() output and renders these calculated totals, handling NaN and empty string inputs gracefully.

Lovable Prompt

Add a print-friendly CSS layout to the invoice preview component so clicking 'Print Invoice' triggers the browser print dialog and shows only the invoice document without the form sidebar, navigation, or action buttons. Use a @media print CSS class that hides all non-invoice elements.

Build Prompt

In Lovable, create a Supabase Edge Function called send-invoice-email that accepts an invoice_id, fetches the invoice with client details and line items from Supabase using the service role key, builds an HTML email containing the invoice details, and sends it to the client's email address using the Resend API. The Resend API key should be read from Deno.env.get('RESEND_API_KEY'). After sending, update the invoice status to 'sent' and set sent_at to the current timestamp.

Frequently asked questions

How do I make invoice numbers sequential and unique?

Use a PostgreSQL sequence: CREATE SEQUENCE invoice_seq; then reference it in your insert with to_char(nextval('invoice_seq'), 'FM0000') to get zero-padded numbers like 0001. Add a trigger that sets invoice_number = 'INV-' || to_char(now(), 'YYYY') || '-' || to_char(nextval('invoice_seq'), 'FM0000') automatically on INSERT.

Can clients pay invoices online through this tool?

Not by default, but you can extend it. Add Stripe Checkout integration: an Edge Function creates a Checkout session for the invoice amount, and the client is redirected to Stripe's payment page. After payment, a Stripe webhook updates the invoice status to 'paid'. This extension typically takes 1-2 hours to add.

How do I deploy this so clients can access their invoice emails?

Publish the Lovable project with the Publish button. For professional client communication, connect a custom domain in Lovable's publish settings. Update the email template in the Edge Function to link to your custom domain for any client-facing invoice view pages.

What PDF library works in Supabase Edge Functions?

Supabase Edge Functions run on Deno, which has limited library support. The most reliable approach for basic invoices is generating clean HTML and returning it as a downloadable file — modern browsers render it perfectly for printing. For true PDF generation, use a third-party API like PDFMonkey, PDFShift, or DocRaptor that accepts HTML and returns a PDF file.

How do I handle multiple currencies for international clients?

Add a currency TEXT column (e.g., 'USD', 'EUR', 'GBP') to both the clients and invoices tables. In the form, add a currency selector. Use the Intl.NumberFormat API in JavaScript to format amounts with the correct currency symbol. Store amounts in the invoice's chosen currency — don't convert them.

How does the overdue detection work?

On the invoices list page load, a Supabase update query runs: UPDATE invoices SET status = 'overdue' WHERE status = 'sent' AND due_date < today AND user_id = current_user. This is a simple approach that catches overdue invoices whenever you visit the page. For more proactive detection (running at midnight daily), you'd use a Supabase scheduled Edge Function or pg_cron.

Can I add my company logo to the invoice PDF?

Yes — store your logo in Supabase Storage (public bucket), get the public URL, and embed it as an img tag in the HTML template inside the Edge Function. For the live preview in Lovable, show the same img tag using the same public URL.

Can RapidDev help add Stripe payment links to the invoice emails?

Yes — connecting Stripe Checkout to invoice emails is a common extension to Lovable-built invoicing tools. RapidDev can help design the webhook flow to automatically mark invoices as paid when Stripe confirms payment.

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.