Skip to main content

Flow

Flow configuration is walkerOS's "configuration as code" approach. A single JSON file defines your entire event collection pipeline, making it portable, version-controlled, and deployable across environments.

Configuration structure

A flow configuration uses the Flow.Json format with two required fields:

  1. version - Schema version (4)
  2. flows - Named flow configurations (each a Flow)

Basic example

Loading...

This captures browser DOM events and sends them to your analytics API endpoint via HTTP POST requests.

Config syntax: package: vs code:

When configuring sources and destinations, you'll see two different syntaxes depending on your operating mode.

Bundled mode (JSON with CLI)

Use package: with a string reference. The CLI downloads and bundles the npm package:

Loading...

Integrated mode (TypeScript with startFlow)

Use code: with a direct import reference:

Loading...

Quick reference

PropertyModeValueWhen to Use
package:Bundled"@walkeros/..." (string)CLI resolves and bundles from npm
code:IntegratedsourceBrowser (import)Direct code reference in your app

Both achieve the same result. The difference is whether the CLI bundles the code for you (Bundled) or you import it directly (Integrated).

See Operating Modes for more details on choosing your approach.

Flow configuration (Flow)

Each flow in flows is a Flow that defines runtime behavior. Per-flow build/runtime metadata sits inside config (Flow.Config).

Platform

Platform is set explicitly via config.platform:

Loading...
Loading...

Platform options:

  • "platform": "server" - Node.js server environment (HTTP endpoints, cloud functions)
  • "platform": "web" - Browser environment (client-side tracking)

The CLI automatically applies platform-specific build defaults:

  • Web: IIFE format, ES2020 target
  • Server: ESM format, Node20 target

The bundle is written to stdout by default. Use -o to write to a file (e.g., -o ./dist/walker.js for web, -o ./dist/bundle.mjs for server).

Bundle

Build-time configuration lives under flow.<name>.config.bundle. The packages field specifies npm packages to download and bundle, and overrides pins transitive dependency versions (npm overrides semantics):

Loading...

packages properties:

  • version - npm version (semver or "latest", defaults to "latest")
  • imports - Array of named exports to import
  • path - Local filesystem path (takes precedence over version)

overrides is a Record<string, string>: for each named package, the version to use for any transitive reference. Direct package specs always win over overrides; overrides only substitute transitive dependencies during resolution.

For development or custom packages, use path to reference a local directory:

Loading...

See Local Packages in the CLI documentation for more details.

Sources

Sources capture events from various inputs. Each source needs:

  • package - The npm package (required for package-based sources, optional when using code:)
  • config - Source-specific settings
Loading...

See Sources documentation for all available options.

Destinations

Destinations receive processed events and send them to analytics tools, databases, or APIs:

Loading...

Configuration options:

  • settings - Destination-specific configuration (API keys, endpoints, etc.)
  • mapping - Event transformation rules (see Mapping documentation)
  • consent - Required consent states
  • policy - Processing rules

Conditional activation:

Sources and destinations support require to delay initialization until specific collector events fire. Use require: ["consent"] to prevent loading until consent is granted:

Loading...

See Destinations documentation for all available options.

Transformers

Transformers process events between sources and destinations. They validate, enrich, or redact events in the pipeline.

Loading...

