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

# Routing

Every chain field decides where an event goes next. This page is the reference for connections, chain resolution, route operators and `stop`. The examples come from [`flow-complete.json`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.json), the tested example flow, so each one is a real part of a working setup.

## Connection rules[​](#connection-rules "Direct link to Connection rules")

Sources, transformers, collectors, and destinations connect in specific ways. This table summarizes every valid connection:

| From        | To          | Field                              | Notes                                                               |
| ----------- | ----------- | ---------------------------------- | ------------------------------------------------------------------- |
| Source      | Transformer | `before` on source                 | Consent-exempt preprocessing                                        |
| Source      | Transformer | `next` on source                   | Source chain, runs before the collector                             |
| Source      | Collector   | omit `next`                        | Default: events go straight to collector                            |
| Transformer | Transformer | `before` on transformer            | Pre-transform enrichment                                            |
| Transformer | Transformer | `next` on transformer              | Chain continues to next transformer                                 |
| Collector   | Transformer | `next` on collector                | Collector chain, runs once per event before the destination fan-out |
| Collector   | Transformer | `before` on destination            | Destination chain, runs for that destination only                   |
| Collector   | Destination | omit `collector.next` and `before` | Default: events go straight to every destination                    |
| Destination | Transformer | `next` on destination              | Post-push processing                                                |

**Connections that are not allowed:**

* Source to source
* Source directly to destination (events must pass through the collector)
* Collector to source

## Chain resolution[​](#chain-resolution "Direct link to Chain resolution")

Every chain field (`source.before`, `source.next`, `transformer.before`, `transformer.next`, `collector.next`, `destination.before`, `destination.next`) accepts the same value and resolves it the same way:

| Value                               | Behavior                                                                                                     |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `"fingerprint"` (string)            | Runs `fingerprint`, then its own `next`, and so on until a step has no `next`                                |
| `["fingerprint", "enrich"]` (array) | Runs the array in order. A member's own `next` is inserted right after that member, then the array continues |

For example, `["bot", "validate", "session"]` where `bot` has `"next": "flag"` runs `bot`, `flag`, `validate`, `session`. Insertion is recursive: if `flag` has a `next` of its own, it runs right after `flag`. A string is the one-member case of the same rule.

The rules that apply everywhere:

