Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/eersnington/sideffect/llms.txt

Use this file to discover all available pages before exploring further.

Step is used to create reusable typed workflow activities. Each step has a name, an input schema, an output schema, and a run function that performs the work. Sideffect validates both the payload and the result against their schemas automatically, so your run function always receives the correct shape and always returns the correct shape. Import Step directly from sideffect.
import { Step } from "sideffect";

Step.make(name, options)

Creates a reusable StepDefinition. The returned definition can be passed to step.do(...) in any workflow, decorated with Rollback.with(...) via .pipe(), or composed with other helpers.
name
string
required
Human-readable name for the step. This string appears in the Cloudflare Workflows dashboard timeline and in any schema decode error messages. Examples: "fetch image", "send email", "add numbers".
options.payload
Schema.Schema<Payload>
required
Effect Schema for the step’s input. Sideffect decodes the raw payload value with this schema before calling run. If decoding fails, a NonRetryableError is thrown and the workflow is not retried.
options.result
Schema.Schema<Result>
required
Effect Schema for the step’s output. Sideffect decodes the value returned by run with this schema before returning it to the workflow. If decoding fails, a NonRetryableError is thrown.
options.run
(payload: Payload, context: StepContext<Env>) => MaybeEffect<Result>
required
The step implementation. Receives the decoded payload and a StepContext (see below). May return a direct value, a Promise, or an Effect.Effect.StepContext<Env> extends Cloudflare’s native WorkflowStepContext and adds:
  • ctx.env — Worker environment bindings, typed from your project’s Cloudflare.Env.
  • ctx.ctx — Worker execution context.
  • ctx.workflowStep — raw NativeWorkflowStep API for advanced use cases.
The inherited Cloudflare fields include:
  • ctx.attempt — current retry attempt number (1-based).
  • ctx.step.name — the step name.
  • ctx.config — the step config (timeout, retries) if configured.
Returns a StepDefinition<Payload, Result, Env> with a .pipe() method for applying transformations such as Rollback.with(...).

StepOptions

Passed as the third argument to step.do(stepDefinition, payload, options?) inside a workflow run function. All fields are optional and delegate directly to Cloudflare’s WorkflowStepConfig.
interface StepOptions {
  retries?: WorkflowStepConfig["retries"];
  timeout?: WorkflowStepConfig["timeout"];
  sensitive?: WorkflowStepConfig["sensitive"];
}
retries
WorkflowStepConfig["retries"]
Cloudflare retry configuration for the step. The shape is defined by Cloudflare’s WorkflowStepConfig type from cloudflare:workers.
timeout
WorkflowStepConfig["timeout"]
Maximum execution time for the step. A Cloudflare duration string (e.g. "5 minutes") or a number of milliseconds.
sensitive
WorkflowStepConfig["sensitive"]
When true, Cloudflare treats step logs and output as sensitive and redacts them from the Workflows dashboard.
StepOptions are only applied when you pass them at call-time via step.do(myStep, payload, options). They are not stored on the StepDefinition itself, so you can reuse the same step definition with different options in different parts of a workflow.

Examples

addNumbers — synchronous step

import { Schema, Step } from "sideffect";

export const addNumbers = Step.make("add numbers", {
  payload: Schema.Struct({ left: Schema.Number, right: Schema.Number }),
  result: Schema.Number,
  run: ({ left, right }) => left + right,
});

multiplyNumber — async step

import { Schema, Step } from "sideffect";

export const multiplyNumber = Step.make("multiply number", {
  payload: Schema.Struct({ value: Schema.Number, by: Schema.Number }),
  result: Schema.Number,
  run: async ({ value, by }) => value * by,
});

effectUppercase — Effect step

import { Effect } from "effect";
import { Schema, Step } from "sideffect";

export const effectUppercase = Step.make("effect uppercase", {
  payload: Schema.String,
  result: Schema.String,
  run: (message) => Effect.succeed(message.toUpperCase()),
});

Using StepOptions at call-time

export const stepContextLayer = workflow.toLayer(async ({ payload }, step) => {
  return step.do(readStepContext, payload, { timeout: "5 minutes" });
});

Step with retry config

export const workflowLayer = workflow.toLayer(async ({ payload }, step) => {
  const result = await step.do(fetchImageStep, { imageKey: payload.imageKey }, {
    retries: { limit: 3, delay: "15 seconds", backoff: "exponential" },
    timeout: "2 minutes",
  });

  return result;
});

Accessing env and ctx in a step

import { Schema, Step } from "sideffect";

export const callCounterDurableObject = Step.make("call counter durable object", {
  payload: Schema.Struct({ key: Schema.String }),
  result: Schema.Struct({ count: Schema.Number }),
  run: async ({ key }, ctx) => {
    const id = ctx.env.COUNTER.idFromName(`workflow-${key}`);
    const response = await ctx.env.COUNTER.get(id).fetch("https://counter.local/count");
    return response.json();
  },
});
Never throw a NonRetryableError from inside a step’s run function to signal a schema problem — Sideffect does this automatically when a payload or result fails to decode. Only throw NonRetryableError explicitly when the failure is logically unrecoverable (e.g. a missing required resource).

Build docs developers (and LLMs) love