Configuration options:

  • package - The npm package (required for package-based steps, optional when using code:)
  • code - Explicit import variable name (optional, auto-resolved)
  • config - Transformer-specific configuration
  • env - Environment-specific settings
  • before - Pre-transformer chain (runs before this transformer's push)
  • next - Next transformer in chain (omit to end chain)
  • variables - Transformer-level variable overrides

Chaining transformers:

Link transformers together using next:

Loading...

Explicit chain control with arrays:

For explicit control over the transformer chain order, use an array instead of a string. This bypasses the automatic chain resolution:

Loading...
SyntaxBehavior
"next": "fingerprint"Walks chain via each transformer's next property
"next": ["fingerprint", "enrich"]Uses exact order specified, ignores transformer next properties

Connecting to sources and destinations:

  • Source preprocessing: Use before on a source for consent-exempt preprocessing (runs before the source's push)
  • Pre-collector chain: Use next on a source to route events through transformers before the collector
  • Post-collector chain: Use before on a destination to route events through transformers after the collector
  • Post-push chain: Use next on a destination to process events after the destination push completes
Loading...

All chain fields (before and next) accept arrays for explicit chain control (see "Explicit chain control with arrays" above).

See Transformers documentation for available transformers and custom transformer guide.

Connection rules

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

FromToFieldNotes
SourceTransformerbefore on sourceConsent-exempt preprocessing
SourceTransformernext on sourcePre-collector chain
SourceCollectoromit nextDefault: events go straight to collector
TransformerTransformerbefore on transformerPre-transform enrichment
TransformerTransformernext on transformerChain continues to next transformer
CollectorTransformerbefore on destinationPost-collector chain
CollectorDestinationomit beforeDefault: events go straight to destination
DestinationTransformernext on destinationPost-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

The next and before fields on sources, transformers, and destinations all accept either a string or an array. The resolution behavior differs:

ValueBehavior
"fingerprint" (string)Walks transformer.next links until the chain ends
["fingerprint", "enrich"] (array)Uses the array as-is, ignoring each transformer's next property

When walking a string chain, three edge cases apply:

  1. String with array next: If fingerprint.next is ["a", "b"], the walker appends that array and stops. The final chain becomes ["fingerprint", "a", "b"].
  2. Missing target: If a transformer name in the chain does not exist, that step is skipped and the event proceeds unchanged. If a string chain points to a nonexistent first transformer, the chain is empty and the event goes through without transformation.
  3. Circular reference: Detected via a visited set and safely broken, with no infinite loop.

Pre-collector vs post-collector transformers

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

[Source.before] → Source ──[next]──→ Pre-transformers ──→ Collector ──→ Post-transformers ──[before]──→ Destination ──[next]──→ Post-push

The same transformer can appear in both a pre-collector and a post-collector chain. For example, you might use fingerprint in a source's next chain and again in a destination's before chain. Each invocation runs independently.

Loading...

In this config, fingerprint runs before the collector (pre-chain) and enrich runs after the collector but before the analytics destination (post-chain).

Conditional routing

Every chain field (source.before, source.next, transformer.before, transformer.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, or none of them (pure gate that only filters by match). match is optional everywhere; omit it to always match.

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 main chain continues with that entry's Route.
  • many: all-match terminal fan-out. Every matching entry spawns an independent flow that runs to its own exit. The main chain terminates at the many step. Restricted to pre-collector positions (source.next, transformer.next, transformer.before). Post-collector fan-out uses the destinations map.

First-match dispatch is the one operator:

Loading...

one entries are evaluated in order and the first matching Route wins. The trailing entry omits match, so it always matches and acts as the default branch. If no entry matches, the event passes through unchanged to the collector.

Terminal fan-out is the many operator. Use it when one inbound event needs to feed several independent processing branches (for example, splitting a webhook payload into per-vendor enrichment paths before the collector):

Loading...

Every entry in many whose match passes runs as its own branch. The main chain stops at the many step, so each branch is responsible for its own continuation. If no entry matches, the event is dropped from the pre-collector pipeline.

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:

Loading...

Deferred activation with require

Sources and destinations support require to delay initialization until a specific collector event fires. This is commonly used for consent-gated loading:

Loading...

The analytics destination will not initialize until a "consent" event is pushed to the collector. Until then, events are queued. This works identically for sources:

Loading...
tip

Combine require with consent on destinations for full consent management: require controls when the destination loads, while consent controls which events it receives. See the Destinations section above for a combined example.

Inline Code (without packages)

For simple one-liner logic, define sources, transformers, or destinations inline without creating a package.

Use code object instead of package:

Inline Transformer:

Loading...

Inline Destination:

Loading...

Code object properties:

  • push - The push function with $code: prefix (required)
  • type - Optional instance type identifier
  • init - Optional init function with $code: prefix

Rules:

  • Use package OR code, never both
  • config stays separate (same as package-based)
  • $code: prefix outputs raw JavaScript at bundle time

Collector

The collector processes events from sources and routes them to destinations:

Loading...

Options:

  • run - Whether to start the collector automatically (default: true)
  • globals - Properties added to every event
  • consent - Default consent state

See Collector documentation for complete options.

Web-specific options

For browser bundles, you can configure window variable names via config.settings:

Loading...

Properties:

  • windowCollector - Global variable name for collector instance (default: "collector")
  • windowElb - Global variable name for event tracking function (default: "elb")

Multi-flow configuration

For managing dev/staging/production flows in one file:

Loading...

Build specific flows using the CLI:

Loading...

Cross-flow references with $flow

Flows in the same file can reference each other's config block via $flow.<name>.<path>. The most common case is linking a web flow's API destination to a server flow's deployed URL:

Loading...

Cross-flow refs read values from another flow's config block (platform, url, settings.*), keeping web and server flows in sync without duplication. The bundler resolves $flow.server.url to https://collect.example.com at build time. Validate is lenient (warns on missing values), bundle is strict and fails loudly if the referenced value is empty, so production builds never ship with an unresolved URL.

Dynamic patterns

Flow configurations support four dynamic patterns for reusable, environment-aware configs:

$var.name - Config Variables

Reference variables defined in variables for shared values across your config:

Loading...

Variables can be defined at three levels (higher specificity wins):

  1. Source/Destination level - Highest priority
  2. Flow (Config) level - Middle priority
  3. Setup level - Lowest priority

$env.NAME - Environment Variables

Reference environment variables with optional defaults:

Loading...

Syntax:

  • $env.GA4_ID - Required, throws if not set
  • $env.GA4_ID:default - Uses "default" if not set
Why only $env supports defaults

Environment variables are external and unpredictable - they might not be set in all environments. Config variables ($var) are explicitly defined, so a missing one indicates a configuration error.

$code: - Inline JavaScript

Embed JavaScript code directly in JSON config values:

Loading...

The $code: prefix is stripped during bundling, outputting raw JavaScript:

Loading...

Use cases:

  • fn: callbacks - Transform values in mapping
  • condition: predicates - Conditional event processing
  • Custom logic - Any inline function or expression

Syntax notes:

  • Multi-line code: Use JSON escapes (\n, \")
  • Scope: Same as bundled code (access to imports and variables)
  • Errors: Caught at build time by TypeScript/esbuild

Type hierarchy

walkerOS uses a clear type hierarchy:

┌─────────────────────────────────────────────────────────────────┐
│ Flow.Json (config file) │
│ ├── version: 4 │
│ ├── variables?: { currency: "EUR" } │
│ └── flows: │
│ └── default: Flow │
└─────────────────────────────────────────────────────────────────┘

│ CLI resolves $var, $env, $flow

┌─────────────────────────────────────────────────────────────────┐
│ Flow (resolved flow) │
│ ├── config: { platform, url?, settings?, bundle? } │
│ ├── sources: { ... } │
│ ├── transformers: { ... } │
│ ├── destinations: { ... } │
│ ├── stores: { ... } │
│ └── collector: { ... } │
└─────────────────────────────────────────────────────────────────┘

│ CLI bundles and transforms

┌─────────────────────────────────────────────────────────────────┐
│ Collector.InitConfig (runtime) │
│ Passed to startFlow() at runtime │
└─────────────────────────────────────────────────────────────────┘
  • Flow.Json - Root config file format for CLI
  • Flow - Single flow configuration (with optional config, sources, destinations, etc.)
  • Flow.Config - Per-flow config block (platform, url, settings, bundle)
  • Collector.InitConfig - Runtime type passed to startFlow()

Complete example

Here's a production-ready flow that accepts HTTP events and sends them to BigQuery:

Loading...

Programmatic usage

You can also use configuration programmatically with the startFlow function:

Loading...

See the Collector documentation for complete API reference.

Next steps

  • CLI - Learn how to bundle and test flows
  • Docker - Deploy flows in containers
  • Sources - Explore available event sources
  • Destinations - Configure analytics destinations
  • Mapping - Transform events for destinations
💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)