Skip to content

SaaS · Postgres · Supabase

Per-seat SaaS pricing in Postgres without the spreadsheet sprawl

Four tables, one RPC, zero spreadsheets

💼🔌📊
If you have been living under a rock, every B2B SaaS still bills per seat. The pricing page says "$29 per user per month" and everyone nods. Spoiler: the schema behind that line is where most teams quietly lose their minds. The usual mess looks like one bloated tenants table with seat_limit, plan_name, and a column called notes that someone is using as a kill switch. Don't do that. The Money Pattern: four tables, one job each. Plans hold the catalogue. Subscriptions link tenant to plan. Overrides handle the "just give them 50 seats, Aiden said it was fine" cases. Usage_daily is the rollup your UI reads.
        
      
Behold: a single view stitches it together. Override beats plan, plan beats default, NULL never wins.
        
      
Plot twist: the firehose stays separate. Raw usage_events writes go through a SECURITY DEFINER RPC so RLS never blocks a logged-in user from counting their own action, but they also can't forge a tenant_id. The dashboard reads usage_daily, never the firehose.
        
      
The catch: nightly cron must roll usage_events into usage_daily or the firehose eats your storage budget alive. Use pg_cron or a Supabase scheduled function. Either works. Skipping it does not. The Verdict: four tables, one view, one RPC. No spreadsheets, no plan_name column with 12 values that should have been an enum three years ago. Per-seat pricing is not hard. The schema sprawl is.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.