Overview
Money Tracker uses Supabase’s PostgreSQL database with migrations and seed files for schema management. The database includes:- User authentication (via Supabase Auth)
- Gmail OAuth connections
- Transactions and categories
- Notification system
- Subscription detection
- Vault for secure secrets
Database commands
All database commands run inside thesupabase-cli Docker container.
- Docker Compose
- npm/bun scripts
Migrations
Migrations are SQL files insupabase/migrations/ that define your database schema. They run in alphanumeric order.
Migration structure
Key migrations include:| Migration | Description |
|---|---|
20260117123342_create_multi_user_gmail_schema.sql | Creates gmail_connections table for OAuth tokens |
20260117141500_add_token_encryption_and_rls.sql | Adds encryption and Row Level Security |
20260117200633_refactor_emails_to_transactions.sql | Creates transactions table |
20260117210838_add_category_column.sql | Adds transaction categories |
20260117231531_add_recipient_email_to_transactions.sql | Adds recipient email tracking |
20260118042626_enable_realtime_and_rls_for_transactions.sql | Enables real-time subscriptions |
20260119003509_add_discarded_emails_table.sql | Creates table for non-transaction emails |
20260127023727_add_pg_cron_and_gmail_watch_renewal.sql | Sets up cron job for Gmail watch renewal |
20260207151917_add_gmail_rpcs.sql | Adds RPC functions for Gmail operations |
20260301224407_create_notification_system.sql | Creates notification tables and triggers |
20260303025417_add_subscription_candidates_rpc.sql | Adds subscription detection logic |
Creating a new migration
Inside the Supabase CLI container:supabase/migrations/.
Applying migrations
Migrations automatically run during:supabase db reset(full reset + migrations + seeds)supabase migration up(pending migrations only)supabase start(on first run)
Seed data
Seeds are SQL files insupabase/seeds/ that populate initial data. They run after migrations during db reset.
Seed files
Creates a test user account for local development.
- Email:
[email protected] - Password:
password123
Inserts 132 demo transactions for the test account spanning multiple months.Includes:
- Various categories (groceries, transport, salary, subscriptions)
- Multiple currencies (USD, EUR, GBP)
- Both income and expense transactions
Seeds sample Gmail connection data for testing.
Creates sample notifications for the test account.
Upserts the
INTERNAL_FUNCTIONS_SECRET into Supabase Vault.Local value: local-dev-internal-secretThis must match the value in supabase/functions/.env.Seed configuration
Seeds are configured insupabase/config.toml:
db reset. To skip seeds:
Vault secrets
Therenew_gmail_watches() database function reads INTERNAL_FUNCTIONS_SECRET from Supabase Vault to authenticate edge function calls.
Local development
The seed file005_internal_functions_secret_local.sql automatically sets this value to local-dev-internal-secret.
Production
Manually create the Vault secret in your remote database:Updating Vault secrets
To update an existing Vault secret:Row Level Security (RLS)
All tables have Row Level Security enabled to ensure users can only access their own data.RLS policies
transactions: Users can read/write only their own transactionsgmail_connections: Users can read/write only their own connectionsnotifications: Users can read/write only their own notificationsseed_jobs: Users can read/write only their own seed jobsdiscarded_emails: Users can read/write only their own discarded emails
Service role access
Edge Functions use theSUPABASE_SERVICE_ROLE_KEY to bypass RLS when necessary, such as:
- Processing incoming Gmail webhooks
- Running background jobs
- Renewing Gmail watches via cron
Realtime subscriptions
The following tables have realtime enabled for instant updates:transactions- New transactions appear immediatelynotifications- Notifications update in real-timeseed_jobs- Progress updates during email import
Enabling realtime on new tables
In your migration:Schema reference
Key tables
gmail_connectionsid(uuid, primary key)user_id(uuid, foreign key to auth.users)email(text, user’s Gmail address)access_token(text, encrypted)refresh_token(text, encrypted)watch_expiration(timestamptz, when Gmail watch expires)history_id(text, last processed history ID)
id(uuid, primary key)user_id(uuid, foreign key to auth.users)amount(numeric, transaction amount)currency(text, ISO currency code)merchant(text, merchant name)category(text, transaction category)transaction_type(text, ‘income’ or ‘expense’)transaction_date(date, when transaction occurred)source_type(text, ‘email’ or ‘manual’)source_message_id(text, Gmail message ID if from email)
id(uuid, primary key)user_id(uuid, foreign key to auth.users)type(text, notification type)title(text, notification title)message(text, notification message)read(boolean, read status)metadata(jsonb, additional data)
Generating TypeScript types
After making schema changes, regenerate TypeScript types:packages/frontend/src/types/database.types.ts with the latest schema.
Troubleshooting
Migration fails with foreign key error
Migration fails with foreign key error
Ensure referenced tables exist before creating foreign keys. Check migration order by filename timestamp.
Seed data not appearing after reset
Seed data not appearing after reset
- Verify seeds are enabled in
supabase/config.toml - Check seed file syntax for SQL errors
- Review logs:
docker compose logs supabase-cli
RLS policy blocks my query
RLS policy blocks my query
Ensure the user is authenticated and the policy allows the operation. Test with the service role key to bypass RLS during debugging.
Realtime not working
Realtime not working
- Check that the table has realtime enabled:
alter publication supabase_realtime add table your_table; - Verify RLS policies allow the user to select from the table
- Ensure the frontend is subscribed to the correct channel