Step 1 — Create a free Supabase project
Go to supabase.com → New Project. Choose a name, set a database password, pick the region closest to you. Takes ~2 minutes.
Step 2 — Run this SQL to create the tables
In your Supabase project, go to SQL Editor → New Query, paste and run:
-- Transactions table
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
transfer_keywords TEXT[],
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE transactions (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
date TEXT, description TEXT, amount NUMERIC,
account_id INTEGER REFERENCES accounts(id),
category_id INTEGER, receipt_path TEXT,
txn_type TEXT DEFAULT 'expense',
quick_entry_id UUID, reconciled BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Quick entries (logged from phone)
CREATE TABLE quick_entries (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
amount NUMERIC NOT NULL,
note TEXT,
category_id INTEGER,
transaction_id UUID REFERENCES transactions(id),
matched BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Categories table
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL, color TEXT,
keywords TEXT[], budget NUMERIC,
parent_id INTEGER REFERENCES categories(id),
created_at TIMESTAMPTZ DEFAULT now()
);
-- Enable public access (RLS off for personal use)
ALTER TABLE accounts DISABLE ROW LEVEL SECURITY;
-- Migration: add balance tracking columns
-- ALTER TABLE accounts ADD COLUMN IF NOT EXISTS current_balance NUMERIC;
-- ALTER TABLE accounts ADD COLUMN IF NOT EXISTS balance_date TEXT;
ALTER TABLE transactions DISABLE ROW LEVEL SECURITY;
-- Migration: add card member field for authorized user tracking
-- ALTER TABLE transactions ADD COLUMN IF NOT EXISTS card_member TEXT;
-- Migration: split transaction support
-- ALTER TABLE transactions ADD COLUMN IF NOT EXISTS is_split_parent BOOLEAN DEFAULT false;
-- ALTER TABLE transactions ADD COLUMN IF NOT EXISTS split_parent_id UUID REFERENCES transactions(id);
ALTER TABLE quick_entries DISABLE ROW LEVEL SECURITY;
ALTER TABLE categories DISABLE ROW LEVEL SECURITY;
-- Income forecasts (BvA / expected income tracking)
CREATE TABLE IF NOT EXISTS income_forecasts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
description TEXT NOT NULL,
expected_amount NUMERIC NOT NULL,
expected_date TEXT,
income_type TEXT DEFAULT 'other',
business_id INTEGER REFERENCES businesses(id),
notes TEXT,
match_keyword TEXT,
status TEXT DEFAULT 'pending',
actual_amount NUMERIC,
matched_transaction_id UUID REFERENCES transactions(id),
reconciled_at TIMESTAMPTZ,
reconcile_notes TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
ALTER TABLE income_forecasts DISABLE ROW LEVEL SECURITY;
-- Storage bucket for PDF receipts
INSERT INTO storage.buckets (id, name, public)
VALUES ('receipts', 'receipts', false)
ON CONFLICT DO NOTHING;
Step 3 — Get your API keys
In your project: Settings → API. Copy the Project URL and the anon public key. Paste them into the Ledger setup screen.
Step 4 — Host Ledger (for phone access)
Upload this HTML file to GitHub Pages (free) or Netlify (free, drag-and-drop). Then bookmark the URL on your phone and tap "Add to Home Screen" in your browser.
Storage limits (free tier)
500 MB database · 1 GB file storage (PDF receipts) · More than enough for personal use.