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). Each named contract holds JSON Schemas under events, keyed by entity then action, 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, point its contract setting at the contract, and reference the step from a destination's before, so it validates the full event after the collector. A $contract reference picks its per-event rules by the event's entity and action. The collector derives both from name, so in a source's next chain (before the collector) per-event rules only match when the source already sets entity and action.
{
"version": 4,
"contract": {
"web": { "events": { "order": { "complete": { "properties": { "data": { "required": ["total", "currency"] } } } } } }
},
"flows": {
"default": {
"config": { "platform": "web" },
"transformers": {
"validate": {
"package": "@walkeros/transformer-validate",
"config": { "settings": {
"contract": ["$contract.web"],
"format": true,
"mode": "strict",
"output": { "isValid": "source.valid", "errors": "validation" }
} }
}
},
"destinations": {
"ga4": {
"package": "@walkeros/web-destination-gtag",
"config": { "settings": { "ga4": { "measurementId": "G-DEMO123456" } } },
"before": "validate"
}
}
}
}
}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. A top-level key that is not part of the event model fails the check. |
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, level } 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\.. Reference this step from the source's next, so it runs before the collector:
"validate": {
"package": "@walkeros/transformer-validate",
"config": { "settings": {
"contract": [{ "type": "object", "properties": { "name": { "not": { "pattern": "^gtm\\." } } } }],
"mode": "strict"
} }
}Design-time checks
walkeros validate checks contracts the way the runtime applies them. A contract binds only where a @walkeros/transformer-validate step links it, so the validator runs each validate step's own examples through that step's resolved contract, with that step's mode, format and output settings, and checks that each example's out agrees with the verdict: a valid event goes on, an invalid one is dropped in strict mode or goes on with its isValid flag set to false in pass mode.
walkeros validate flow.jsonThe top-level contract block is the reference for setting up mappings, $contract references and editor IntelliSense, but it binds nothing by itself. Examples of other steps are never checked against it, and a flow without a validate step gets no contract checks.
Break 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), run the validate step with mode: "strict", and give it an example whose in misses currency while its out still passes the event on. The validator names the exact path and what the step really does:
Validation Results:
⚠ flows.default.transformers.validate.examples.missingCurrency.out: Example out disagrees with the validate step: the event breaks the contract (#: Property "data" does not match schema.; #/data: Instance does not have required property "currency".), so with mode "strict" the step drops it (["return", false]).
→ Make the example out show what the validate step does with its settings.
✓ All checks passed
Summary: 0 error(s), 1 warning(s), 0 skippedA disagreement is a warning by default; add --strict to turn it into an error (exit code 1; any other warning under --strict exits with code 2) and fail CI before a broken example ships. Fix it by making the example out show the drop ([["return", false]]), or by completing the in event.
Next steps
- Contract: named, inheritable schemas referenced via
$contract - Validate transformer: full settings reference and recipes
- Mapping: transform events between steps