Create Your Own Source
A source captures events from an environment (browser, server, third-party API) and forwards them to the walkerOS collector.
The source interface
Sources are async functions that receive a context object and return a source instance:
The context contains:
The returned instance must implement:
Types bundle
Sources use a Types interface to bundle all TypeScript types:
The 4 type parameters:
- Settings: Source configuration options
- Mapping: Event mapping (usually
neverfor sources) - Push: External push signature (what
source.pushexposes) - Env: Internal dependencies (what source calls via
env.elb)
Context destructuring
The context parameter contains everything your source needs:
The collector provides env.push (formerly env.elb). You provide other dependencies (like window, document, custom APIs) when configuring the source.
Per-scope context (server sources)
A single source factory instance handles many concurrent invocations: an Express server processes overlapping requests, a Lambda may be reused across invocations, a queue consumer processes batches. Each logical unit of work, an HTTP request, a queue message, a websocket frame, is a scope. To keep ingest metadata and response delegates isolated between concurrent scopes, server sources wrap each invocation with context.withScope:
Inside the body, env.push carries the scope's ingest and respond all the way to destinations. Transformers and destinations read env.respond to delegate the HTTP response; context.ingest for source-extracted metadata. The scope ends when the body resolves.
Browser sources (and other single-scope sources) skip withScope. A browser tab is a single logical scope for its entire lifetime; calling env.push directly on the factory env is correct. Only server sources handling concurrent inbound work need withScope.
Minimal example
Complete example: Event API source
Capturing events from a third-party API with event listeners:
Using your source
Testing your source
Test Utilities
Test Example
Reading a step back from the collector
When an integration test needs to call a step's raw push directly through the collector (a source's push(req, res), a destination's push(event, ctx), etc.), the bag types (collector.sources, collector.destinations, collector.transformers, collector.stores) erase the per-step generic. The push signature collapses to Elb.Fn on read.
Use the typed accessors from @walkeros/core to recover the narrow type without casts:
Symmetric helpers exist for every step kind: Source.getSource, Destination.getDestination, Transformer.getTransformer, Store.getStore. Each throws <Kind> not found: <id> when the id is unknown. The helpers are opt-in and have no runtime cost.
Setup lifecycle (optional)
Sources may implement an optional setup() lifecycle for one-time, operator-time provisioning, for example registering a webhook callback or creating a Pub/Sub subscription. Setup runs only when an operator explicitly invokes walkeros setup source.<name>; the runtime never auto-invokes it. Opt in via config.setup in the flow config (true or an object).
See Setup lifecycle on the destinations page for the full concept, and walkeros setup CLI command for the operator-facing command.
Conditional activation with require
Sources can declare dependencies on collector events. A source with require won't receive lifecycle events (via on()) until all specified events have fired:
How it works:
- Sources are registered in
collector.sourcesimmediately, and the collector runsInstance.init()on each one eagerly after registration.Source.Config.initflips totrueonce init has run. requiredoes not gate code execution. It gateson()delivery. Lifecycle events targeted at a source whoserequireis unmet are buffered in the source'sInstance.queueOnarray.- After each collector event (
consent,user,session,run, etc.), the collector decrements the matching entry from each source'srequirelist. When a source's require is empty (andconfig.init === true), its queued lifecycle events are replayed by callingsource.on(type, data)for each entry, then the queue is cleared. - Chains work naturally:
consentclears the session source'srequire, which firesuser, which clears the dataLayer source'srequire, which then receives any queued events.
Common patterns:
| Require | Use case |
|---|---|
['consent'] | Wait for CMP consent before tracking |
['user'] | Wait for identity resolution |
['session'] | Wait for session detection |
['consent', 'run'] | Wait for both consent and collector run |
Common patterns
Polling for API Readiness
When the external API isn't immediately available:
Source as adapter pattern
Sources bridge external systems and the collector:
External System ←→ Source (Adapter) ←→ Collector
Two interfaces:
-
External (
source.push): Platform-specific signature- Browser:
push(elem, data, options)→ ReturnsPromise<PushResult> - Server:
push(req, res)→ ReturnsPromise<void>(writes HTTP response) - Your choice: Match your environment's needs
- Browser:
-
Internal (
env.elb): Standard collector interface- Always
Elb.Fn- same across all sources - Sources translate external inputs → standard events →
env.elb(event)
- Always
Example signatures:
The Push type parameter defines what your source exposes externally. Internally, all sources use env.elb to forward to the collector.
Key concepts
- Context pattern: Sources receive a single context object with config, env, logger, id
- Validate custom dependencies: Only check optional env properties your source needs
- Types bundle: Use 4-parameter pattern for full type safety
- Adapter pattern: External push adapts to environment, internal env.push stays standard
- Cleanup: Implement
destroy()to remove listeners - Stateless: Let collector manage state
Package convention
Every walkerOS package includes machine-readable metadata for tooling and discovery.
walkerOS field in package.json
| Field | Required | Description |
|---|---|---|
walkerOS | Yes | Object with type and platform metadata |
Build-time generation
Use buildDev() from the shared tsup config to auto-generate walkerOS.json:
This file contains your package's JSON Schemas and examples, enabling MCP tools and the CLI to validate configurations without installing your package.
Optional: Hints
Packages can export a hints record from src/dev.ts to provide lightweight, actionable context beyond schemas and examples, such as capture timing, event formats, or troubleshooting tips. Hints are serialized into walkerOS.json and surfaced via MCP tools. See the walkeros-create-source skill for details.
Publishing checklist
-
walkerOSfield in package.json - Keywords include
walkerosandwalkeros-source -
buildDev()in tsup.config.ts -
dist/walkerOS.jsongenerated on build -
npm run testpasses -
npm run lintpasses
Next steps
- Review Browser Source for DOM patterns
- Review DataLayer Source for interception patterns
- Learn about creating destinations