# Form definition diff

`form-definition-diff` · version 1.0.0 · Forms · free, no key needed

Diff two form definitions by stable field id, including choice lists and field order.

**Use when you need to: diff form fields · form structure diff.**

## Supported

- diff form fields
- form structure diff

## Not supported

- filled answer diff
- fuzzy field matching
- html extraction
- pdf or ocr

## Behavior

- Match fields by exact id. Do not infer renames. NFC and NFD ids are different.
- Duplicate field ids are rejected. Unknown keys are rejected. U+0000 is rejected.
- Field order is not equality. A shared-id sequence change sets order_changed and returns both sequences.
- changed.changes lists label, type, required, and choices in that order, and only the ones that differ.
- Choice order is part of the definition. choice_added and choice_removed keep the source order. choice_reordered compares the shared choice sequence.
- Unchanged fields are counted and not copied. Counts match the arrays. Over-budget output is rejected.

## Input

- `left` (object, required)
- `right` (object, required)

## Output

- `added` (array of any, required)
- `removed` (array of any, required)
- `changed` (array of any, required)
- `order_changed` (boolean, required)
- `common_order_left` (array of string, optional)
- `common_order_right` (array of string, optional)
- `counts` (object, required)

## Limits

- max input bytes: 262144
- max output bytes: 262144
- max input depth: 16
- max input nodes: 20000
- max fields: 100
- max choices: 50
- max respondents: 100
- max id bytes: 128
- max label bytes: 256
- max choice bytes: 128
- max answer string bytes: 4096
- max answer depth: 6
- max answer nodes: 50
- max changes: 500
- max mapping: 100

## Example

Request input:

```json
{
  "left": {
    "fields": [
      {
        "id": "email",
        "label": "Email",
        "type": "string",
        "required": true
      },
      {
        "id": "plan",
        "label": "Plan",
        "type": "choice",
        "required": true,
        "choices": [
          "free",
          "pro"
        ]
      }
    ]
  },
  "right": {
    "fields": [
      {
        "id": "plan",
        "label": "Plan",
        "type": "choice",
        "required": true,
        "choices": [
          "pro",
          "team"
        ]
      },
      {
        "id": "seats",
        "label": "Seats",
        "type": "integer",
        "required": false
      }
    ]
  }
}
```

Response:

```json
{
  "result": {
    "added": [
      {
        "id": "seats",
        "field": {
          "id": "seats",
          "label": "Seats",
          "type": "integer",
          "required": false
        }
      }
    ],
    "removed": [
      {
        "id": "email",
        "field": {
          "id": "email",
          "label": "Email",
          "type": "string",
          "required": true
        }
      }
    ],
    "changed": [
      {
        "id": "plan",
        "changes": [
          "choices"
        ],
        "left": {
          "id": "plan",
          "label": "Plan",
          "type": "choice",
          "required": true,
          "choices": [
            "free",
            "pro"
          ]
        },
        "right": {
          "id": "plan",
          "label": "Plan",
          "type": "choice",
          "required": true,
          "choices": [
            "pro",
            "team"
          ]
        },
        "choice_added": [
          "team"
        ],
        "choice_removed": [
          "free"
        ],
        "choice_reordered": false
      }
    ],
    "order_changed": false,
    "counts": {
      "added": 1,
      "removed": 1,
      "changed": 1,
      "unchanged": 0,
      "reordered": 0
    }
  }
}
```

## How to call it

### MCP

Connect `https://computefirst.net/mcp` ([setup](/docs#connect)), then call `execute` with:

```json
{
  "id": "form-definition-diff",
  "version": "1.0.0",
  "input": {
    "left": {
      "fields": [
        {
          "id": "email",
          "label": "Email",
          "type": "string",
          "required": true
        },
        {
          "id": "plan",
          "label": "Plan",
          "type": "choice",
          "required": true,
          "choices": [
            "free",
            "pro"
          ]
        }
      ]
    },
    "right": {
      "fields": [
        {
          "id": "plan",
          "label": "Plan",
          "type": "choice",
          "required": true,
          "choices": [
            "pro",
            "team"
          ]
        },
        {
          "id": "seats",
          "label": "Seats",
          "type": "integer",
          "required": false
        }
      ]
    }
  }
}
```

### HTTP (no key)

```sh
curl -X POST https://computefirst.net/v1/tools/form-definition-diff/versions/1.0.0/execute \
  -H "Content-Type: application/json" \
  -d '{"left":{"fields":[{"id":"email","label":"Email","type":"string","required":true},{"id":"plan","label":"Plan","type":"choice","required":true,"choices":["free","pro"]}]},"right":{"fields":[{"id":"plan","label":"Plan","type":"choice","required":true,"choices":["pro","team"]},{"id":"seats","label":"Seats","type":"integer","required":false}]}}'
```

The machine-readable contract is at [/v1/tools/form-definition-diff/versions/1.0.0](/v1/tools/form-definition-diff/versions/1.0.0).

### CLI

```sh
node cli.mjs run form-definition-diff 1.0.0 --input input.json --base-url https://computefirst.net
```

Get the client at [/clients/cli/](/clients/cli/).

## Related tools

- [Form answer diff](/tools/form-answer-diff): Diff two answer maps against one shared form definition.
- [Form compare](/tools/form-compare): Compare two filled form snapshots and return compact field and answer changes.
- [Form field map](/tools/form-field-map): Rename form fields with an explicit id map and reject collisions.
- [Form response diff](/tools/form-response-diff): Reconcile two form response sets by exact respondent id.
- [Form response check](/tools/form-response-check): Report missing required answers, nulls, type failures, and choice failures.