1. **Resolved per hop**: each route is evaluated when the event reaches it, never ahead of time. Its `match` reads `{ ingest, event }` as the previous step left them, so a route can decide on a value an earlier step loaded, for example a `state` get that writes `ingest.tier`. A conditional `next` on a transformer sees what that transformer did.
2. **Repeated steps run as written**: a step listed twice runs twice.
3. **Cycles**: a transformer's own `next` that leads back to a step already on that insertion path is skipped. Each copy of an event is also limited to 256 steps.
4. **Unknown ids**: an id that names no transformer is logged as a warning and skipped, and the chain continues. `walkeros validate` reports it as an `UNKNOWN_ROUTE_TARGET` error.
5. **Forks and stops**: `many` splits the event into independent copies and `stop` ends one copy, see [Route operators](#route-operators) and [Dropping an event with `stop`](#dropping-an-event-with-stop).

## Where transformers run[​](#where-transformers-run "Direct link to Where transformers run")

All transformers live in a single `transformers` pool. Their position in the pipeline depends on which field references them:

```
[Source.before] → Source ──[next]──→ Source chain ──→ Collector ──[next]──→ Collector chain ──→ fan-out ──[before]──→ Destination chain ──→ Destination ──[next]──→ Post-push
```

The same transformer can appear in several chains. For example, you might use `fingerprint` in a source's `next` chain and again in a destination's `before` chain. Each invocation runs independently.

Use `collector.next` for work every destination needs, and `destination.before` for work only one destination needs. The collector chain runs once per event, after the collector completed it (globals, user, consent, ids) and before the event is handed to the destinations.

The server flow of the complete example uses every position. The source chain stops an opted-out user (see [Opt-out](https://www.walkeros.io/docs/guides/consent.md#opt-out)), then runs `dedup` before the collector:

`/flows/server/sources/express/next`

```json
[
  {
    "match": {
      "key": "event.user.optout",
      "operator": "eq",
      "value": "true"
    },
    "stop": true
  },
  "dedup"
]
```

source.next names the hops after the source: the opt-out stop, then dedup. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

The collector chain runs once per event, for every destination: `fingerprint` and `bot` always, `enrich` for everything but impressions.

`/flows/server/collector/next`

```json
[
  "fingerprint",
  "bot",
  {
    "match": {
      "key": "event.trigger",
      "operator": "regex",
      "value": "^(impression|visible)$",
      "not": true
    },
    "next": "enrich"
  }
]
```

collector.next runs once per event before the fan-out: fingerprint and bot for all, enrich for all but impressions. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

On the web, `collector.next` stops an opted-out user, then runs one inline step:

`/flows/web/collector/next`

```json
[
  {
    "match": {
      "key": "event.user.optout",
      "operator": "eq",
      "value": "true"
    },
    "stop": true
  },
  "pageGroup"
]
```

On the web collector.next stops an opted-out user first, then runs the inline pageGroup step once per event. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

The inline code step derives globals.pageGroup from the page path once per event, so every destination can group pages the same way.

`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"
  },
  "user": {
    "device": "d3v1c3",
    "session": "s3ss10n"
  },
  "source": {
    "type": "browser",
    "platform": "web",
    "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"
    },
    "source": {
      "type": "browser",
      "platform": "web",
      "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
        }
      }
    ]
  }
}
```

Checkout page group: example `checkout` of step `pageGroup`. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

A destination chain runs for one destination only. Meta, Piwik PRO and Data Manager each name `eventFilter`; Pub/Sub, which feeds the warehouse, does not:

`/flows/server/destinations/meta/before`: `"eventFilter"`. destination.before runs for one destination only; a stop there skips just that one. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

A destination chain can be a route too. On Pub/Sub, `pseudonymize` runs only when an email exists:

`/flows/server/destinations/pubsub/before`

```json
{
  "match": {
    "key": "event.user.email",
    "operator": "exists",
    "value": ""
  },
  "next": "pseudonymize"
}
```

A conditional next on Pub/Sub runs pseudonymize only when an email exists; other events pass untouched. [Guide chapter `consent-privacy`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#consent-privacy-consent-and-personal-data)

## Conditional routing[​](#conditional-routing "Direct link to Conditional routing")

Every chain field (`source.before`, `source.next`, `transformer.before`, `transformer.next`, `collector.next`, `destination.before`, `destination.next`) accepts a `Route`. `Route` is recursive:

```
type Route = string | Route[] | RouteConfig;
```

A `string` names a transformer (or a path, see below). A `Route[]` runs each entry in order as a pipeline. A `RouteConfig` is a disjoint union: it sets **exactly one** of `next`, `one`, `many`, `stop`, or none of them (pure gate that only filters by `match`). `match` is optional on `next`, `one`, `many` and `stop`; omit it to always match. A pure gate requires `match`.

At every position, `match` reads the same root: `{ ingest, event }`. Use `ingest.*` for request context and `event.*` for the event as it is at that point (at `source.before`, the source's raw output). Each route is evaluated only when the event reaches it.

### Route operators[​](#route-operators "Direct link to Route operators")

A `RouteConfig` is a disjoint union. Pick the operator that matches the routing shape you need:

* `next`: single continuation. Optionally gated by `match`.
* `one`: first-match dispatch. Walk entries in order; the first whose `match` passes wins, and the chain continues with that entry's `Route`.
* `many`: all-match fan-out, allowed in every chain field. Every matching entry becomes its own copy of the event, and each copy finishes the rest of the path on its own: the rest of the array, every enclosing chain, and the positions after it (collector, destinations). Copies are never merged. If only one entry matches, the event continues without a copy.
* `stop`: `{ "stop": true }` ends the running copy of the event. With `match`, it ends the copy only when the match passes; otherwise the chain continues.

An array made only of route configs, with no transformer id in it, is an implicit `one`: the first matching entry wins. Write `{ "one": [...] }` to make that explicit, or add a transformer id to make it a sequence.

First-match dispatch is the `one` operator. The server source of the complete example sorts raw requests before an event exists:

`/flows/server/sources/express/before`

```json
{
  "one": [
    {
      "match": {
        "and": [
          {
            "key": "ingest.path",
            "operator": "prefix",
            "value": "/g/collect"
          },
          {
            "key": "ingest.url",
            "operator": "regex",
            "value": "[?&]tid=G-SUBSITE(&|$)"
          }
        ]
      },
      "next": "ga4Decode"
    },
    {
      "match": {
        "key": "ingest.path",
        "operator": "suffix",
        "value": ".js"
      },
      "next": "file"
    },
    {
      "match": {
        "key": "ingest.path",
        "operator": "prefix",
        "value": "/g/collect"
      },
      "stop": true
    }
  ]
}
```

source.before runs on the raw request, before an event exists. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/sources/express/before/one`

```json
[
  {
    "match": {
      "and": [
        {
          "key": "ingest.path",
          "operator": "prefix",
          "value": "/g/collect"
        },
        {
          "key": "ingest.url",
          "operator": "regex",
          "value": "[?&]tid=G-SUBSITE(&|$)"
        }
      ]
    },
    "next": "ga4Decode"
  },
  {
    "match": {
      "key": "ingest.path",
      "operator": "suffix",
      "value": ".js"
    },
    "next": "file"
  },
  {
    "match": {
      "key": "ingest.path",
      "operator": "prefix",
      "value": "/g/collect"
    },
    "stop": true
  }
]
```

one takes the first matching branch; no match falls through. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`one` entries are evaluated in order and the first matching `Route` wins: a GA4 hit of the sub-site property is decoded, a script request is served, and any other GA4 hit stops. An entry without `match` always matches and acts as a default branch. If no entry matches, as for a `POST /collect`, the route selects nothing and the event continues unchanged, here to the source's `next`.

`/flows/server/sources/express/before/one/2`

```json
{
  "match": {
    "key": "ingest.path",
    "operator": "prefix",
    "value": "/g/collect"
  },
  "stop": true
}
```

GA4 hits for any other property stop at the route instead of reaching the collector undecoded. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

The conditions use the combinator `and` and three operators:

`/flows/server/sources/express/before/one/0/match/and`

```json
[
  {
    "key": "ingest.path",
    "operator": "prefix",
    "value": "/g/collect"
  },
  {
    "key": "ingest.url",
    "operator": "regex",
    "value": "[?&]tid=G-SUBSITE(&|$)"
  }
]
```

and needs every condition. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/sources/express/before/one/0/match/and/0/operator`: `"prefix"`. prefix matches the start of a value. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/sources/express/before/one/0/match/and/1/operator`: `"regex"`. regex matches a pattern: only hits of the sub-site property are decoded. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/sources/express/before/one/1/match/operator`: `"suffix"`. suffix matches the end of a value: script requests end in .js. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

### Conditional next and sequences[​](#conditional-next-and-sequences "Direct link to Conditional next and sequences")

A conditional next is a `match` plus a `next`: the target runs only when the match holds, otherwise the chain goes on. `not` inverts a condition:

`/flows/server/collector/next/2`

```json
{
  "match": {
    "key": "event.trigger",
    "operator": "regex",
    "value": "^(impression|visible)$",
    "not": true
  },
  "next": "enrich"
}
```

A conditional next (match plus next) runs the target only when the match holds; otherwise the chain goes on. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/collector/next/2/match/not`: `true`. not inverts a condition: everything that is not an impression. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

An array that mixes conditional next entries and step ids is a sequence. Each entry decides in turn, and a plain id always runs. The `enrich` step of the complete example is such a sequence: the customer lookup only for logged-in events, the session steps only for their event, `validate` always last.

`/flows/server/transformers/enrich/next`

```json
[
  {
    "match": {
      "key": "event.user.id",
      "operator": "exists",
      "value": ""
    },
    "next": "loadUser"
  },
  {
    "match": {
      "key": "event.name",
      "operator": "eq",
      "value": "session start"
    },
    "next": "sessionSave"
  },
  {
    "match": {
      "key": "event.name",
      "operator": "eq",
      "value": "order complete"
    },
    "next": "sessionLoad"
  },
  "validate"
]
```

A list mixing conditional next entries and step ids is a sequence: each match decides in turn, validate always runs last. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/transformers/enrich/next/0/match/operator`: `"exists"`. exists checks that a path has a value: only logged-in events load the customer. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/transformers/enrich/next/1/match/operator`: `"eq"`. eq compares as strings. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

A logged-in order loads the customer lifetime value, restores the session with its gclid and is validated against the server contract, in that order.

`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"
  },
  "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,
      "segment": "loyal"
    },
    "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
        }
      }
    ]
  }
}
```

Enrich an order: example `orderComplete` of step `enrich`. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

Fan-out is the `many` operator. Use it when one inbound event needs to feed several independent branches, for example a parser plus an audit log:

```json
{
  "sources": {
    "http": {
      "package": "@walkeros/server-source-express",
      "next": {
        "many": [
          {
            "match": { "key": "ingest.path", "operator": "prefix", "value": "/purchase" },
            "next": "purchase_parser"
          },
          { "next": "audit_log" }
        ]
      }
    }
  }
}
```

A request to `/purchase` matches both entries, so it becomes two copies: one runs `purchase_parser`, the other `audit_log`, and each then continues to the collector and the destinations on its own. Any other request matches only `audit_log` and continues without a copy. If no entry matches, the event continues unchanged.

Each copy gets its own `event.id`, derived from the original id and the branch position, so deduplication on `event.id` (and vendor deduplication such as Meta's `event_id`) keeps every copy. The copies share the same `trace`. A transformer that returns an array of results forks the same way.

`many` works in every chain field and at any depth. Inside an array, each copy finishes the rest of the array:

```
"next": ["decode", { "many": ["enrich_a", "enrich_b"] }, "session"]
```

This runs `decode`, then two copies: `enrich_a` then `session`, and `enrich_b` then `session`.

The complete example has no genuine use for `many`, `transformer.before` or `destination.next`. The tested route fixture `routeCases` in `@walkeros/core/dev` covers them.

## Dropping an event with `stop`[​](#dropping-an-event-with-stop "Direct link to dropping-an-event-with-stop")

A `stop` entry ends the running copy of the event. Combine it with `match` to drop only some events; without `match` it always stops. A `stop` that does not match falls through and the chain continues. In a `many`, a `stop` ends only its own copy; the other copies continue.

What a stop means depends on the position it is resolved in:

| Position                                                                  | A resolved `stop` means                                                                 |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `source.before`, `source.next`                                            | The event never reaches the collector. The push result is `{ ok: true, dropped: true }` |
| `transformer.before`, `transformer.next`, a transformer result `{ next }` | The running copy ends; the position that started the chain applies its own meaning      |
| `collector.next`                                                          | No destination receives the event                                                       |
| `destination.before`                                                      | This destination skips the event; other destinations are unaffected                     |
| `destination.next`                                                        | The post-push chain ends; delivery already happened                                     |

`collector.next` runs before the destination fan-out, so a `stop` there drops the event for **all** destinations. To drop an event for one destination only, put the `stop` in that destination's `before`.

Drop product impressions everywhere:

```
"collector": {

  "next": [

    { "match": { "key": "event.name", "operator": "eq", "value": "product impression" }, "stop": true },

    "session"

  ]

}
```

Drop events for some vendors only: in the complete example, Meta, Piwik PRO and Data Manager name `eventFilter` as their `before`. Its own `next` is a `stop` with an `or` match: impressions, likely bots and events that failed the contract. Pub/Sub does not name it, so the warehouse keeps every event.

`/flows/server/transformers/eventFilter`

```json
{
  "next": {
    "match": {
      "or": [
        {
          "key": "event.trigger",
          "operator": "regex",
          "value": "^(impression|visible)$"
        },
        {
          "key": "event.user.botScore",
          "operator": "gt",
          "value": "50"
        },
        {
          "key": "event.source.valid",
          "operator": "eq",
          "value": "false"
        }
      ]
    },
    "stop": true
  },
  "examples": { … }
}
```

eventFilter keeps impressions, likely bots and invalid events from Meta, Piwik PRO and Data Manager; Pub/Sub keeps everything. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/transformers/eventFilter/next`

