Skip to content

Supabase backend

WeSpend uses Supabase from day one: Postgres for storage, Row Level Security for authorization, anonymous auth for onboarding, and Realtime for live multi-device sync.

1. Create a project

Sign in at supabase.com and create a new project. Once it is provisioned, grab the project URL and anon key from Project Settings -> API and put them in your .env.

2. Run the migrations

The schema lives in supabase/migrations/, applied in order:

FileContents
0001_initial_schema.sqlTables, constraints, indexes
0002_rls_policies.sqlRow Level Security policies
0003_functions.sqlBootstrap RPCs, role-guard trigger, realtime publication
0004_device_push_tokens.sqlExpo push tokens per device for household activity notifications
0005_meal_card.sqlmeal_card_settings and the card value on expenses.paid_from
0006_notifications.sqlIn-app notifications feed table
0007_settlement_reminder_function.sqlPostgres function for the weekly settlement reminder
0008_enable_cron_and_net_extensions.sqlEnables pg_cron and pg_net
0009_schedule_settlement_reminder_cron_jobs.sqlCron jobs that POST the reminder via an Edge Function (auth by CRON_SECRET)
0010_add_is_active_to_members.sqlmembers.is_active for archive/reactivate
0011_bills.sqlbill_settings, bill_categories, bill_entries + realtime
0012_bill_settings_realtime.sqlAdds bill_settings to the realtime publication

Option A: SQL editor

Open SQL Editor in the dashboard and paste the contents of each file in order, running each one.

Option B: Supabase CLI

bash
npm install -g supabase
supabase link --project-ref YOUR-PROJECT-REF
supabase db push

3. Enable anonymous sign-in

Onboarding signs members in anonymously, so enable it under Authentication -> Providers -> Anonymous.

How authorization works

Every household-scoped table carries a household_id. The is_household_member(household_id) function checks whether the current auth.uid() belongs to that household, and every RLS policy uses it for both reads and writes.

Two operations run as security definer because the caller is not yet a member when they happen:

  • create_household(household_name, owner_name) - creates the household, the owner member, and seeds default budget and bill categories.
  • accept_invite(invite_code) - binds the caller's identity to a pre-created member slot (only if unclaimed).

A members_role_guard trigger ensures only the household owner can change the is_owner / is_finance_manager columns.

Realtime

The supabase_realtime publication includes budgets, expenses, settlements, members, categories, and the bill tables (bill_settings, bill_categories, bill_entries). The app subscribes per household and invalidates the matching TanStack Query caches on any change, so every member's device updates live. See use-household-realtime.

For the full schema and ER diagram, see the Data model.

Spend together, settle simply.