Deep dive into the internals of how Workflow SDK directives transform your code.
This is an advanced guide that dives into internals of the Workflow SDK directive and is not required reading to use workflows. To simply use the Workflow SDK, check out the getting started guides for your framework.
Workflows use special directives to mark code for transformation by the Workflow SDK compiler. This page explains how "use workflow" and "use step" directives work, what transformations are applied, and why they're necessary for durable execution.
Workflows use two directives to mark functions for special handling:
export async function handleUserSignup(email: string) {
"use workflow";
const user = await createUser(email);
await sendWelcomeEmail(user);
return { userId: user.id };
}
async function createUser(email: string) {
"use step";
return { id: crypto.randomUUID(), email };
}Key directives:
"use workflow": Marks a function as a durable workflow entry point"use step": Marks a function as an atomic, retryable step
These directives trigger the @workflow/swc-plugin compiler to transform your code in different ways depending on the execution context.
The compiler operates in three distinct modes, transforming the same source code differently for each execution context:
Comparison Table
| Mode | Used In | Purpose | Output API Route | Required? |
|---|---|---|---|---|
| Step | Build time | Bundles step handlers | .well-known/workflow/v1/step | Yes |
| Workflow | Build time | Bundles workflow orchestrators | .well-known/workflow/v1/flow | Yes |
| Client | Build/Runtime | Provides workflow IDs and types to start | Your application code | Optional* |
* Client mode is recommended for better developer experience—it provides automatic ID generation and type safety. Without it, you must manually construct workflow IDs or use the build manifest.
When you build your application, the Workflow SDK generates three handler files in .well-known/workflow/v1/:
flow.js
Contains all workflow functions transformed in workflow mode. This file is imported by your framework to handle workflow execution requests at POST /.well-known/workflow/v1/flow.
How it's structured:
All workflow code is bundled together and embedded as a string inside flow.js. When a workflow needs to execute, this bundled code is run inside a Node.js VM (virtual machine) to ensure:
- Determinism: The same inputs always produce the same outputs
- Side-effect prevention: Direct access to Node.js APIs, file system, network, etc. is blocked
- Sandboxed execution: Workflow orchestration logic is isolated from the main runtime
Build-time validation:
The workflow mode transformation validates your code during the build:
- Catches invalid Node.js API usage (like
fs,http,child_process) - Prevents imports of modules that would break determinism
Most invalid patterns cause build-time errors, catching issues before deployment.
What it does:
- Exports a
POSThandler that accepts Web standardRequestobjects - Executes bundled workflow code inside a Node.js VM for each request
- Handles workflow execution, replay, and resumption
- Returns execution results to the orchestration layer
Why a VM? Workflow functions must be deterministic to support replay. The VM sandbox prevents accidental use of non-deterministic APIs or side effects. All side effects should be performed in step functions instead.
step.js
Contains all step functions transformed in step mode. This file is imported by your framework to handle step execution requests at POST /.well-known/workflow/v1/step.
What it does:
- Exports a
POSThandler that accepts Web standardRequestobjects - Executes individual steps with full runtime access
- Returns step results to the orchestration layer
webhook.js
Contains webhook handling logic for delivering external data to running workflows via createWebhook().
What it does:
- Exports a
POSThandler that accepts webhook payloads - Validates tokens and routes data to the correct workflow run
- Resumes workflow execution after webhook delivery
Note: The webhook file structure varies by framework. Next.js generates webhook/[token]/route.js to leverage App Router's dynamic routing, while other frameworks generate a single webhook.js or webhook.mjs handler.
The multi-mode transformation enables the Workflow SDK's durable execution model:
- Step Mode (required) - Bundles executable step functions that can access the full runtime
- Workflow Mode (required) - Creates orchestration logic that can replay from event logs
- Client Mode (optional) - Prevents direct execution and enables type-safe workflow references
This separation allows:
- Deterministic replay: Workflows can be safely replayed from event logs without re-executing side effects
- Sandboxed orchestration: Workflow logic runs in a controlled VM without direct runtime access
- Stateless execution: Your compute can scale to zero and resume from any point in the workflow
- Type safety: TypeScript works seamlessly with workflow references (when using client mode)
A key aspect of the transformation is maintaining deterministic replay for workflow functions.
Workflow functions must be deterministic:
- Same inputs always produce the same outputs
- No direct side effects (no API calls, no database writes, no file I/O)
- Can use seeded random/time APIs provided by the VM (
Math.random(),Date.now(), etc.)
Because workflow functions are deterministic and have no side effects, they can be safely re-run multiple times to calculate what the next step should be. This is why workflow function bodies remain intact in workflow mode—they're pure orchestration logic.
Step functions can be non-deterministic:
- Can make API calls, database queries, etc.
- Have full access to Node.js runtime and APIs
- Results are cached in the event log after first execution
Learn more about Workflows and Steps.
The compiler generates stable IDs for workflows and steps based on file paths and function names:
Pattern: {type}//{filepath}//{functionName}
Examples:
workflow//workflows/user-signup.js//handleUserSignupstep//workflows/user-signup.js//createUserstep//workflows/payments/checkout.ts//processPayment
Key properties:
- Stable: IDs don't change unless you rename files or functions
- Unique: Each workflow/step has a unique identifier
- Portable: Works across different runtimes and deployments
Although IDs can change when files are moved or functions are renamed, Workflow SDK functions assume atomic versioning in the World. This means changing IDs won't break old workflows from running, but will prevent runs from being upgraded and will cause your workflow/step names to change in observability across deployments.
These transformations are framework-agnostic—they output standard JavaScript that works anywhere.
For users: Your framework handles all transformations automatically. See the Getting Started guide for your framework.
For framework authors: Learn how to integrate these transformations into your framework in Building Framework Integrations.
If you need to debug transformation issues, you can inspect the generated files:
- Look in
.well-known/workflow/v1/: Check the generatedflow.js,step.js,webhook.js, and other emitted debug files. - Check build logs: Most frameworks log transformation activity during builds
- Verify directives: Ensure
"use workflow"and"use step"are the first statements in functions - Check file locations: Transformations only apply to files in configured source directories






















