walkeros-understanding-mapping

star 340

Use when transforming walkerOS events in the flow (source→collector or collector→destination), configuring data/map/loop/set/condition/policy, or using $code: syntax in JSON configs.

elbwalker By elbwalker schedule Updated 6/3/2026

name: walkeros-understanding-mapping description: Use when transforming walkerOS events in the flow (source→collector or collector→destination), configuring data/map/loop/set/condition/policy, or using $code: syntax in JSON configs.

Understanding walkerOS Mapping

Overview

Mapping transforms data at multiple points in the walkerOS flow:

  1. Source → Collector: Transform raw input (HTTP requests, dataLayer pushes) into walkerOS events
  2. Collector → Destination: Transform walkerOS events into vendor-specific formats

Core principle: Mapping is the universal transformation layer. Same strategies work everywhere in the flow.

Core Functions

See packages/core/src/mapping.ts for implementation.

Function Purpose
getMappingEvent(event, rules) Find mapping rule for an event
getMappingValue(value, data, options) Transform a value using mapping config
processEventMapping(event, config, collector) Unified processing for sources/destinations

processEventMapping Flow

1. Apply config.policy (modifies event)
2. Find matching rule via getMappingEvent()
3. Apply rule.policy (modifies event)
4. Transform config.data (global)
5. Check rule.ignore (short-circuits if true)
5b. Read rule.silent (informational - destination honors it)
6. Override event.name if rule.name
7. Transform rule.data (event-specific)

Configuration Hierarchy

Mapping.Config (Top Level)

interface Config {
  consent?: Consent; // Required consent for ALL events
  data?: Value; // Global data transformation
  include?: string[]; // Event sections to flatten into context.data
  policy?: Policy; // Pre-processing for ALL events
  mapping?: Rules; // Event-specific rules
}

Mapping.Config.include

include lists event sections to flatten into the destination's PushContext.data before the rule runs. Sections are top-level keys of the event (globals, user, consent, data, context, nested, source, custom). The runtime helper flattenIncludeSections (in packages/core/src/include.ts) handles the merge.

Use this when your mapping needs values from multiple event sections without plumbing each path through a key:/map: config. include is also available on Mapping.Rule (rule-level overrides config-level).

{
  include: ['globals', 'user'],
  mapping: {
    page: {
      view: {
        // user.id and globals.* are now reachable via top-level paths in
        // data/key/map values, e.g. `key: 'id'` for user.id.
        data: { map: { user_id: 'id', site: 'language' } },
      },
    },
  },
}

Mapping.Rule (Per Event)

interface Rule {
  name?: string; // Override event name
  data?: Value; // Event-specific data transformation
  include?: string[]; // Event sections to flatten into context.data (rule-level)
  ignore?: boolean; // Skip this event entirely (no processing, no push)
  silent?: boolean; // Process settings side effects, skip destination default push
  policy?: Policy; // Event-specific pre-processing
  condition?: Function; // Match condition (for arrays)
  consent?: Consent; // Required consent for this rule
  settings?: unknown; // Custom event configuration
  batch?: number; // Batch size for grouping
  extend?: RulePatch; // Config-layer merge onto a package-shipped default rule
  remove?: string[]; // Output-layer: dotted paths stripped from the final payload
}

extend and remove (patching package-shipped rules)

Some packages ship their own default mapping rules. A user rule at the same key normally replaces the default in full. Two keywords change that:

  • extend (config layer): a partial rule deep-merged onto the package-shipped default at init, before any event is evaluated. A null value clears an inherited field. Lets you add or override one field while keeping the rest.
  • remove (output layer): dotted paths stripped from the produced payload after evaluation, applied last. Useful for dropping PII or vendor-reserved fields without rewriting the full rule.

A rule with neither keyword keeps the standard replace behavior.

See the website mapping docs for the authoritative reference and the GA4 example.

{
  "purchase": {
    "extend": {
      "data": { "map": { "affiliation": "params.ep.affiliation" } }
    },
    "remove": ["currency"]
  }
}

Silent vs Ignore

Both flags control rule behavior but have different semantics:

  • ignore: true - the rule matched but nothing happens. No data transform, no destination call, no side effects. Use for suppression.
  • silent: true - the rule matched and the destination push() is called. settings.identify, settings.revenue, settings.group, etc. still run. Only the destination's default forwarding call (e.g. track(), capture(), event()) is suppressed. Use for "identify without an event" style flows.