```json
{
  "match": {
    "or": [
      {
        "key": "event.trigger",
        "operator": "regex",
        "value": "^(impression|visible)$"
      },
      {
        "key": "event.user.botScore",
        "operator": "gt",
        "value": "50"
      },
      {
        "key": "event.source.valid",
        "operator": "eq",
        "value": "false"
      }
    ]
  },
  "stop": true
}
```

A transformer next continues the chain after the step. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

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

```json
[
  {
    "key": "event.trigger",
    "operator": "regex",
    "value": "^(impression|visible)$"
  },
  {
    "key": "event.user.botScore",
    "operator": "gt",
    "value": "50"
  },
  {
    "key": "event.source.valid",
    "operator": "eq",
    "value": "false"
  }
]
```

or needs any condition. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/transformers/eventFilter/next/match/or/1/operator`: `"gt"`. gt compares numbers. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

`/flows/server/transformers/eventFilter/next/stop`: `true`. stop with a match drops the event at that point. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

A valid event from a human reaches the vendor.

`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": 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
      }
    }
  ]
}
```

`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
        }
      }
    ]
  }
}
```

Valid human order passes: example `validPasses` of step `eventFilter`. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

Without `match`, a stop always ends the copy. The `file` step serves `walker.js` and stops, so a script request never becomes an event:

`/flows/server/transformers/file/next`

```json
{
  "stop": true
}
```

An unconditional stop: a script request never becomes an event. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)

A dropped event is recorded as a `skip` with reason `dropped` in the flow's observability data. `walkeros validate` warns about entries placed after an unconditional `stop`, because they can never run.

To name and reuse a chain without writing code, declare a **path** transformer: a transformer entry with no `code` / `package`, just `before` / `next` / `cache`. The collector synthesizes a code-less passthrough so the named entry can be referenced from any `Route`:

`/flows/server/transformers/enrich`

```json
{
  "next": [ … ],
  "examples": { … }
}
```

A step with only next is a named chain other routes can call. [Guide chapter `chains-routing`](https://github.com/elbwalker/walkerOS/blob/main/packages/cli/examples/flow-complete.md#chains-routing-chains-and-routes)
