Step examples
Add examples to any step in your flow config to document what it expects and produces. On a GA4 destination (@walkeros/web-destination-gtag with measurement ID G-DEMO123456):
"examples": {
"purchase": {
"in": { "name": "order complete", "data": { "id": "ORD-123", "total": 149.97 } },
"mapping": { "name": "purchase", "data": { "map": { "transaction_id": "data.id", "value": "data.total" } } },
"out": [["gtag", "event", "purchase", { "transaction_id": "ORD-123", "value": 149.97, "send_to": "G-DEMO123456" }]]
}
}
Examples are test fixtures, documentation, and simulation data in one place. They live in your flow config, get stripped at build time, and have zero runtime impact.
Examples are mainly internal: packages use them for tests, docs and demos. In your own flow, use them sparingly, where one shows something the package examples cannot, for example a client flow's source with an example of its default behaviour. Mark such a fixture "public": false to keep it out of docs, UI and MCP default output while tests and --simulate still use it.
What are step examples
Every source, transformer, and destination in a walkerOS flow is a step. Each step receives input and produces output. Step examples are named { in, out } pairs that capture concrete data for each step: what goes in, what comes out.
They serve multiple purposes simultaneously:
- Documentation: developers see real data shapes, not just type signatures
- Testing: your tests use examples as fixtures, and
walkeros validatechecks that connected steps have compatible examples - Simulation: debug your pipeline before deployment
- LLM context: AI tools use examples to suggest correct mappings
The { in, out } format
Each example has a human-readable name and two fields:
{
"examples": {
"page view": {
"in": {
"name": "page view",
"data": { "title": "Home", "path": "/" }
},
"out": [["gtag", "event", "page_view", { "send_to": "G-DEMO123456" }]]
},
"purchase": {
"in": {
"name": "order complete",
"data": { "id": "ORD-123", "total": 149.97, "currency": "EUR" }
},
"mapping": {
"name": "purchase",
"data": {
"map": {
"transaction_id": "data.id",
"value": "data.total",
"currency": "data.currency"
}
}
},
"out": [["gtag", "event", "purchase", {
"transaction_id": "ORD-123",
"value": 149.97,
"currency": "EUR",
"send_to": "G-DEMO123456"
}]]
}
}
}| Field | Type | Description |
|---|---|---|
in | any | The data the step receives |
mapping | object | The mapping rule applied to this event (destinations only) |
out | array | The effects the step produces, in order. Each entry is a tuple [callable, ...args]: a vendor call such as ["gtag", ...] for a destination, ["elb", event] for a source, ["return", value] for a transformer. [] means no observable effect |
out: [["return", false]] | array | The transformer rejects or filters this event |
trigger | object | Sources only: how the example fires the source (type, such as load, click or POST, and options). This is simulation and test input, not the event's trigger field, which is a string |
command | string | Route in through a walker command instead of pushing it as an event (config, consent, user, run) |
title, description | string | Labels shown in docs and MCP output |
public | boolean | false hides a test-only example from docs, UI and default MCP output (default true) |
For destinations, the optional mapping field captures the mapping rule that transforms the walkerOS event into the vendor-specific output. This ties the example to a specific mapping configuration, so tests and simulations can register it dynamically as { [entity]: { [action]: example.mapping } }.
Command examples
Some destinations need to react to walker commands (consent updates, user identification, run state) rather than events. Set the command field on a step example so the test runner invokes elb('walker <command>', in) instead of pushing in as a regular event:
{
"command": "consent",
"in": { "marketing": true, "functional": true },
"out": [
["gtag", "consent", "default", { "ad_storage": "denied", "ad_user_data": "denied", "ad_personalization": "denied", "analytics_storage": "denied" }],
["gtag", "consent", "update", { "ad_storage": "granted", "ad_user_data": "granted", "ad_personalization": "granted", "analytics_storage": "granted" }]
]
}Supported commands: config, consent, user, run. When command is set, mapping is ignored, because commands don't flow through event mapping. See the walkeros-using-step-examples skill for the full test-runner pattern.
You can add as many named examples as you need. Tests refer to each example by its name, and the MCP flow_examples tool lists them by name for each step.
Three type zones
A walkerOS flow has three distinct type zones. The shape of in and out depends on where the step sits:
ARBITRARY walkerOS.Event ARBITRARY
┌─────────┐ ┌──────────────────────┐ ┌──────────────┐
│ Source │ → │ Collector → Transform │ → │ Destination │
│ ingest │ │ → ... → Transform │ │ output │
└─────────┘ └──────────────────────┘ └──────────────┘
package- known structure, package-
specific varying properties specific
- Source
in: raw HTTP request body, DOM event, dataLayer push (package-specific) - Source
out: theelbcall that pushes thewalkerOS.Event, as[["elb", event]] - Transformer
inand destinationin:walkerOS.Event(known structure, varying properties) - Transformer
out:[["return", { "event": event }]], or[["return", false]]when the transformer drops the event - Destination
out: the vendor calls, such asgtag()orfbq()arguments or an HTTP request (vendor-specific)
Adding examples to a flow
Here is a complete flow config with examples on a source, transformer, and destination:
{
"version": 4,
"flows": {
"default": {
"config": { "platform": "web" },
"sources": {
"browser": {
"package": "@walkeros/web-source-browser",
"examples": {
"page view": {
"trigger": { "type": "load", "options": { "url": "https://example.com/", "title": "Home" } },
"in": "",
"out": [["elb", {
"name": "page view",
"data": { "domain": "example.com", "title": "Home", "referrer": "", "id": "/" },
"context": {},
"globals": {},
"trigger": "load",
"source": { "type": "browser", "platform": "web", "url": "https://example.com/", "referrer": "" }
}]]
}
}
}
},
"transformers": {
"validate": {
"package": "@walkeros/transformer-validate",
"config": {
"settings": { "contract": [{ "type": "object", "properties": { "data": { "required": ["title"] } } }] }
},
"examples": {
"page view": {
"in": { "name": "page view", "entity": "page", "action": "view", "data": { "title": "Home" } },
"out": [["return", { "event": {
"name": "page view", "entity": "page", "action": "view",
"data": { "title": "Home" },
"source": { "valid": true }
} }]]
}
}
}
},
"destinations": {
"ga4": {
"package": "@walkeros/web-destination-gtag",
"config": {
"settings": { "ga4": { "measurementId": "G-DEMO123456" } }
},
"before": "validate",
"examples": {
"page view": {
"in": { "name": "page view", "data": { "title": "Home" } },
"mapping": {
"name": "page_view",
"data": { "map": { "page_title": "data.title" } }
},
"out": [["gtag", "event", "page_view", { "page_title": "Home", "send_to": "G-DEMO123456" }]]
}
}
}
}
}
}
}The ga4 destination runs validate in its before chain, so cross-step validation checks the transformer's out against the destination's in.
Examples are stripped by the bundler at build time. They never appear in your production bundle.
Using examples with the CLI
Simulate a step with an example's input
Push an event through one step with its external calls mocked. --event takes the input itself, so pass an example's in value:
# Push the "page view" example's in value through the ga4 destination
walkeros push flow.json --simulate destination.ga4 --event '{"name":"page view","data":{"title":"Home"}}'The CLI runs the event through the flow with the step mocked and prints what the step did: for a destination, the matched mapping rule and every recorded vendor call. See simulation output. It does not compare anything against the step's out and exits with code 1 only when the push fails with an error, so a mapping that produces the wrong output still exits 0. To assert on out, run the examples in your own tests (see Using examples in tests).
Validate examples
walkeros validate checks examples statically. It does not run any step:
walkeros validate flow.jsonFor steps connected through next (source to transformer, transformer to transformer) or a destination's before chain, it checks that at least one out of the upstream step is structurally compatible with an in of the downstream step. Both values must have the same type. Two objects are compatible when they share at least half of the keys of the object with fewer keys; any two arrays are compatible regardless of their content. This is a shape check, not an equality check. An out written as effects is read for the events it passes on: [["elb", event]] and [["return", { "event": ... }]] pass the event, [["return", [...]]] passes each item, and ["return", false], message.ack, response or vendor calls pass nothing.
- No compatible pair is an error.
- A connection is only checked when both steps declare
examples. If either step has noexampleskey, nothing is checked and no warning is produced. - When both steps declare
examplesbut one side has no usable value, the connection produces a warning. An upstreamoutcounts when it passes on at least one event; empty values such asout: []are skipped. A downstreamincounts unless its example setscommand. - Other chains, such as a source's or transformer's
before, a destination'snextorcollector.next, and steps without these fields are not checked. - Each
@walkeros/transformer-validatestep's own examples agree with its linked contract and settings (see Validate); examples of other steps are not checked against contracts.
Using examples in tests
Extract examples from your flow config and use them as test fixtures:
import { readFileSync } from 'fs';
import { startFlow } from '@walkeros/collector';
import { destinationGtag } from '@walkeros/web-destination-gtag';
const flow = JSON.parse(readFileSync('flow.json', 'utf-8'));
const ga4 = flow.flows.default.destinations.ga4;
const examples: Record<string, { in: { name: string }; mapping?: unknown; out: unknown[][] }> = ga4.examples;
it.each(Object.entries(examples))('ga4 destination: %s', async (name, example) => {
// Record every gtag() call instead of loading the real gtag.js
const calls: unknown[][] = [];
const env = { window: { dataLayer: [], gtag: (...args: unknown[]) => calls.push(['gtag', ...args]) } };
const [entity, action] = example.in.name.split(' ');
const { elb } = await startFlow({
destinations: { ga4: { code: destinationGtag, config: { ...ga4.config, mapping: { [entity]: { [action]: example.mapping } } }, env } },
});
await elb(example.in);
// calls[0] is the gtag('config', ...) call the destination makes on init
expect(calls.slice(1)).toEqual(example.out);
});This pattern means your flow config is the single source of truth for both documentation and tests.
Package-level vs flow-level examples
Examples exist at two levels:
| Level | Where | Purpose |
|---|---|---|
| Package | walkerOS.json in the npm package (via dev.ts) | Generic examples showing what the package handles |
| Flow | Inline on step references in flow.json | Specific examples for your actual data and mappings |
Package examples are generic and ship with the npm package. They show the package's capabilities with sample data. The MCP tools package_get and flow_examples read them from the package's walkerOS.json on the CDN.
Flow examples are specific to your setup. They use your real event names, your actual data properties, and your configured mappings. In the MCP flow_examples tool, a step's flow examples take precedence over its package's examples; a step without flow examples falls back to the package's examples.
How packages ship examples
Packages export examples through the dev.ts convention:
// src/dev.ts
export * as schemas from './schemas';
export * as examples from './examples';These are included in the package's walkerOS.json metadata and discoverable via CDN, following the same { in, out } format used in flow configs.
A package with several exports (for example destinationBigQuery and destinationPubSub in @walkeros/server-destination-gcp) also exports exportExamples, a map from every export name, the default included, to that export's examples. The CLI, MCP and walkerOS.json pick the entry for the step's import:
// src/dev.ts
export const exportExamples = {
destinationBigQuery: bigqueryExamples,
destinationPubSub: pubsubExamples,
};Such a package also exports exportSchemas, the same map for schemas: every export name, the default included, to that export's settings (and mapping or setup where it has them). walkeros validate and MCP package_get use the entry for the step's import. The build fails when either map does not list exactly the exports in package.json walkerOS.exports, or an exportSchemas entry has no settings:
// src/dev.ts
export const exportSchemas = {
destinationBigQuery: { settings: bigquery.settings, mapping: bigquery.mapping },
destinationPubSub: { settings: pubsub.settings, mapping: pubsub.mapping, setup: pubsub.setup },
};In the complete example
Every step of flow-complete.json carries examples. The web GA4 destination's set:
{
"purchase": { … },
"purchaseWithoutCoupon": { … },
"productView": { … },
"consentUpdate": { … }
}step-envelopeNext steps
- CLI: Bundle and simulate flows with examples
- Flow: Learn the full flow configuration format
- Mapping: Transform events between steps
- Event model: Understand the walkerOS event structure