ShipVeryFastShipVeryFast
Documentation

Data model

This page explains the shape of the database and the reasoning behind it. For migrations and the Supabase clients, see Database & models.

The core tables

A single migration, supabase/migrations/0001_initial_schema.sql, creates everything:

  • users, mirrors auth.users with profile and role fields.
  • subscriptions, links a user to their Stripe customer and subscription with a status.
  • payments, one row per invoice or charge.
  • email_verifications and password_resets, short-lived tokens with expiry.
  • blog_posts, content with SEO fields and a published flag.

Why mirror auth.users

Supabase keeps its own auth.users table. ShipVeryFast adds a public.users table and a trigger that creates a matching row whenever someone signs up. This gives you a normal table to join against and extend, without touching the auth schema.

supabase/migrations/0001_initial_schema.sql (excerpt)
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute function public.handle_new_user();

Row Level Security is the boundary

RLS is enabled on every table. A user can read and update only their own profile, and read only their own subscriptions and payments. Published blog posts are public. Because the rule lives in the database, it holds no matter which client makes the query, so a leaked key or a buggy route cannot read another tenant's data.

The service role bypasses RLS

SUPABASE_SERVICE_ROLE_KEY ignores RLS by design, for trusted server work. Use the anon client for anything driven by user input, and keep the service key server-side only.