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.

When you run vite build or vite dev, Sideffect reads your workflow source files with the TypeScript parser and walks the AST to find exported WorkflowLayer values — it never imports or executes your modules during this phase. This means discovery is safe to run in any environment, works before your runtime dependencies are available, and cannot trigger side effects from your application code.

Supported export patterns

Sideffect recognises four static forms. Any of these can appear in the files or directories you list in workflowPaths.

1. Inline Workflow.make(...).toLayer(...)

The most direct form: call Workflow.make and chain .toLayer in a single exported variable declaration.
import { Schema, Step, Workflow } from "sideffect";

export const addNumbersLayer = Workflow.make({
  name: "add-numbers",
  payload: Schema.Struct({ left: Schema.Number, right: Schema.Number }),
}).toLayer(async (event, step) => {
  return { result: event.payload.left + event.payload.right };
});

2. Variable holding the definition, then .toLayer() in the same file

You can define the workflow object first and call .toLayer on it later in the same file. Sideffect tracks the intermediate variable and resolves the name correctly.
import { Schema, Workflow } from "sideffect";

const myWorkflow = Workflow.make({
  name: "my-workflow",
  payload: Schema.Struct({ message: Schema.String }),
});

export const myLayer = myWorkflow.toLayer(async (event) => {
  return { echo: event.payload.message };
});

3. Imported workflow definition from a relative module

The workflow definition can live in a separate file and be imported into the file that calls .toLayer. Sideffect follows local relative imports to resolve the definition.
// src/workflows/definition.ts
import { Schema, Workflow } from "sideffect";

export const myWorkflow = Workflow.make({
  name: "my-workflow",
  payload: Schema.Struct({ message: Schema.String }),
});
// src/workflows/my-workflow.ts
import { myWorkflow } from "./definition";

export const myLayer = myWorkflow.toLayer(async (event) => {
  return { echo: event.payload.message };
});

4. Default-exported workflow layer

A default export is also discovered. Sideffect uses "default" as the export name internally and derives the class name and binding from workflow.name as usual.
import { Schema, Workflow } from "sideffect";

const myWorkflow = Workflow.make({
  name: "my-workflow",
  payload: Schema.Struct({ message: Schema.String }),
});

export default myWorkflow.toLayer(async (event) => {
  return { echo: event.payload.message };
});

Naming convention

The name string you pass to Workflow.make drives two identifiers that Sideffect generates automatically:
name valueCloudflare class nameWorker binding
"add-numbers"AddNumbersADD_NUMBERS
"image-processing"ImageProcessingIMAGE_PROCESSING
"billing_invoice"BillingInvoiceBILLING_INVOICE
Class name rule: the name is split on any run of non-alphanumeric characters, each part is capitalised, and the parts are joined — producing a PascalCase identifier that Cloudflare accepts as a WorkflowEntrypoint export name. Binding rule: the name is converted to SCREAMING_SNAKE_CASE by inserting underscores before uppercase-to-lowercase transitions and replacing non-alphanumeric runs with _, then the whole string is uppercased.

Unsupported patterns

Dynamically constructed workflow names are not discoverable by static analysis. If the name property in Workflow.make({ name: ... }) is not a string literal or template literal without substitutions, Sideffect will skip that export entirely and it will not appear in the generated bindings or type file.
// ❌ Cannot be discovered — name is a runtime variable
const workflowName = getWorkflowName();
export const myLayer = Workflow.make({ name: workflowName }).toLayer(...);

// ✅ Discoverable — name is a string literal
export const myLayer = Workflow.make({ name: "my-workflow" }).toLayer(...);
If you need a dynamically named workflow, register it manually via the config.workflows array in your withCloudflareWorkflows options and export the WorkflowEntrypoint class yourself.

Configuring workflowPaths

The workflowPaths option accepts an array of file or directory paths relative to your project root (or to the directory containing your Wrangler config if you pass configPath).
withCloudflareWorkflows(cloudflare, {
  workflowPaths: [
    "src/workflows",            // directory — scanned recursively
    "src/jobs/billing.ts",      // single file
    "../shared/src/workflows.ts", // file outside the project root
  ],
})
When a path points to a directory, Sideffect recursively reads every file whose extension is one of .ts, .tsx, .mts, .cts, .js, .jsx, .mjs, or .cjs, skipping declaration files (.d.ts, .d.mts, .d.cts). When a path points to a file, that file is read directly without any directory traversal. Sideffect also follows barrel re-exports. If a file contains export * from "./other-file" or export { myLayer } from "./other-file" pointing at a local module, the referenced module is scanned for workflow layers automatically.

TypeScript dependency

Workflow discovery uses the TypeScript compiler API (ts.createSourceFile) to parse source files. The typescript package must be installed as a dev dependency in the project that uses sideffect/vite. Runtime usage of sideffect and sideffect/cloudflare in the Worker itself is unaffected and does not require TypeScript at runtime.
npm install -D typescript

Using collectWorkflowEntries directly

collectWorkflowEntries is exported from sideffect/vite for cases where you need to run workflow discovery outside of the Vite plugin — for example, in a custom build script or a test helper that asserts which workflows are registered.
import { collectWorkflowEntries } from "sideffect/vite";

const workflows = collectWorkflowEntries(["src/workflows"], process.cwd());

for (const workflow of workflows) {
  console.log(workflow.config.binding, "→", workflow.config.class_name);
}
The function accepts the same workflowPaths array and an optional baseDirectory (defaults to process.cwd()). It returns the same CapturedSideffectWorkflow[] array that withCloudflareWorkflows uses internally when writing bindings and the sideffect-env.d.ts type file.

Build docs developers (and LLMs) love