Skip to main content
Keep migrations in the directory selected by application inspection. Filenames use YYYYMMDDHHMMSS_name.sql: a 14-digit UTC prefix and lowercase descriptive name. Read ohmyhost init --dry-run --json for the exact source contract. Ordinary schema migrations are expand-only. Admitted statements create tables, types, domains, sequences, views/materialized views, triggers, indexes or SQL/PLpgSQL functions (SECURITY DEFINER requires SET search_path); ALTER TABLE … ADD, COMMENT ON, CREATE SCHEMA IF NOT EXISTS auth|extensions|private, CREATE EXTENSION IF NOT EXISTS pgcrypto|btree_gist|unaccent, and SET check_function_bodies=false are the other allowed forms. Ordinary DROP/TRUNCATE, row changes, CREATE OR REPLACE, other schema ALTER, GRANT, DO and transaction control are refused. Supabase/storage dependencies, unsupported auth references and provider-role authority remain unsupported. Init reports migration_sql_not_admitted with the refused file/reason. The coordinated 0.1.30 or later release adds a bounded RLS policy lifecycle: CREATE POLICY, ALTER POLICY conditions, atomic DROP POLICY/recreation and single ENABLE/FORCE statements on explicitly named, migrator-owned ordinary tables in public or private. Use TO PUBLIC or omit the role. Every touched table must finish enabled and forced. Only the provided ohmyhost.user_id() and ohmyhost.claims() helper calls are allowed within the reserved schema; platform objects remain protected. DISABLE/NO FORCE, CASCADE, role/grant changes and destructive schema changes are still refused. Existing tables gain only nullable columns without defaults and non-unique indexes. Constraints, foreign keys, triggers and unique indexes may attach only to tables created within the same pending migration run. All pending files apply atomically with a thirty-second statement and five-second lock timeout; CREATE INDEX CONCURRENTLY therefore fails. Applied files are immutable: never edit, rename or remove one; every new timestamp follows the applied catalog. Maximum 128 files,256 KiB each,2 MiB total, UTF-8/LF without BOM. Source planning checks canonical filenames, not complete SQL admission. Run init before paying for a build; execution/catalog validation may still fail as database_migration_failed, with none of the new files applied. Read DATABASE_MIGRATION_FAILED’s file and reason: MIGRATION_CHANGED_EXISTING_SCHEMA, MIGRATION_EXECUTION_FAILED, APPLIED_MIGRATION_CHANGED or MIGRATION_SQL_NOT_ADMITTED. Fix the cause and plan the new commit; never reset to repair a failed migration.

Make an additive change

Add a nullable field, deploy code supporting both shapes, then backfill with authorized database writes of at most 1,000 rows or bounded application work, outside migrations. Dropping/renaming columns and tightening existing constraints are unsupported; leave retired columns unused and enforce new rules in application code. Commit each versioned migration with the application code and matching lockfile. Before promotion, test the migration against representative existing records. A successful clean-database setup alone does not prove an upgrade preserves customer data. For RLS adoption, deploy and verify context-aware application code first, then apply enforcement in a later migration. Every consumer of shared Dev/Prod data must be ready before that migration. After activation, use only a verified context-ready build as a rollback target. Existing-app adoption.

Promote the schema

The promotion plan identifies the verified artifact and migration effects. Isolated Prod receives migrations, not Dev records. After applying, verify both the new feature and pre-existing Prod rows. In shared mode, both environments refer to the same data. Explain that impact before changing it. Do not restore a Dev dump into an existing Prod database to keep them synchronized. Dev and Prod · Database Skill · SQL exports.