> Part of the walkerOS documentation. Project overview and full index: <https://www.walkeros.io/llms.txt>

# Contract

A contract is the schema for your event data: which fields are required, what types they have, what values are allowed. It lives alongside `flows` in flow\.json as a named, inheritable block that any flow can reference via `$contract.<name>.<path>`.

```
{

  "contract": {

    "default": {

      "tagging": 1,

      "schema": {

        "type": "object",

        "properties": {

          "globals": { "required": ["country"] }

        }

      },

      "events": {

        "product": {

          "*": { "properties": { "data": { "required": ["id", "name"] } } },

          "add": { "properties": { "data": { "required": ["quantity"] } } }

        }

      }

    }

  }

}
```

## Why use a contract[​](#why-use-a-contract "Direct link to Why use a contract")

A contract is a single, inheritable description of what your events should look like. The contract itself describes; enforcement is an explicit [`@walkeros/transformer-validate`](/preview/pr-720/docs/transformers/validate.md) step that references it. Tools and humans also read the contract for governance, documentation, and schema-driven workflows.

* **Single source of truth.** Define `product add` requirements once. Every flow that ships these events references the same definition.
* **Inheritance.** Layer additional rules on top with `extend` (for example, `web_loggedin` extend `web` extend `default`) rather than copying.
* **Versioned.** `tagging` tracks contract revisions alongside the events they govern.
* **Self-documenting.** Each schema is JSON Schema, so `description` and `examples` annotate fields the same way humans and tools read them.
* **Decoupled from enforcement.** The validate transformer enforces the contract wherever you place it in the pipeline.
* **Composable with the rest of the config.** Reference fragments anywhere via `$contract.<name>.<path>` (see [Reference syntax](/preview/pr-720/docs/guides/reference-syntax.md#contract--contract-references)).

If your flow has a single throwaway event, you do not need a contract. Reach for one as soon as the same shape needs to hold across more than one flow.

## The shape[​](#the-shape "Direct link to The shape")

`contract` is a top-level key on flow\.json (parallel to `flows`, not nested inside a flow). Each entry is a named contract. The full shape:

| Field          | Purpose                                             |
| -------------- | --------------------------------------------------- |
| `extend?`      | Inherit from another named contract                 |
| `tagging?`     | Integer revision marker                             |
| `description?` | Human-readable note                                 |
| `events?`      | Entity-action keyed JSON Schemas, applied per event |
| `schema?`      | A single JSON Schema applied to every event         |

```
{

  "version": 4,

  "contract": {

    "default": {

      "tagging": 1,

      "schema": {

        "type": "object",

        "properties": {

          "globals": {

            "required": ["country"],

            "properties": {

              "country": { "type": "string" }

            }

          },

          "consent": {

            "required": ["analytics"],

            "properties": {

              "analytics": { "type": "boolean", "const": true }

            }

          }

        }

      }

    },

    "web": {

      "extend": "default",

      "events": {

        "product": {

          "add": {

            "properties": {

              "data": { "required": ["id", "quantity"] }

            }

          }

        }

      }

    }

  }

}
```

### Schema[​](#schema "Direct link to Schema")

`schema` is a JSON Schema for the full event. Standard event field names (`globals`, `context`, `consent`, `user`, `custom`, `source`, `data`) live inside `schema.properties`. The schema runs on every event the contract governs, in addition to any per-event rules under `events`.

### Event schemas[​](#event-schemas "Direct link to Event schemas")

Inside `events`, entity-action keyed entries define JSON Schemas for a partial `WalkerOS.Event`. The `*` key matches anything (see [Wildcard inheritance](#wildcard-inheritance)).

```
"events": {

  "product": {

    "*": {

      "description": "A product in the catalog",

      "properties": {

        "data": {

          "type": "object",

          "required": ["id", "name"],

          "properties": {

            "id": { "type": "string", "description": "Product SKU" },

            "name": { "type": "string", "description": "Display name" }

          }

        }

      }

    },

    "add": {

      "description": "Product added to cart",

      "properties": {

        "data": {

          "type": "object",

          "required": ["quantity"],

          "properties": {

            "quantity": { "type": "integer", "minimum": 1 }

          }

        }

      }

    }

  }

}
```

## Inheritance with extend[​](#inheritance-with-extend "Direct link to Inheritance with extend")

Use `extend` to inherit from another named contract. Inheritance is additive:

* **`schema`** merges additively across the chain (deep merge of `properties`, union of `required`)
* **`events`** merge at the entity-action level
* **Scalars** (`tagging`, `description`): child overrides if set, otherwise inherited from parent
* **Chains supported**: `web_loggedin` extend `web` extend `default`
* **Circular references** are detected and throw

Extend chains are resolved first, then wildcards expand on the merged result. Schema merging follows the same additive rules as wildcard merging (see [Merge rules](#merge-rules)), so the same limitations on JSON Schema composition keywords apply.

```
"contract": {

  "default": {

    "tagging": 1,

    "schema": {

      "properties": {

        "globals": { "required": ["country"] }

      }

    }

  },

  "web": {

    "extend": "default",

    "events": {

      "product": {

        "add": { "properties": { "data": { "required": ["id", "quantity"] } } }

      }

    }

  },

  "web_loggedin": {

    "extend": "web",

    "schema": {

      "properties": {

        "user": {

          "required": ["id"],

          "properties": { "id": { "type": "string" } }

        }

      }

    }

  }

}
```

`web_loggedin` resolves to: `tagging: 1` (from `default`), `globals.country` required (from `default`), `events.product.add` (from `web`), and `user.id` required (added by `web_loggedin`), all within a single merged `schema`.

## Wildcard inheritance[​](#wildcard-inheritance "Direct link to Wildcard inheritance")

Contracts support four wildcard levels in `events`. For entity-action pairs listed under `events`, all matching levels merge **additively**:

| Level | Pattern             | Matches                               |
| ----- | ------------------- | ------------------------------------- |
| 1     | `*` → `*`           | All events (global rules)             |
| 2     | `*` → `action`      | A specific action across all entities |
| 3     | `entity` → `*`      | All actions of a specific entity      |
| 4     | `entity` → `action` | Exact match                           |

For `product add`, levels 1, 3, and 4 all apply and combine:

```
"events": {

  "*":       { "*":   { "properties": { "consent": { "required": ["analytics"] } } } },

  "product": { "*":   { "properties": { "data": { "required": ["id", "name"] } } },

               "add": { "properties": { "data": { "required": ["quantity"] } } } }

}



// Resolved "product add":

//   consent.analytics required   (from * -> *)

//   data.id, data.name required  (from product -> *)

//   data.quantity required       (from product -> add)
```

Wildcards expand into the entity-action pairs listed under `events`, and only there do all matching levels merge. An event whose pair is not listed gets a single entry instead, the first that exists of: `entity` → `*` (which already carries the `*` → `*` rules), `*` → `action` (without the `*` → `*` rules), `*` → `*`. In the example above, `product view` gets the `product` → `*` rules plus the `*` → `*` rules. List a pair explicitly when several levels must apply to it.

Contracts vs mapping wildcards

Contract wildcards use **additive merging** for pairs listed under `events`: all matching levels combine. Mapping wildcards use **fallback matching**: the first match wins.

This difference is intentional. Contracts express cumulative requirements (entity-level rules apply to every action); mappings select a single transformation target.

**Contract:** `product.*` rules and `product.add` rules both apply to `product add` **Mapping:** `product.add` matches first, so `product.*` is never checked

### Merge rules[​](#merge-rules "Direct link to Merge rules")

When multiple wildcard levels (or `extend` chains) match, the JSON Schemas merge with these rules:

| JSON Schema keyword                                                    | Merge behavior                       |
| ---------------------------------------------------------------------- | ------------------------------------ |
| `required`                                                             | Union (deduplicated)                 |
| `properties` and other object-valued keywords (`items`, `not`, ...)    | Deep merge                           |
| Scalar and array keywords (`minimum`, `pattern`, `enum`, `oneOf`, ...) | Child overrides parent               |
| Annotations (`description`, `examples`, `title`, `$comment`)           | Stripped from resolved event schemas |

`required` arrays are united and object values merge key by key. Every other array (`oneOf`, `enum`, `type` arrays, `allOf`) and every scalar is replaced by the child's value. Use `allOf` composition at the schema level if full JSON Schema composition is needed.

Annotations stay on the source contract for tooling (CLI hints, IDE descriptions). Resolved event schemas are stripped; the `schema` block keeps its annotations. Validators ignore annotations either way.

## `$contract` references[​](#contract-references "Direct link to contract-references")

Reference any part of a resolved contract with `$contract.<name>.<path>`. The contract is fully resolved (extend + wildcards) before path access, so the returned value is the merged shape, not the raw entry. A validate transformer references a contract via its `contract` setting:

```
"transformers": {

  "validate": {

    "package": "@walkeros/transformer-validate",

    "config": { "settings": { "contract": ["$contract.web"], "mode": "strict" } }

  }

}
```

### Deep paths[​](#deep-paths "Direct link to Deep paths")

Walk into the resolved shape with dot notation:

| Path                                      | Returns                                                                        |
| ----------------------------------------- | ------------------------------------------------------------------------------ |
| `$contract.web`                           | The fully resolved contract entry                                              |
| `$contract.web.schema`                    | The merged event-level JSON Schema                                             |
| `$contract.web.schema.properties.globals` | Just the resolved globals sub-schema                                           |
| `$contract.web.schema.properties.consent` | Just the resolved consent sub-schema                                           |
| `$contract.web.events`                    | All resolved event schemas for the `web` contract                              |
| `$contract.web.events.product.add`        | Resolved schema for `product add`, with all matching wildcard levels merged in |
| `$contract.web.tagging`                   | The contract revision number                                                   |

## CLI validation[​](#cli-validation "Direct link to CLI validation")

Validate contracts standalone or as part of a flow:

```
# Validate a contract file

walkeros validate contract.json --type contract



# Validate inline

echo '{"default":{"events":{"product":{"add":{"properties":{}}}}}}' | walkeros validate --type contract



# Validate a full flow (contract field types, extend references, example compliance)

walkeros validate flow.json
```

The contract validator checks:

* The root is an object of named contract entries; a flat entity-action map or a `$`-prefixed root key is rejected as the flat shape
* `tagging` is a number (if present)
* `extend` references exist and are not circular
* Entity and action keys are non-empty
* `schema`, if present, is a valid JSON Schema object
* Each event entry is a valid JSON Schema object
* A key outside `extend`, `tagging`, `description`, `events`, `schema` is a warning, not an error. Exception: top-level `globals`, `context`, `custom`, `user` and `consent` keys pass without a warning, but no validator reads them. Put these rules under `schema.properties`.

The flat-shape and unknown-key checks run only with `--type contract`. Validating a full flow ignores unknown keys inside a contract entry.

Advanced: shared schema fragments via `variables`

Top-level `variables` holds reusable values that any part of the config can pull in with `$var.<name>`. Whole-string references preserve native type; deep paths walk into the value:

```
{

  "variables": {

    "idSchema": {

      "required": ["id"],

      "properties": { "id": { "type": "string" } }

    }

  },

  "contract": {

    "web": {

      "events": {

        "product": {

          "*": { "properties": { "data": "$var.idSchema" } }

        }

      }

    }

  }

}
```

Use `variables` when the same JSON Schema fragment shows up in many events. For contract-shaped reuse across flows, prefer `extend`.

`$var` references in a contract resolve at bundle time, so the runtime validate step enforces them. The step-example check in `walkeros validate` reads the contract before `$var` resolution and does not apply rules pulled in with `$var`.

## Complete example[​](#complete-example "Direct link to Complete example")

A complete example flow with a contract (a full-event `schema` plus per-event rules) lives at [`packages/cli/examples/flow-complete.json`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.json).

A shorter illustration, with the validate step running before a destination:

```
{

  "version": 4,

  "contract": {

    "default": {

      "tagging": 1,

      "description": "Web shop tracking contract",

      "schema": {

        "type": "object",

        "properties": {

          "globals": {

            "required": ["country", "currency"],

            "properties": {

              "country":  { "type": "string", "pattern": "^[A-Z]{2}$" },

              "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }

            }

          },

          "consent": {

            "required": ["analytics"],

            "properties": {

              "analytics": { "type": "boolean", "const": true }

            }

          }

        }

      },

      "events": {

        "product": {

          "*":   { "properties": { "data": { "required": ["id", "name"] } } },

          "add": { "properties": { "data": { "required": ["quantity"], "properties": { "quantity": { "type": "integer", "minimum": 1 } } } } }

        },

        "order": {

          "complete": { "properties": { "data": { "required": ["total"], "properties": { "total": { "type": "number", "minimum": 0 } } } } }

        }

      }

    },

    "web_loggedin": {

      "extend": "default",

      "schema": {

        "properties": {

          "user": {

            "required": ["id", "email"],

            "properties": {

              "id":    { "type": "string" },

              "email": { "type": "string", "format": "email" }

            }

          }

        }

      }

    }

  },

  "flows": {

    "web-shop": {

      "config": { "platform": "web" },

      "transformers": {

        "validate": {

          "package": "@walkeros/transformer-validate",

          "config": { "settings": { "contract": ["$contract.web_loggedin"], "mode": "strict" } }

        }

      },

      "destinations": {

        "ga4": {

          "package": "@walkeros/web-destination-gtag",

          "config": { "settings": { "ga4": { "measurementId": "G-DEMO123456" } } },

          "before": "validate"

        }

      }

    }

  }

}
```

For `product add`, a validate transformer that references `$contract.web_loggedin` sees these rules (all from the merged shape):

| Source                                | Rule                                               |
| ------------------------------------- | -------------------------------------------------- |
| `default.schema.properties.globals`   | `country`, `currency` required, both match pattern |
| `default.schema.properties.consent`   | `analytics` required and `true`                    |
| `default.events.product.*`            | `data.id`, `data.name` required                    |
| `default.events.product.add`          | `data.quantity` required, integer, minimum 1       |
| `web_loggedin.schema.properties.user` | `user.id`, `user.email` required                   |

## Next steps[​](#next-steps "Direct link to Next steps")

* **[Validate](/preview/pr-720/docs/getting-started/flow/validate.md)**: enforce a contract at runtime with the validate transformer
* **[Mapping](/preview/pr-720/docs/mapping.md)**: transform events between steps
* **[Step examples](/preview/pr-720/docs/getting-started/flow/step-examples.md)**: pair every step with input/output fixtures
* **[Reference syntax](/preview/pr-720/docs/guides/reference-syntax.md)**: all `$contract`, `$var`, `$flow`, `$store`, `$secret`, `$code:`, `$env` references
