Validate
A contract is the canonical definition of what your events should look like. Validation enforces that contract at runtime with an explicit transformer step, @walkeros/transformer-validate, the same way enrichment or decoding is a transformer step. The contract describes the shape; the transformer applies it.
Where event shapes live
Event shapes live in the top-level contract block of flow.json (a sibling of flows), keyed by entity then action as JSON Schema, with extend inheritance and * wildcards. Reference any contract from a step with $contract.<name>.
{
"version": 4,
"contract": {
"web": { "events": { "order": { "complete": { "properties": { "data": { "required": ["total", "currency"] } } } } } }
}
}
See Contract for the full shape, inheritance with extend, and wildcard merging.
Enforce at runtime
Add a @walkeros/transformer-validate step and point its contract setting at the contract. The transformer reads the { ingest, event } context and validates the canonical event.
{
"version": 4,
"contract": {
"web": { "events": { "order": { "complete": { "properties": { "data": { "required": ["total", "currency"] } } } } } }
},
"flows": {
"default": {
"transformers": {
"validate": {
"package": "@walkeros/transformer-validate",
"config": { "settings": {
"contract": ["$contract.web"],
"format": true,
"mode": "strict",
"output": { "isValid": "source.valid", "errors": "validation" }
} },
"next": "ga4"
}
}
}
}
}Settings
| Field | Type | Description |
|---|---|---|
contract? | Array<$contract ref | JSON Schema> | Constraints. A $contract.<name> ref selects per entity.action; an inline schema applies to the whole event. All entries must pass (AND). |
format? | boolean | Also validate the canonical WalkerOS.Event structure. All fields are optional, so this checks shape and field types, not presence. |
mode? | 'strict' | 'pass' | strict drops invalid events (stops the chain); pass (default) annotates and continues so a downstream route can act. |
output? | { isValid?, errors? } | Split targets. isValid (default source.valid) writes the boolean verdict onto the event; errors (default validation) writes the issue list onto the ingest context. Empty string skips a write. |
The verdict travels with the event as analytics-grade data (event.source.valid). The issue list ({ path, message } entries) stays off the event, on the observer-visible ingest context, so it survives even a strict-mode drop.
Filter unwanted events
Filtering is the same mechanism, not a separate setting. Author an inline schema that rejects the unwanted events and run mode: "strict". For example, drop GTM lifecycle noise (gtm.js, gtm.dom) by rejecting any name matching ^gtm\.:
"validate": {
"package": "@walkeros/transformer-validate",
"config": { "settings": {
"contract": [{ "type": "object", "properties": { "name": { "not": { "pattern": "^gtm\\." } } } }],
"mode": "strict"
} }
}Design-time checks
The CLI flow validator checks step examples (destination.in, transformer.in) against the resolved contract and reports violations before you deploy, so a sample event that breaks the contract is caught at authoring time.
walkeros validate flow.jsonBreak it on purpose
Contracts are easiest to trust after watching one fail. Take the contract above (it requires total and currency on order complete data) and give a step example an event that misses currency. The validator names the exact path and problem:
Validation Results:
⚠ destination.ga4.examples.purchase.in: Example violates contract: #/data: Instance does not have required property "currency".
→ Fix the example data to satisfy the contract schema.
Summary: 0 error(s), 1 warning(s)Contract violations are warnings by default; add --strict to turn them into errors (exit code 1) and fail CI before a broken event shape ships. A tag manager drops the same broken field silently, in production.
Next steps
- Contract: named, inheritable schemas referenced via
$contract - Validate transformer: full settings reference and recipes
- Mapping: transform events between steps