If both flags are set on the same rule, ignore wins.

Common use case: a user login event that should call amplitude.identify() but should not create a separate track("user login") event in Amplitude.

Mapping.ValueConfig (Value Extraction)

interface ValueConfig {
  key?: string; // Extract from path
  value?: Primitive; // Static fallback value
  fn?: Function; // Custom transformation
  map?: Record; // Object transformation
  loop?: [path, config]; // Array transformation
  set?: Value[]; // Create array from values
  condition?: Function; // Conditional extraction
  consent?: Consent; // Consent-gated extraction
  validate?: Function; // Value validation
}

Event Matching

Match events to transformation rules by entity and action.

const mapping = {
  // Exact match: "product view" → view_item
  product: {
    view: { name: 'view_item' },
    add: { name: 'add_to_cart' },
  },

  // Wildcard action: "foo *" → foo_interaction
  foo: {
    '*': { name: 'foo_interaction' },
  },

  // Wildcard entity: "* click" → generic_click
  '*': {
    click: { name: 'generic_click' },
  },
};

Conditional Mapping (Array)

Array of rules - first matching condition wins:

order: {
  complete: [
    {
      condition: (event) => event.data?.value > 100,
      name: 'high_value_purchase',
    },
    { name: 'purchase' }, // Fallback (no condition)
  ],
}

JSON with $code:

{
  "order": {
    "complete": [
      {
        "condition": "$code:(event) => event.data?.value > 100",
        "name": "high_value_purchase"
      },
      { "name": "purchase" }
    ]
  }
}

Value Mapping Strategies

Common patterns shown below. For detailed examples of all 12 strategies, see value-strategies.md.

Essential Patterns

// Key extraction (string shorthand)
'data.price'                              // → event.data.price

// Key with fallback
{ key: 'data.currency', value: 'USD' }    // Use USD if missing

// Static value
{ value: 'USD' }

// Function transform
{ fn: (event) => event.data.price * 100 } // Convert to cents

// Object map
{ map: { item_id: 'data.id', item_name: 'data.name' } }

// Array loop
{ loop: ['nested', { map: { item_id: 'data.id' } }] }

// Loop with "this" (single item as array)
{ loop: ['this', { map: { item_id: 'data.id' } }] }

// Set (create array)
{ set: ['data.id'] }                      // → ["SKU-123"]

// Fallback chain: Value[] at any value position (first defined value wins)
[{ key: 'data.sku' }, { key: 'data.id' }, { value: 'unknown' }]

// Consent-gated
{ key: 'user.email', consent: { marketing: true } }

// Validate
{ key: 'data.email', validate: (v) => v.includes('@') }

Policy (Pre-Processing)

Policy modifies the event BEFORE mapping rules are applied. Use for:

  • Adding computed fields
  • Normalizing data structure
  • Consent-gated field injection

Config-Level Policy

Applied to ALL events:

config: {
  policy: {
    'user_data.external_id': 'user.id',
    'custom_data.server_processed': { value: true },
  },
  mapping: { /* ... */ }
}

Event-Level Policy

Applied after config policy, only for specific event:

mapping: {
  order: {
    complete: {
      policy: {
        'enriched.total_cents': {
          fn: (event) => Math.round(event.data.total * 100)
        }
      },
      name: 'purchase',
      data: { /* ... */ }
    }
  }
}

Policy with Consent

{
  "policy": {
    "user_data.em": {
      "key": "user.email",
      "consent": { "marketing": true }
    }
  }
}

Rule Features

Ignore Events

mapping: {
  test: { '*': { ignore: true } },  // Ignore all test events
}

Batch Processing

To batch all of a destination's events, set config.batch (no mapping rule needed):

config: {
  batch: { size: 5 },  // Flush after every 5 events (a bare number is the wait window in ms)
}

A rule-level batch batches only that entity-action into its own buffer and overrides config.batch per field:

mapping: {
  order: {
    complete: {
      batch: { wait: 1000 },  // Only order complete batches, with a 1s debounce
    }
  }
}

Custom Settings

mapping: {
  order: {
    complete: {
      name: 'purchase',
      settings: { priority: 'high', retryCount: 3 }
    }
  }
}

$code: Prefix (JSON Configs)

The $code: prefix enables JavaScript functions in JSON configurations:

{
  "fn": "$code:(event) => event.data.price * 100",
  "condition": "$code:(event) => event.data?.value > 100",
  "validate": "$code:(value) => value > 0"
}

