Skip to main content
Ask your AI

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​

FieldTypeDescription
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?booleanAlso 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.json

The 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 skipped

A 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​

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)