Skip to content

Quickstart

This guide sets up Contractual in a new directory with linting, breaking change detection, a versioned changeset, and a bumped contract version. Estimated time: 5 minutes.

Create a project directory and add a JSON Schema file. This is the contract Contractual will manage.

Terminal window
mkdir my-project && cd my-project

Create schemas/order.schema.json:

{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/order.schema.json",
"title": "Order",
"description": "A customer order",
"type": "object",
"required": ["id", "status", "amount"],
"properties": {
"id": {
"type": "string",
"description": "Unique order identifier"
},
"status": {
"type": "string",
"enum": ["pending", "confirmed", "shipped", "delivered"],
"description": "Current order status"
},
"amount": {
"type": "string",
"description": "Order total as a formatted string, e.g. '12.50'"
},
"customer_id": {
"type": "string",
"description": "ID of the customer who placed the order"
}
},
"additionalProperties": false
}
Terminal window
mkdir schemas
# paste the JSON above into schemas/order.schema.json

Run contractual init from the repository root. It scans for recognizable spec files and scaffolds the configuration.

Terminal window
contractual init

Expected output:

Scanning for contracts...
Found schemas/order.schema.json → JSON Schema
Scaffolding contractual.yaml... done
Scaffolding .contractual/ done
.contractual/snapshots/ versioned snapshots will live here
.contractual/changesets/ pending changesets will live here
.contractual/versions.json
Run `contractual lint` to validate contracts.

This creates two items:

  • contractual.yaml: configuration file
  • .contractual/: state directory that tracks versions, snapshots, and pending changesets

Open contractual.yaml to view the generated configuration:

# yaml-language-server: $schema=https://contractual.dev/schema.json
contracts:
- name: order-schema
type: json-schema
path: schemas/order.schema.json

Validate the contract against the JSON Schema meta-schema.

Terminal window
contractual lint

Expected output:

Linting contracts...
✓ order-schema schemas/order.schema.json
All contracts passed lint.

If the schema has structural problems such as unknown keywords, invalid types, or malformed $ref, lint reports them here before any diff runs.

Edit schemas/order.schema.json to change the type of the amount field from string to number. This simulates an accidental breaking change that Contractual is designed to catch.

Find this section:

"amount": {
"type": "string",
"description": "Order total as a formatted string, e.g. '12.50'"
},

Change it to:

"amount": {
"type": "number",
"description": "Order total as a numeric value, e.g. 12.50"
},

Save the file.

Terminal window
contractual breaking

Expected output:

Checking for breaking changes...
✗ order-schema schemas/order.schema.json
BREAKING /properties/amount/type
Changed type: string → number
Consumers expecting a string value will fail to validate.
1 breaking change detected.
Exit code: 1

Contractual compares the current schema against the snapshot saved during init (the baseline). It classifies the type change from string to number as breaking because existing consumers that expect a string value now fail validation.

A changeset is a small markdown file that declares what changed and at what semver level. Contractual auto-generates one from its diff analysis.

Terminal window
contractual changeset --no-interactive

Expected output:

Generating changeset...
order-schema MAJOR (breaking change detected)
Created .contractual/changesets/lucky-tiger-runs.md
Review and edit the changeset before committing it.

Open .contractual/changesets/lucky-tiger-runs.md to view the generated changeset:

---
"order-schema": major
---
Changed type of `amount` field from `string` to `number`.
Consumers that pass string values for `amount` must be updated to pass numeric values instead.

The frontmatter declares the bump level (major) per contract. The body is the human-readable description that appears in the CHANGELOG. Both can be edited before committing.

Once the changeset is ready, run contractual version to consume it.

Terminal window
contractual version

Expected output:

Consuming changesets...
order-schema
0.0.0 → 1.0.0 (major bump from lucky-tiger-runs.md)
Updated .contractual/versions.json
Updated .contractual/snapshots/order-schema/1.0.0.json
Deleted .contractual/changesets/lucky-tiger-runs.md
Updated schemas/CHANGELOG.md
Done. Commit the changes and push.

Contractual has:

  1. Bumped order-schema from 0.0.0 to 1.0.0
  2. Saved a new snapshot of the schema at the new version
  3. Deleted the consumed changeset file
  4. Appended an entry to schemas/CHANGELOG.md

For OpenAPI, AsyncAPI, and ODCS contracts, contractual version also writes the new version number into the spec file itself (for example, info.version in an OpenAPI document). JSON Schema has no standard version field, so this step is skipped here.

Check schemas/CHANGELOG.md:

# Changelog
## 1.0.0 - 2026-02-17
### Breaking Changes
- Changed type of `amount` field from `string` to `number`.
Consumers that pass string values for `amount` must be updated to pass numeric values instead.

That is the full CLI workflow. The result is a versioned contract with an auditable history.