Using supaschema with Supabase Projects and the CLI
Configure supaschema alongside the Supabase CLI for local development, staging pushes, and production-safe declarative schema management.
supaschema fits into the Supabase workflow as the migration author: you describe your schema in SQL files, supaschema diffs them and writes the migration, and then the Supabase CLI applies it. supaschema never touches your real database directly — it only generates SQL. The Supabase CLI retains full control over when and how migrations are applied.
Do not edit files in supabase/migrations/ by hand unless you are hand-authoring a migration for a change supaschema cannot automate (such as an enum value removal). Generated files will be overwritten on the next diff run.
Create supaschema.config.json at the project root. The supabase-auto adapter is the recommended starting point for Supabase projects — it applies sensible defaults for managed schema exclusions, role handling, and transaction wrapping:
supabase-auto pre-configures managed schema exclusions. Use generic for plain PostgreSQL.
schemaPaths
Array of directories or glob patterns pointing to your schema SQL files.
migrationsDir
Where supaschema writes generated migration files. Should match the Supabase CLI’s migrations directory.
excludedGrantRoles
Roles that appear in GRANT statements in your schema but should not be created by supaschema (they already exist in a real Supabase project).
transactionMode
per-migration wraps each migration in a single transaction. Use none for migrations that include CREATE INDEX CONCURRENTLY or other transaction-unsafe operations.
The supabase-auto adapter automatically excludes the following schemas from declarative ownership. You cannot accidentally overwrite Supabase’s internal tables by placing SQL in these schemas:
auth
storage
realtime
vault
extensions
cron
net
supabase_functions
graphql
graphql_public
If supaschema encounters a reference to one of these schemas in your schema files, it raises a warning rather than generating a migration that would conflict with Supabase’s managed infrastructure.
When no databaseUrl is set in your config or environment, supaschema reads supabase/config.toml to discover the local Supabase stack’s connection parameters automatically. This means you can run supaschema diff without any additional configuration after supabase start:
Write or update your table definitions, views, functions, and RLS policies in supabase/schemas/:
-- supabase/schemas/001_app.sqlCREATE SCHEMA app;CREATE TABLE app.accounts ( id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL);ALTER TABLE app.accounts ENABLE ROW LEVEL SECURITY;CREATE POLICY accounts_select ON app.accounts FOR SELECT TO public USING (true);
3
Generate the migration
npx supaschema diff
supaschema writes a timestamped migration file to supabase/migrations/ and refreshes database.types.ts and database.zod.ts.
Row-level security policies are fully supported. Write them alongside your table definitions in your schema files and supaschema will include them in the generated migration:
-- supabase/schemas/001_app.sql-- Enable RLSALTER TABLE app.accounts ENABLE ROW LEVEL SECURITY;-- Users can only read their own accountCREATE POLICY accounts_own_select ON app.accounts FOR SELECT TO authenticated USING (auth.uid() = owner_id);-- Only the service role can insertCREATE POLICY accounts_service_insert ON app.accounts FOR INSERT TO service_role WITH CHECK (true);
supaschema tracks policies by name. Renaming a policy is treated as a drop and recreate. If you need to rename a policy without dropping it, hand-author that specific ALTER POLICY statement.
Use verify to prove your migration applies cleanly, is idempotent, and leaves the database matching your declared schema. Pass --ensure-roles to create stub roles (authenticated, anon, service_role) that exist in a real Supabase project but may not exist on a plain Postgres instance:
supaschema sync combines the drift gate and apply step into one command. Use it in environments where you want to verify the database is in sync and apply any pending migrations in a single operation:
npx supaschema sync --local
sync exits non-zero if drift is detected and the migration fails to apply, making it safe to use as a deployment gate in staging pipelines.
supaschema never applies migrations to remote databases. Use the Supabase CLI for that:
# Push all pending migrations to your linked projectsupabase db push# Or target a specific environmentsupabase db push --db-url "$PRODUCTION_DATABASE_URL"
Always run npx supaschema verify against a staging environment before pushing to production. Verify applies the migration to a throwaway database — point it at a disposable staging branch, not at production.
For staging branches on Supabase, set SUPASCHEMA_VERIFY_ALLOW_REMOTE=1 to permit verify to target a remote URL that you treat as intentionally disposable:
Here is the complete flow from schema change to type-safe client usage:
1
Declare the schema
-- supabase/schemas/001_app.sqlCREATE TABLE app.posts ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), account_id uuid NOT NULL REFERENCES app.accounts(id) ON DELETE CASCADE, title text NOT NULL, body text, published boolean NOT NULL DEFAULT false, created_at timestamptz NOT NULL DEFAULT now());ALTER TABLE app.posts ENABLE ROW LEVEL SECURITY;CREATE POLICY posts_public_select ON app.posts FOR SELECT TO public USING (published = true);CREATE POLICY posts_own_all ON app.posts FOR ALL TO authenticated USING ( account_id IN ( SELECT id FROM app.accounts WHERE owner_id = auth.uid() ) );