# Schema patch format

A schema patch is a single JSON object that describes a change to a diagram. The same format is accepted by #patch= URLs, the MCP server, and the paste-back box in the editor.

## Where patches are accepted

| Surface | How |
|---|---|
| Diagram URL | `https://drawsql.app/draw#patch=<url-encoded JSON>` — renders instantly, no account |
| MCP server | The `visualize_schema` tool takes a `schema` patch or SQL `ddl` |
| Editor | **Edit → Take to your AI** copies your schema, and accepts a patch pasted back |

The URL and MCP paths build a new diagram. The editor path applies the patch to the
diagram you already have open, and shows a diff before anything changes.

## Strategy

Every patch carries a `strategy`:

- `merge` — add, update and delete. Required if the patch has `deletions`.
- `additive` — add only. Existing entities are never touched.

Reference tables and columns **by name**. Include only what changes; omit unchanged
columns and unused optional fields.

## Minimal example

```json
{
  "strategy": "merge",
  "tables": [
    {
      "name": "posts",
      "comment": "Blog posts",
      "columns": [
        { "name": "id", "type": "bigint", "is_primary_key": true, "is_auto_increment": true },
        { "name": "user_id", "type": "bigint", "is_index": true },
        { "name": "title", "type": "varchar", "length": 255 },
        { "name": "body", "type": "text", "is_nullable": true }
      ]
    }
  ]
}
```

## Tables

| Property | Type | Notes |
|---|---|---|
| `name` | string | Table name (required) |
| `old_name` | string | Previous name. Set this only when renaming |
| `comment` | string | Table comment |
| `parent_name` | string | Name of a group to nest this table in. The group may be in the same patch |
| `parent_uuid` | string | UUID of an existing group |
| `columns` | array | See below |
| `indexes` | array | See below |
| `left`, `top` | integer | Canvas coordinates in pixels. Omit them and DrawSQL lays the diagram out for you |

## Columns

| Property | Type | Notes |
|---|---|---|
| `name` | string | Column name (required) |
| `type` | string | Data type name, resolved for the diagram's driver (required) |
| `length` | integer | Type length, e.g. `255` for `varchar(255)` |
| `is_primary_key` | boolean | Primary key |
| `is_auto_increment` | boolean | Auto-increment or serial |
| `is_nullable` | boolean | Allows NULL |
| `is_unique_key` | boolean | Unique constraint |
| `is_index` | boolean | Single-column index |
| `is_unsigned` | boolean | Unsigned numeric (MySQL) |
| `default` | string | Default value |
| `enum_values` | array | Allowed values for `enum` and `set` columns |

Use `enum_values` for enums. Do not describe the allowed values in a sticky note.

```json
{
  "strategy": "merge",
  "tables": [
    {
      "name": "users",
      "columns": [
        { "name": "role", "type": "enum", "enum_values": ["admin", "instructor", "student"] }
      ]
    }
  ]
}
```

### Accepted aliases

These older names still work. The engine rewrites them before applying the patch.

| Alias | Becomes |
|---|---|
| `is_primary` | `is_primary_key` |
| `is_not_null` | `is_nullable`, inverted |
| `is_unique` | `is_unique_key` |

## Indexes

```json
{ "name": "idx_users_email", "type": "unique", "columns": ["email"] }
```

`type` is one of `primary`, `unique`, `index`. All three fields are required.

## Relationships

```json
{
  "type": "one-to-many",
  "source_table": "users",
  "source_column": "id",
  "target_table": "posts",
  "target_column": "user_id"
}
```

`type` is one of `one-to-one`, `one-to-many`, `many-to-one`.

For `one-to-many` the source is the "one" side. For `many-to-one` it is the "many"
side, so the same link can be written either way round.

## Groups

Create the group, then point tables at it with `parent_name`. Both can live in one patch.

```json
{
  "strategy": "merge",
  "groups": [{ "name": "Auth" }, { "name": "Content" }],
  "tables": [
    { "name": "users", "parent_name": "Auth", "columns": [{ "name": "id", "type": "bigint", "is_primary_key": true }] },
    { "name": "posts", "parent_name": "Content", "columns": [{ "name": "id", "type": "bigint", "is_primary_key": true }] }
  ]
}
```

Every table that belongs to a group must set `parent_name` itself. Groups do not
list their members.

## Sticky notes

```json
{ "content": "All timestamps are UTC", "parent_name": "Auth" }
```

`parent_name` and `parent_uuid` are optional.

## Renames

Set `old_name` alongside the new `name`. This works on tables and on columns.

```json
{
  "strategy": "merge",
  "tables": [
    { "name": "articles", "old_name": "posts", "columns": [{ "name": "content", "old_name": "body" }] }
  ]
}
```

## Deletions

`deletions` requires `"strategy": "merge"`.

```json
{
  "strategy": "merge",
  "deletions": {
    "tables": ["tmp"],
    "columns": [{ "table": "users", "columns": ["legacy"] }],
    "relationships": [
      { "source_table": "users", "source_column": "id", "target_table": "posts", "target_column": "user_id" }
    ]
  }
}
```

- `tables` — table names
- `columns`, `indexes` — grouped by table. `table` takes a name or a UUID. The object
  shorthand `{"users": ["legacy"]}` is accepted too
- `relationships` — the four endpoint fields
- `groups` — `{ "name": "Auth", "delete_children": false }`. With `delete_children`
  false the members are ungrouped rather than removed
- `sticky_notes` — sticky note UUIDs

## Tolerance

The patch engine expects imperfect input and repairs it:

- Unknown keys are stripped rather than rejected.
- String booleans (`"true"`, `"false"`) are coerced.
- String numbers (`"255"`) are coerced.
- Whitespace around names is trimmed.
- Duplicate entries for one table are merged.

A patch that conflicts with itself is refused with an error naming the entity, for
example a table listed in both `tables` and `deletions.tables`.

## Canonical contract

The machine-readable contract is published at
[`/schema-patch-contract.json`](/schema-patch-contract.json). It is the same file
the applier and the AI tool schemas are built from, so it never disagrees with what
the engine accepts. Read it if you are generating patches programmatically.
