Atlas already reports table and column drops as destructive changes. Two new checks, BC103 (dropping a table) and BC104 (dropping a column), now also classify them as backward-incompatible, enabled by default. Destructive is about data loss, these are about API compatibility, since even an empty column is a breaking change for the code that selects it.
Dropping a column causes two problems: the data is lost (DS102/DS103), and every deployed client that still references the object starts erroring, even if the column is empty and even if the data loss was allowlisted. This happens even when the drop itself is expected and accepted. The previous version of the application commonly keeps running while the migration applies (rolling deployments, staged rollouts), its queries still reference the dropped object, and the backend starts returning errors. In practice, that is downtime.
The problem is one of release ordering, and it is invisible in the SQL. The safe rollout is expand-and-contract: deprecate the column or table, remove every reference to it from the application, and deploy that release. Only after no running version references the object is it safe to drop. BC103 and BC104 fire on the contract step and tell the author whether the deprecation release has shipped yet.
What It Reports by Default
Given a migration that drops a column and a table:
ALTER TABLE users DROP COLUMN name;DROP TABLE pets;
-- analyzing version 2 -- destructive changes detected: -- L1: Dropping non-virtual column "name" https://atlasgo.io/lint/analyzers#DS103 -- L2: Dropping table "pets" https://atlasgo.io/lint/analyzers#DS102 -- backward incompatible changes detected: -- L1: Clients using column "users"."name" will fail https://atlasgo.io/lint/analyzers#BC104 -- L2: Clients using table "pets" will fail https://atlasgo.io/lint/analyzers#BC103
The two analyzers are complementary: destructive names the change, incompatible names the consequence. Drops that clients never see are skipped, such as an object created, recreated, or renamed back within the same file.
Enforcing Deprecation First
The drop_table and drop_column blocks, part of Atlas Pro, attach your team's policy to the diagnostic, evaluated per dropped object, with the object bound to self. The message is free-form, so tailor it to your stack, e.g., point authors at your ORM's schema path and deprecation syntax, name the owning team to contact, link an internal runbook, or add anything else they should be aware of before dropping:
lint { incompatible { drop_column { message = "deprecate ${self.table.name}.${self.name} in the application schema (ent: Deprecated, GraphQL: @deprecated) and release it before dropping" } drop_table { message = "contact the owners of ${self.schema.name}.${self.name} before dropping it" } }}
self exposes the dropped object's name and its parents (table.name, schema.name), and the Atlas editor plugins autocomplete the fields as you type ${self...}.
Each diagnostic then carries the policy:
-- backward incompatible changes detected: -- L1: Clients using column "users"."name" will fail: deprecate users.name in the application schema (ent: Deprecated, GraphQL: @deprecated) and release it before dropping https://atlasgo.io/lint/analyzers#BC104
The reviewer's question becomes concrete: has the release that deprecated users.name actually rolled out? If not, the PR waits.
Tuning and Disabling
Silence a check entirely in the project configuration:
lint { check "BC104" { skip = true }}
Or per statement or file in the migration itself:
-- atlas:nolint BC103DROP TABLE audit_log_2019;
-- atlas:nolint incompatible disables the whole analyzer, including the rename checks BC101/BC102.
See the analyzers reference for the full check documentation.