Skip to content

Flow Context

The Flow Context is the shared runtime object for a single flow execution.

It is how steps communicate with each other without relying on globals, hidden mutable state, or complex parameter threading.

What the context contains

The context gives each step access to:

  • ctx.input: the original flow input
  • ctx.get(key): read a stored value
  • ctx.set(key, value): store a value
  • ctx.has(key): check if a value exists

If the flow is using cancellation support, the context also carries the execution signal.

Accessing input

The input passed to flow.run(input) is available as ctx.input.

ts
flow.step("validate", (ctx) => {
  console.log(ctx.input.userId);
});

This input should be treated as the source data for the execution, not as a place to accumulate intermediate state.

Reading and writing data

Use set() and get() to share intermediate data between steps:

ts
flow.step("step-1", async (ctx) => {
  ctx.set("orderId", "12345");
});

flow.step("step-2", (ctx) => {
  const orderId = ctx.get<string>("orderId");
  console.log(orderId);
});

Why context matters

Without context, each step would need to recalculate data, re-query dependencies, or rely on out-of-band storage. Context keeps the flow readable and makes compensation practical, because rollback logic often needs IDs produced by earlier steps.

Typical context values include:

  • created record IDs
  • reservation IDs
  • external transaction IDs
  • derived values needed later in the flow

Type safety

You can specify types when reading values:

ts
const val = ctx.get<string>("myKey");

This keeps larger workflows easier to maintain and reduces accidental misuse of stored values.

The context lifecycle

  • Start: created when run() is called
  • During Steps: passed to each step function
  • During Compensation: passed to compensation functions
  • End: discarded when the flow finishes

This is an execution-local object. It does not persist across runs by itself.

Important note

The context is unique to one execution.

That means:

  • it is not shared between two separate calls to run()
  • it is not a cache
  • it is not a replacement for durable storage

Even if two runs use the same idempotency key, they do not share one live in-memory context object.

What to store in context

Good candidates:

  • values created by earlier steps that later steps need
  • values needed for compensation
  • small derived values that improve readability

Poor candidates:

  • large binary payloads
  • entire database snapshots
  • secrets you do not want to risk logging indirectly
  • values that should really live in durable storage

Best Practices

  • Treat ctx.input as read-only.
  • Use descriptive keys like paymentId or reservationId.
  • Store references, not large objects, when possible.
  • Put only execution-relevant data in context.

Released under the MIT License.