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

# Validate

[](#)[Source code](https://github.com/elbwalker/walkerOS/tree/main/packages/transformers/validate)[ Package](https://www.npmjs.com/package/@walkeros/transformer-validate)

Checks events against JSON Schema [contracts](/preview/pr-749/docs/getting-started/flow/contract.md) and records a verdict. In `pass` mode it annotates the event and continues so a downstream step can route on the result. In `strict` mode it drops invalid events by stopping the chain. The transformer runs on both web and server.

## Installation[​](#installation "Direct link to Installation")

```bash
npm install @walkeros/transformer-validate
```

* Integrated
* Bundled

```typescript
import { startFlow } from '@walkeros/collector';
import { transformerValidate } from '@walkeros/transformer-validate';

await startFlow({
  transformers: {
    validate: {
      code: transformerValidate,
      config: { settings: { contract: [contractWeb], mode: 'strict' } },
    },
  },
});
```

```json
"transformers": {
  "validate": {
    "package": "@walkeros/transformer-validate",
    "config": { "settings": { "contract": ["$contract.web"], "mode": "strict" } },
    "next": "ga4"
  }
}
```

## How it works[​](#how-it-works "Direct link to How it works")

The transformer reads the `{ ingest, event }` context and validates the canonical event. Each `contract` entry is a constraint: a `$contract.<name>` reference resolves to the entity-action schemas for the event, an inline JSON Schema applies to the whole event. All entries are AND-ed and every error is aggregated. Set `format: true` to additionally check the canonical partial event shape (field types and structure): no unknown top-level fields and no unknown keys on a nested entity; `user`, `source`, `data`, `globals`, `custom`, `context` and `consent` accept extra keys of their value type. Required-field rules come from `contract`.

`mode` decides what happens to an invalid event:

* **`pass`** (default): write the verdict to the event and continue. A downstream destination or transformer routes on `event.source.valid`.
* **`strict`**: record the errors, then stop the chain so the event never reaches downstream steps.

The verdict and the error list are written to two different places. The boolean verdict goes onto the **event** (`output.isValid`, default `source.valid`) as analytics-grade data that travels with the event. The issue list goes onto the **ingest** (`output.errors`, default `validation`) as observer-visible diagnostics, never event data, so it survives even a strict-mode drop. Set either path to an empty string to skip that write.

The transformer never throws. A schema the engine cannot compile or apply (for example an unresolvable `$ref`) is reported as an issue with `level: "error"`, so `pass` still forwards the event and `strict` drops it like an invalid one. A field whose value is `undefined` counts as absent, as in JSON.

## Configuration[​](#configuration "Direct link to Configuration")

This transformer uses the standard transformer config wrapper (consent, data, env, id, ...). For the shared fields see [transformer configuration](/docs/transformers#configuration). Package-specific fields live under `config.settings` and are listed below.

## Settings[​](#settings "Direct link to Settings")

| Property   | Type                 | Description                                                                                                                                                                                                                                                                                                                                                              | More |
| ---------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- |
| `contract` | `Array<object>`      | Validation constraints. Each entry is a resolved $contract.\* rule (with entity-action \`events\` schemas and/or a full-event \`schema\`) or an inline whole-event JSON Schema. All entries are AND-ed; every error is aggregated.                                                                                                                                       |      |
| `format`   | `boolean`            | When true, also validate the canonical WalkerOS.Event structural shape (correct field types; no unknown top-level fields and no unknown keys on a nested entity; \`user\`, \`source\`, \`data\`, \`globals\`, \`custom\`, \`context\` and \`consent\` accept extra keys of their value type). All fields are optional, so this checks structure and types, not presence. |      |
| `mode`     | `'strict' \| 'pass'` | \`strict\` drops invalid events (chain-stop) after recording errors; \`pass\` annotates and continues. Default \`pass\`.                                                                                                                                                                                                                                                 |      |
| `output`   | `output`             | Where the verdict (on the event) and the issue list (on the ingest) are written.                                                                                                                                                                                                                                                                                         |      |
| `isValid`  | `string`             | Event dot-path for the boolean verdict. Default \`source.valid\`. Empty string = skip.                                                                                                                                                                                                                                                                                   |      |
| `errors`   | `string`             | Ingest dot-path for the issue list. Default \`validation\`. Empty string = skip.                                                                                                                                                                                                                                                                                         |      |

## Mapping[​](#mapping "Direct link to Mapping")

This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see [mapping](/docs/mapping).

## Examples

### Format check accepts custom user keys

With format:true, extra keys on user (segment, ltv) are part of the event model and pass. The verdict source.valid:true is written to the event.

Event

```json
{
  "name": "page view",
  "entity": "page",
  "action": "view",
  "data": {
    "title": "Home"
  },
  "user": {
    "id": "u1",
    "segment": "vip",
    "ltv": 5
  }
}
```

Out

```javascript
return {
  "event": {
    "name": "page view",
    "entity": "page",
    "action": "view",
    "data": {
      "title": "Home"
    },
    "user": {
      "id": "u1",
      "segment": "vip",
      "ltv": 5
    },
    "source": {
      "valid": true
    }
  }
}
```

### Filter gtm.\* via a contract pattern (real event passes)

The same ^gtm\\. rejection contract leaves a real "page view" untouched: it passes and is annotated source.valid:true.

Event

```json
{
  "name": "page view",
  "entity": "page",
  "action": "view"
}
```

Out

```javascript
return {
  "event": {
    "name": "page view",
    "entity": "page",
    "action": "view",
    "source": {
      "valid": true
    }
  }
}
```

### Strict validate against a contract (valid)

A "page view" with the required data.title passes the inline contract. The verdict source.valid:true is written to the event; the chain continues.

Event

```json
{
  "name": "page view",
  "entity": "page",
  "action": "view",
  "data": {
    "title": "Home"
  }
}
```

Out

```javascript
return {
  "event": {
    "name": "page view",
    "entity": "page",
    "action": "view",
    "data": {
      "title": "Home"
    },
    "source": {
      "valid": true
    }
  }
}
```

## Reference a contract[​](#reference-a-contract "Direct link to Reference a contract")

The canonical place for event shapes is the top-level `contract` block. Reference it with `$contract.<name>`; the bundler resolves the reference to a concrete schema before deploy, so the runtime transformer only ever sees resolved schemas.

In [`flow-complete.json`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.json) the server flow's `validate` step checks every enriched event against the `server` contract, which extends `default` with the visitor hash:

`/flows/server/transformers/validate`

```json
{
  "package": "@walkeros/transformer-validate",
  "config": {
    "settings": {
      "contract": [
        "$contract.server"
      ],
      "mode": "pass"
    }
  },
  "examples": { … }
}
```

validate checks each event against $contract.server at runtime. [Guide chapter `contract`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#contract-the-contract-and-the-validate-step)

The order has everything the server contract asks for, so source.valid is true and the event continues.

`Event`

```json
{
  "name": "order complete",
  "entity": "order",
  "action": "complete",
  "data": {
    "id": "ORD-100",
    "total": 149.8,
    "currency": "EUR",
    "coupon": "WELCOME10"
  },
  "id": "ev-order-complete",
  "trigger": "load",
  "timestamp": 1700000000000,
  "consent": {
    "functional": true,
    "marketing": true
  },
  "globals": {
    "language": "en",
    "pageGroup": "checkout"
  },
  "user": {
    "device": "d3v1c3",
    "session": "s3ss10n",
    "id": "cust-42",
    "email": "jane@example.com",
    "hash": "5f1c0a9e7d3b2c41",
    "botScore": 0,
    "botCategory": "human",
    "ltv": 420
  },
  "source": {
    "type": "express",
    "platform": "server",
    "url": "https://www.example.com/checkout/thanks"
  },
  "nested": [
    {
      "entity": "product",
      "data": {
        "id": "SKU-1",
        "name": "Trail Runner",
        "price": 129.9,
        "quantity": 1
      }
    },
    {
      "entity": "product",
      "data": {
        "id": "SKU-2",
        "name": "Running Socks",
        "price": 9.95,
        "quantity": 2
      }
    }
  ]
}
```

`Out`

```javascript
return {
  "event": {
    "name": "order complete",
    "entity": "order",
    "action": "complete",
    "data": {
      "id": "ORD-100",
      "total": 149.8,
      "currency": "EUR",
      "coupon": "WELCOME10"
    },
    "id": "ev-order-complete",
    "trigger": "load",
    "timestamp": 1700000000000,
    "consent": {
      "functional": true,
      "marketing": true
    },
    "globals": {
      "language": "en",
      "pageGroup": "checkout"
    },
    "user": {
      "device": "d3v1c3",
      "session": "s3ss10n",
      "id": "cust-42",
      "email": "jane@example.com",
      "hash": "5f1c0a9e7d3b2c41",
      "botScore": 0,
      "botCategory": "human",
      "ltv": 420
    },
    "source": {
      "type": "express",
      "platform": "server",
      "url": "https://www.example.com/checkout/thanks",
      "valid": true
    },
    "nested": [
      {
        "entity": "product",
        "data": {
          "id": "SKU-1",
          "name": "Trail Runner",
          "price": 129.9,
          "quantity": 1
        }
      },
      {
        "entity": "product",
        "data": {
          "id": "SKU-2",
          "name": "Running Socks",
          "price": 9.95,
          "quantity": 2
        }
      }
    ]
  }
}
```

Order matches the server contract: example `validOrder` of step `validate`. [Guide chapter `contract`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#contract-the-contract-and-the-validate-step)

It runs in `pass` mode, so an invalid event keeps flowing with `source.valid: false` and the warehouse keeps the violation:

`/flows/server/transformers/validate/config/settings/mode`: `"pass"`. mode pass marks source.valid instead of dropping, so the warehouse keeps violations; strict is the hard gate. [Guide chapter `contract`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#contract-the-contract-and-the-validate-step)

Without user.hash the event breaks the server contract. mode pass keeps it for the warehouse and writes source.valid false; eventFilter keeps it from vendors.

`Event`

```json
{
  "name": "order complete",
  "entity": "order",
  "action": "complete",
  "data": {
    "id": "ORD-100",
    "total": 149.8,
    "currency": "EUR",
    "coupon": "WELCOME10"
  },
  "id": "ev-order-complete",
  "trigger": "load",
  "timestamp": 1700000000000,
  "consent": {
    "functional": true,
    "marketing": true
  },
  "globals": {
    "language": "en",
    "pageGroup": "checkout"
  },
  "user": {
    "device": "d3v1c3",
    "session": "s3ss10n",
    "id": "cust-42",
    "email": "jane@example.com",
    "botScore": 0,
    "botCategory": "human"
  },
  "source": {
    "type": "express",
    "platform": "server",
    "url": "https://www.example.com/checkout/thanks"
  },
  "nested": [
    {
      "entity": "product",
      "data": {
        "id": "SKU-1",
        "name": "Trail Runner",
        "price": 129.9,
        "quantity": 1
      }
    },
    {
      "entity": "product",
      "data": {
        "id": "SKU-2",
        "name": "Running Socks",
        "price": 9.95,
        "quantity": 2
      }
    }
  ]
}
```

`Out`

```javascript
return {
  "event": {
    "name": "order complete",
    "entity": "order",
    "action": "complete",
    "data": {
      "id": "ORD-100",
      "total": 149.8,
      "currency": "EUR",
      "coupon": "WELCOME10"
    },
    "id": "ev-order-complete",
    "trigger": "load",
    "timestamp": 1700000000000,
    "consent": {
      "functional": true,
      "marketing": true
    },
    "globals": {
      "language": "en",
      "pageGroup": "checkout"
    },
    "user": {
      "device": "d3v1c3",
      "session": "s3ss10n",
      "id": "cust-42",
      "email": "jane@example.com",
      "botScore": 0,
      "botCategory": "human"
    },
    "source": {
      "type": "express",
      "platform": "server",
      "url": "https://www.example.com/checkout/thanks",
      "valid": false
    },
    "nested": [
      {
        "entity": "product",
        "data": {
          "id": "SKU-1",
          "name": "Trail Runner",
          "price": 129.9,
          "quantity": 1
        }
      },
      {
        "entity": "product",
        "data": {
          "id": "SKU-2",
          "name": "Running Socks",
          "price": 9.95,
          "quantity": 2
        }
      }
    ]
  }
}
```

Order without visitor id: example `missingHash` of step `validate`. [Guide chapter `contract`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#contract-the-contract-and-the-validate-step)

The vendors only get valid events: their shared `eventFilter` step stops an event whose verdict is `false`. Use `strict` instead when invalid events should reach no destination at all.

`/flows/server/transformers/eventFilter/next/match/or/2`

```json
{
  "key": "event.source.valid",
  "operator": "eq",
  "value": "false"
}
```

Only events that match the contract reach the vendors. [Guide chapter `contract`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#contract-the-contract-and-the-validate-step)

source.valid false (contract broken) is stopped for vendors; the warehouse keeps it.

`Event`

```json
{
  "name": "order complete",
  "entity": "order",
  "action": "complete",
  "data": {
    "id": "ORD-100",
    "total": 149.8,
    "currency": "EUR",
    "coupon": "WELCOME10"
  },
  "id": "ev-order-complete",
  "trigger": "load",
  "timestamp": 1700000000000,
  "consent": {
    "functional": true,
    "marketing": true
  },
  "globals": {
    "language": "en",
    "pageGroup": "checkout"
  },
  "user": {
    "device": "d3v1c3",
    "session": "s3ss10n",
    "id": "cust-42",
    "email": "jane@example.com",
    "hash": "5f1c0a9e7d3b2c41",
    "botScore": 0,
    "botCategory": "human",
    "ltv": 420
  },
  "source": {
    "type": "express",
    "platform": "server",
    "url": "https://www.example.com/checkout/thanks",
    "valid": false
  },
  "nested": [
    {
      "entity": "product",
      "data": {
        "id": "SKU-1",
        "name": "Trail Runner",
        "price": 129.9,
        "quantity": 1
      }
    },
    {
      "entity": "product",
      "data": {
        "id": "SKU-2",
        "name": "Running Socks",
        "price": 9.95,
        "quantity": 2
      }
    }
  ]
}
```

`Out`

```javascript
return false
```

Invalid event stopped: example `invalidStopped` of step `eventFilter`. [Guide chapter `contract`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#contract-the-contract-and-the-validate-step)

Add `format: true` to also check the canonical event shape, and `output` to move the verdict (`isValid`, default `source.valid`) or the issue list (`errors`, default `validation`).

## Filter unwanted events[​](#filter-unwanted-events "Direct link to Filter unwanted events")

There is no separate `ignore` or `filter` setting. To drop unwanted events (for example GTM lifecycle pings like `gtm.js` / `gtm.dom`), author an inline schema that rejects them and run `mode: "strict"`. A schema where `name` must NOT match `^gtm\.` fails those events while real events pass.

```json
"validate": {
  "package": "@walkeros/transformer-validate",
  "config": { "settings": {
    "contract": [{ "type": "object", "properties": { "name": { "not": { "pattern": "^gtm\\." } } } }],
    "mode": "strict"
  } }
}
```

## Limitations[​](#limitations "Direct link to Limitations")

* The error list may contain an extra parent entry pointing at a `properties` wrapper per failure, a quirk of the underlying JSON Schema engine. The `isValid` verdict is unaffected: it is `false` exactly when there is at least one failure.
* Issues are emitted at `level: "error"`; there is no warn level.
* `flow_simulate` does not run a source's `policy` and `next` chain, so a green simulation does not prove a validate step placed there.

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

* **[Contract](/preview/pr-749/docs/getting-started/flow/contract.md)** - named, inheritable event schemas referenced via `$contract`
* **[Create your own](/preview/pr-749/docs/transformers/create-your-own.md)** - build custom transformers
