> ## Documentation Index
> Fetch the complete documentation index at: https://archie.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Schema diff

> Compare the schemas of two environments side by side. Forward-only semantics surface exactly the changes that need to flow from source to target.

The Environment Diff compares the database schemas of two environments side by side. Use it before a [merge](/features/backend/environments/merge) to see exactly what would flow from source to target, or to audit drift between any two environments.

The diff engine introspects both environments' schemas, runs a forward-only comparison, and generates the SQL needed to bring the target up to date.

<img src="https://mintcdn.com/archie-e998dbf6/zOlE9kC39qGD3yqx/features/backend/environments/environments-schema-diff.png?fit=max&auto=format&n=zOlE9kC39qGD3yqx&q=85&s=95e0a3716868569b30daeab60f1c4fb9" alt="Environment schema diff comparison" width="1603" height="461" data-path="features/backend/environments/environments-schema-diff.png" />

## How the diff works

Three stages, all server-side:

| Stage                   | What happens                                                                                                     |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------- |
| 1. Schema analysis      | The platform introspects both environments — tables, columns, indexes, constraints, relationships, enums, views. |
| 2. Comparison           | The two structures are compared with **forward-only** semantics (see below).                                     |
| 3. Migration generation | For every detected change, the platform produces the DDL needed to apply it to the target.                       |

## Forward-only semantics

The engine compares source against target asymmetrically. Specifically:

* Objects in **source but not target** → shown as `CREATED`. They need to be created in the target.
* Objects in **both** → compared field by field. Differences are flagged as modifications.
* Objects **only in target** → **not** shown as `DROPPED`.

The third rule is the key design decision. If a table exists only in the target, it was never present in the source — labeling it `TABLE_DROPPED` would suggest an intentional drop happened in the source. The diff focuses on changes that originated in the source and need to flow to the target.

**Exception** — for column-level changes within a table that exists in both environments, `COLUMN_DROPPED` **is** detected, because that's an actual column-removal operation performed on the source.

## Change types

| Change type               | Description                                                     | Breaking  |
| ------------------------- | --------------------------------------------------------------- | --------- |
| `TABLE_CREATED`           | Table in source but not target.                                 | No        |
| `COLUMN_ADDED`            | New column added to a source table.                             | No        |
| `COLUMN_DROPPED`          | Column removed from a source table that still exists in target. | Yes       |
| `COLUMN_TYPE_CHANGED`     | Column data type modified.                                      | Depends\* |
| `COLUMN_NULLABLE_CHANGED` | Column's nullable constraint toggled.                           | Depends   |
| `COLUMN_DEFAULT_CHANGED`  | Column default value modified.                                  | No        |
| `INDEX_CREATED`           | New index added.                                                | No        |
| `INDEX_DROPPED`           | Index removed.                                                  | No        |
| `ENUM_CREATED`            | New enum type in source.                                        | No        |
| `ENUM_VALUE_ADDED`        | New value on an enum.                                           | No        |
| `ENUM_VALUE_REMOVED`      | Value removed from an enum.                                     | Yes       |
| `VIEW_CREATED`            | View in source but not target.                                  | No        |
| `VIEW_MODIFIED`           | View SQL definition changed.                                    | No        |
| `RELATIONSHIP_ADDED`      | New foreign key relationship.                                   | No        |
| `RELATIONSHIP_DROPPED`    | Foreign key relationship removed.                               | Yes       |

\*Type narrowing (`text` → `varchar(50)`, `int8` → `int4`) is breaking — it can lose data. Type widening (`varchar` → `text`) is safe.

`TABLE_DROPPED`, `ENUM_DROPPED`, and `VIEW_DROPPED` aren't generated at the top level — see [forward-only semantics](#forward-only-semantics) above.

## Cherry-pick selection

The diff view supports selecting individual changes for a subsequent [merge](/features/backend/environments/merge):

* Tick or untick each change individually.
* Use the group checkbox on a table to select or deselect all changes for that table at once.
* **Breaking changes are not pre-selected by default**. You have to explicitly opt in to drops, type narrowing, and enum-value removals — protection against accidental data loss.

The selection set is what flows into the merge. Anything you leave unticked stays in the source.

## Reading the diff output

Each entry in the diff has these fields:

| Field                   | What it tells you                                                      |
| ----------------------- | ---------------------------------------------------------------------- |
| `changeType`            | One of the change-type identifiers above.                              |
| `objectType`            | `table`, `column`, `index`, `enum`, `view`, or `relationship`.         |
| `objectName`            | The name of the affected object (e.g., the table name).                |
| `fieldName`             | For column-level changes, the column name.                             |
| `oldValue` / `newValue` | Before/after values for modifications (e.g., `varchar(255)` → `text`). |
| `isBreaking`            | Boolean — true if the change can cause data loss or API breakage.      |
| `sql`                   | The DDL statement that would apply this change.                        |

## Use cases

| Goal                             | How to use the diff                                                                              |
| -------------------------------- | ------------------------------------------------------------------------------------------------ |
| Pre-merge review                 | Diff source → target before clicking Merge to see exactly what will flow.                        |
| Audit drift between environments | Diff `staging` against `master` to surface ad-hoc changes that snuck into one but not the other. |
| Generate migration SQL           | Read the `sql` field on each change to feed into another tool.                                   |
| Verify a merge succeeded         | Diff source → target after a merge — should return zero differences if the merge was clean.      |

## Running the diff via GraphQL

```graphql theme={null}
query EnvironmentDiff($input: EnvironmentDiffInput!) {
  environmentDiff(input: $input) {
    success
    message
    changes {
      changeType
      objectType
      objectName
      fieldName
      oldValue
      newValue
      isBreaking
      sql
    }
    summary {
      totalChanges
      breakingChanges
      addedTables
      droppedTables
      modifiedTables
    }
  }
}
```

```json theme={null}
{
  "input": {
    "projectId": "f7e4a264-d659-4719-91e8-c2d74654e529",
    "sourceEnvironment": "master",
    "targetEnvironment": "staging"
  }
}
```

The `summary` block is a quick at-a-glance view; `changes` carries the detail.

## FAQ

<AccordionGroup>
  <Accordion title="Why doesn't the diff show TABLE_DROPPED at the top level?">
    Forward-only semantics. A table that exists only in the target was never in the source, so describing it as "dropped" would be misleading. Drops are surfaced at the column level only, where they reflect an actual removal that happened in the source.
  </Accordion>

  <Accordion title="What does 'breaking' mean exactly?">
    A change that may cause data loss or break consumers of the API. Drops, narrowing type changes, removing enum values, and removing relationships are flagged as breaking. They're never pre-selected for merge — you have to opt in.
  </Accordion>

  <Accordion title="Can I diff three environments at once?">
    The diff is pairwise. To compare three environments, run two diffs and reconcile. Most workflows compare a feature branch against `staging` and `staging` against `master` independently.
  </Accordion>

  <Accordion title="Does the diff include data, or just schema?">
    Schema only — tables, columns, indexes, enums, views, relationships. Data differences aren't surfaced; the diff is about structural drift, not row counts.
  </Accordion>

  <Accordion title="What happens if I diff during a branching or merging operation?">
    Both environments must be in `active` status. Diffs against environments in `branching`, `merging`, or `error` state are rejected — the schemas may not be in a consistent state to compare.
  </Accordion>
</AccordionGroup>