Important: The $code: prefix is processed by the CLI bundler. It converts JSON strings to actual JavaScript functions during build.

Function Signatures

All mapping callbacks share (value, context). The second argument is a Mapping.Context object (see "Context object" below).

Context Signature
fn (value, context) => result
condition (value) (value, context) => boolean
condition (rule) (event, context) => boolean
validate (value, context) => boolean
loop condition (value, context) => boolean

One-arg signatures like (value) => value.toUpperCase() continue to work, TypeScript ignores the unused second arg.

Context object

Field Type Required Description
event WalkerOS.DeepPartialEvent yes The root event being mapped
mapping Mapping.Value | Mapping.Rule yes The surrounding mapping config (or rule)
collector Collector.Instance yes Active collector, use for push and queue
logger Logger.Instance yes Use for info/warn/error/debug
consent WalkerOS.Consent (optional) no Resolved consent at this evaluation point

Quick Reference

Value Extraction Cheatsheet

Pattern Result
"data.id" Extract event.data.id
{ value: "USD" } Static "USD"
{ key: "x", value: "y" } Extract x, fallback to "y"
{ fn: (e) => ... } Custom function
{ map: {...} } Object transformation
{ loop: ["nested", {...}] } Array transformation
{ loop: ["this", {...}] } Single-item as array
{ set: ["a", "b"] } Create array [valA, valB]
[m1, m2, m3] Fallback chain
{ consent: {...} } Consent-gated
{ condition: fn } Conditional
{ validate: fn } Validated

Rule Features Cheatsheet

Feature Purpose
name Override event name
data Transform event data
ignore Skip event entirely (no processing, no push)
silent Run settings side effects, skip default forwarding
policy Pre-process event
condition Match condition (arrays)
consent Required consent
settings Custom configuration
batch Batch size
extend Config-layer merge onto a package-shipped default (null clears a field)
remove Output-layer: dotted paths stripped from the final payload

Config Features Cheatsheet

Feature Purpose
consent Required consent (all events)
data Global data transformation
policy Global pre-processing
mapping Event-specific rules

Complete Examples

For full destination configuration examples (TypeScript + JSON), see complete-examples.md.


Mapping at the Transformer Position

Mapping.Config accepts the same shape at three positions in the flow, but the semantic differs by position:

Position What the mapping produces
Source A walkerOS event from raw input
Transformer A mutated walkerOS event that continues through the chain
Destination A vendor-shaped payload that the destination consumes

When a transformer step declares only a mapping (no code, no package), the collector synthesizes a push that runs processEventMapping against each event. Same keyword as the destination field, different semantic at this step position. See walkeros-understanding-transformers for the pass-through-step model.

Which fields apply at the transformer position

Only event-mutating fields run; vendor-payload fields are no-ops with a one-time init warning:

Field Transformer position
policy Applies, pre-processes the event before rule matching
include Applies, flattens event sections into mapping context
mapping[].policy Applies, per-event policy
mapping[].name Applies, renames the event (mutation is observable downstream)
mapping[].ignore Applies, drops the event from the chain entirely (no downstream step sees it)
mapping[].consent Applies, consent gate
data, mapping[].data Ignored at this position (event mutation does not produce a vendor payload)
mapping[].silent Ignored at this position (destination-only concept)

Note the ignore: true semantic shift: at a destination it skips delivery to that destination only; at a transformer step it drops the event from the chain so no downstream step (transformer or destination) sees it.

{
  "transformers": {
    "redactPII": {
      "mapping": {
        "policy": {
          "user.email": { "value": "[redacted]" }
        },
        "mapping": {
          "test": {
            "*": { "ignore": true }
          },
          "order": {
            "complete": { "name": "purchase" }
          }
        }
      }
    }
  }
}

Where Mapping Lives

Location Purpose
Source config Transform raw input → walkerOS events
Transformer step config Mutate walkerOS events in-flight
Destination config Transform walkerOS events → vendor format
packages/core/src/mapping.ts Core mapping functions
packages/core/src/types/mapping.ts Type definitions
packages/cli/examples/flow-complete.json Comprehensive example (53 features)

Related Skills

Source Files:

Detailed References:

Examples:

Install via CLI
npx skills add https://github.com/elbwalker/walkerOS --skill walkeros-understanding-mapping
Repository Details
star Stars 340
call_split Forks 20
navigation Branch main
article Path SKILL.md
More from Creator