This is a minimal example showing how to keep your entire database state in your repository using Supabase : Policies, triggers, cron, functions etc.
I was constantly running into issues where:
- RLS UI is annoying - managing policies through Supabase dashboard is clunky and error-prone
- RLS deployment bugs - policies weren't being deployed as part of go-live, or were named differently from what they actually do
- My local database was different from staging
- Staging was different from production
- Database functions, triggers, and policies were getting lost
- We were increasingly relying on postgres db functions and because of that a function code was changing often.
- Cron jobs would disappear after deployments
- Team members had inconsistent database states
- Row Level Security policies would drift - manual changes in Supabase dashboard weren't in code
So i decided to build a simple system to allow syncing our database across developers and envs.
database-state-example/
├── supabase/
│ ├── run.js # Migration runner script
│ ├── db-migrations/ # SQL migration files
│ ├── db-functions/ # PostgreSQL functions
│ ├── policies.sql # Row Level Security policies
│ ├── triggers.sql # Database triggers
│ └── cron.sql # Scheduled jobs
├── package.json # CLI commands and dependencies
└── README.md # This file
- Version-controlled database state: All migrations, functions, policies, triggers, and cron jobs are in your repo
- Simple migration system: Track which migrations have been applied
- Comprehensive CLI: Create, run, revert, and manage database changes
- CI/CD ready: Easy integration with deployment pipelines
- Policy synchronization: Automatic cleanup and recreation of RLS policies
- Install dependencies:
yarn install
- Set up your database connection:
export DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
- Run migrations:
yarn migrate
yarn migrate
- Run all pending migrationsyarn sync:db
- Sync functions, triggers, policies, and cron jobsyarn create:migration <name>
- Create a new migration fileyarn create:function <name>
- Create a new PostgreSQL functionyarn list:migrations
- List applied and pending migrationsyarn revert:migration <name>
- Revert a specific migrationyarn help
- Show all available commands
The run.js
script:
- Connects to your database
- Creates a
supabase_migrations
table to track applied migrations - Compares local migration files with applied migrations
- Runs pending migrations in order
- Records successful migrations in the tracking table
This ensures your database state is always in sync with your codebase.
The drop_policies_in_transaction()
function is crucial for keeping Row Level Security policies in sync:
-- This must be at the top of policies.sql
SELECT supabase_migrations.drop_policies_in_transaction();
Why this is important:
- Policies can be created manually in the database (via Supabase dashboard)
- These manual policies won't be in your code
- Without dropping them first, you'll have orphaned policies
- The function ensures only policies defined in your code exist
This prevents policy drift and ensures your security rules are always exactly what you've defined in your repository.