Skip to main content
By default, supaschema refuses to generate migrations that could destroy data — column drops, type changes, table removals, and incompatible view replacements all cause the diff to halt with a diagnostic error. The hints system is how you give explicit, reviewable approval for those operations. Instead of disabling safety checks globally, you name the exact objects you intend to change, and supaschema renders the narrowest possible SQL to carry out your intent.

Why Hints Exist

When supaschema computes a diff, it classifies every planned operation as either safe (additive or purely structural) or potentially destructive (data-loss risk). Safe operations — adding columns, creating indexes, new tables — are always rendered automatically. Destructive operations are held back until you acknowledge them. This design catches the most common source of production incidents: a schema file is updated, a migration is generated and reviewed, but nobody noticed that renaming a column in the declarative file would produce a DROP COLUMN + ADD COLUMN pair instead of an ALTER TABLE … RENAME COLUMN. Hints force that acknowledgement to be explicit and checked into source control alongside the schema change.
Hints are stored in supaschema.config.json under the hints key. They are committed to your repository, giving you a durable audit trail of every destructive change that was approved and when.

Object Key Format

Both hints.destructive and hints.renames identify database objects using a kind:schema.name string format. The exact shape depends on the object kind:
Object KindKey FormatExample
Tabletable:schema.nametable:app.accounts
Columncolumn:schema.table.columncolumn:app.accounts.email
Functionfunction:schema.name(signature)function:app.greeting()
Viewview:schema.nameview:app.account_names
Policypolicy:schema.table_name:policy_namepolicy:app.accounts_select:accounts
Sequencesequence:schema.namesequence:app.order_id_seq
Indexindex:schema.index_name:table_nameindex:app.accounts_email_idx:accounts
Wildcard**
Run npx supaschema plan to see the exact object key for every planned operation. Copy keys directly from that output rather than constructing them by hand.
Using the wildcard key "*" approves all destructive operations in the current diff. Reserve this for throw-away development databases. Never commit a wildcard hint to a config file used in staging or production.

hints.destructive — Approving Destructive Changes

Add an object key to hints.destructive to tell supaschema you have reviewed the risk and want it to proceed with a data-loss operation on that specific object.
{
  "hints": {
    "destructive": [
      "column:app.accounts.email"
    ]
  }
}
When supaschema finds a hinted key, it renders the safest possible SQL for the change:
  • Column dropsDROP COLUMN IF EXISTS with the exact column name
  • Type changesALTER COLUMN … TYPE … using the new type, without a USING clause unless the types are compatible
  • Table dropsDROP TABLE IF EXISTS
  • Function dropsDROP FUNCTION IF EXISTS
Hinting an object key approves the destructive change for one diff run. Once the migration has been generated and the hint is no longer needed, remove it from the config to restore the default safety posture.

Approving Multiple Objects

You can list as many keys as needed for a single migration:
{
  "hints": {
    "destructive": [
      "column:app.accounts.email",
      "column:app.accounts.phone",
      "view:app.account_names"
    ]
  }
}

hints.renames — Authorizing Object Renames

When you rename a table, column, view, or function in your schema file, supaschema sees a deletion and an addition — it does not infer a rename on its own. Supply a hints.renames entry to instruct supaschema to emit an ALTER … RENAME TO statement instead of a drop-and-recreate pair.
{
  "hints": {
    "renames": [
      { "from": "table:app.users", "to": "table:app.accounts" }
    ]
  }
}
You can chain multiple renames in a single diff:
{
  "hints": {
    "renames": [
      { "from": "table:app.users", "to": "table:app.accounts" },
      { "from": "column:app.accounts.full_name", "to": "column:app.accounts.display_name" }
    ]
  }
}

Supported Rename Kinds

KindSupported
Tables
Columns
Functions
Procedures
Views
Materialized Views
Sequences
Indexes

Rename Restrictions

  • You cannot rename an object across schemas. The from and to keys must reference the same schema. To move an object to a different schema, drop and recreate it manually. Attempting a cross-schema rename produces the SUPA_PLAN_RENAME_SET_SCHEMA_UNSUPPORTED diagnostic.
  • Both the from and to keys must use the same kind. Mismatched kinds produce the SUPA_PLAN_RENAME_KIND_MISMATCH diagnostic.
  • The from key must match an object that exists in the live database. If no matching object is found, supaschema emits SUPA_PLAN_RENAME_HINT_UNMATCHED.
After generating a rename migration, verify it against your target database with npx supaschema diff --migration before applying. The SUPA_PLAN_RENAME_VERIFY_REQUIRED flag is set when supaschema cannot statically confirm the rename is safe.

Using --write-hints to Scaffold Your Config

When a diff is blocked by a destructive change and you are not sure which object keys to add, run the diff command with the --write-hints flag:
npx supaschema diff --write-hints
supaschema outputs (or writes to your config) a skeleton hints block containing every object key that needs to be acknowledged before the migration can be generated:
{
  "hints": {
    "destructive": [
      "column:app.accounts.email"
    ],
    "renames": []
  }
}
Review the skeleton, remove any keys you did not intend to change, and rerun diff normally. This workflow ensures you see the full scope of destructive operations before approving any of them.
Pipe --write-hints output into a code review comment or pull request description so reviewers can audit exactly which destructive changes are being authorized.

Incompatible View and Routine Replacements

If you change a view’s column list in a way that is incompatible with a simple CREATE OR REPLACE VIEW — for example, removing a column or changing a column’s type — PostgreSQL rejects the replacement. When you hint the view’s object key, supaschema renders a safe DROP VIEW IF EXISTS followed by the new CREATE VIEW:
{
  "hints": {
    "destructive": [
      "view:app.account_names"
    ]
  }
}
The same pattern applies to functions and procedures whose signature changes in an incompatible way.

Enum Changes

Adding new values to an existing ENUM type is fully supported and does not require a hint — supaschema renders ALTER TYPE … ADD VALUE automatically. Removing enum values or reordering them is a different story: PostgreSQL does not support either operation natively, and no safe automated migration exists. supaschema blocks these changes unconditionally and asks you to hand-author a recipe. A typical recipe involves:
  1. Creating a new enum type with the desired values
  2. Migrating all columns using the old type to the new one with USING
  3. Dropping the old type
-- Example hand-authored enum migration
ALTER TYPE app.status RENAME TO status_old;
CREATE TYPE app.status AS ENUM ('active', 'inactive');
ALTER TABLE app.accounts
  ALTER COLUMN status TYPE app.status
  USING status::text::app.status;
DROP TYPE app.status_old;
Place this SQL in a manual migration file in your migrationsDir and track it through your normal deployment process.

Diagnostic Codes

CodeMeaning
SUPA_PLAN_DESTRUCTIVE_HINT_REQUIREDA destructive operation was detected and no matching hint was found
SUPA_PLAN_COLUMN_ALTER_HINT_REQUIREDA column type change requires a hint before supaschema will render the ALTER COLUMN
SUPA_PLAN_RENAME_HINT_UNMATCHEDA hints.renames entry references an object that does not exist in the live database
SUPA_PLAN_RENAME_KIND_MISMATCHThe from and to keys in a rename hint use different object kinds
SUPA_PLAN_RENAME_VERIFY_REQUIREDThe rename could not be statically verified; run diff --migration to confirm