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

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.