---
title: How the Directives Work
description: Deep dive into the internals of how Workflow SDK directives transform your code.
docs_index: /llms.txt
lastUpdated: 2026-10-20
type: conceptual
summary: Learn how the compiler transforms directive-annotated code into three execution modes.
prerequisites:
  - /docs/how-it-works/understanding-directives
related:
  - /docs/foundations/workflows-and-steps
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

> This is an advanced guide that dives into internals of the Workflow SDK directive and is not required reading to use workflows. To use the Workflow SDK, check out the [getting started](/v4/docs/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.

## Directives overview

Workflows use two directives to mark functions for special handling:

```typescript
export async function handleUserSignup(email: string) {
  "use workflow"; // [!code highlight]

  const user = await createUser(email);
  await sendWelcomeEmail(user);

  return { userId: user.id };
}

async function createUser(email: string) {
  "use step"; // [!code highlight]

  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 three transformation modes

The compiler operates in three distinct modes, transforming the same source code differently for each execution context:

```mermaid
flowchart LR
A["Source Code<br/>with directives"] --> B["Step Mode"]
A --> C["Workflow Mode"]
A --> D["Client Mode"]
B --> E["step.js<br/>(Step Execution)"]
C --> F["flow.js<br/>(Workflow Execution)"]
D --> G["Your App Code<br/>(Enables `start`)"]
```

### 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.

## Detailed transformation examples

#### Step Mode

**Step Mode** creates the step execution bundle served at `/.well-known/workflow/v1/step`.

**Input:**

```typescript
export async function createUser(email: string) {
  "use step";
  return { id: crypto.randomUUID(), email };
}
```

**Output:**

```typescript
export async function createUser(email: string) {
  return { id: crypto.randomUUID(), email };
}
(function(__wf_fn, __wf_id) { // [!code highlight]
    var __wf_sym = Symbol.for("@workflow/core//registeredSteps"), __wf_reg = globalThis[__wf_sym] || (globalThis[__wf_sym] = new Map()); // [!code highlight]
    __wf_reg.set(__wf_id, __wf_fn); // [!code highlight]
    __wf_fn.stepId = __wf_id; // [!code highlight]
})(createUser, "step//workflows/user.js//createUser"); // [!code highlight]
```

**What happens:**

- The `"use step"` directive is removed.
- The function body is kept completely intact (no transformation).
- The function is registered with the runtime via an inline immediately invoked function expression (IIFE), with no imports needed.
- Step functions run with full Node.js, Deno, or Bun access.

**Why no transformation?** Step functions execute in your main runtime with full access to Node.js APIs, file system, databases, etc. They don't need any special handling; they run normally.

**ID Format:** Step IDs follow the pattern `step//{filepath}//{functionName}`, where the filepath is relative to your project root.

#### Workflow Mode

**Workflow Mode** creates the workflow execution bundle served at `/.well-known/workflow/v1/flow`.

**Input:**

```typescript
export async function createUser(email: string) {
  "use step";
  return { id: crypto.randomUUID(), email };
}

export async function handleUserSignup(email: string) {
  "use workflow";
  const user = await createUser(email);
  return { userId: user.id };
}
```

**Output:**

```typescript
export async function createUser(email: string) {
  return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//workflows/user.js//createUser")(email); // [!code highlight]
}

export async function handleUserSignup(email: string) {
  const user = await createUser(email);
  return { userId: user.id };
}
handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
```

**What happens:**

- Step function bodies are **replaced** with calls to `globalThis[Symbol.for("WORKFLOW_USE_STEP")]`.
- Workflow function bodies remain **intact**; they execute deterministically during replay.
- The workflow function gets a `workflowId` property for runtime identification.
- The `"use workflow"` directive is removed.

**Why this transformation?** When a workflow executes, it needs to replay past steps from the [event log](/v4/docs/how-it-works/event-sourcing) rather than re-executing them. The `WORKFLOW_USE_STEP` symbol is a special runtime hook that:

1. Checks if the step has already been executed (in the event log)
2. If yes: Returns the cached result
3. If no: Triggers a suspension and enqueues the step for background execution

**ID Format:** Workflow IDs follow the pattern `workflow//{filepath}//{functionName}`. The `workflowId` property is attached to the function to allow [`start()`](/v4/docs/api-reference/workflow-api/start) to work at runtime.

#### Client Mode

**Client Mode** transforms workflow functions in your application code to prevent direct execution.

**Input:**

```typescript
export async function handleUserSignup(email: string) {
  "use workflow";
  const user = await createUser(email);
  return { userId: user.id };
}
```

**Output:**

```typescript
export async function handleUserSignup(email: string) {
  throw new Error("You attempted to execute ..."); // [!code highlight]
}
handleUserSignup.workflowId = "workflow//workflows/user.js//handleUserSignup"; // [!code highlight]
```

**What happens:**

- Workflow function bodies are **replaced** with an error throw.
- The `workflowId` property is added (same as workflow mode).
- Step functions are not transformed in client mode.

**Why this transformation?** Workflow functions cannot be called directly from application code; they must be started using [`start()`](/v4/docs/api-reference/workflow-api/start). The error prevents accidental direct execution while the `workflowId` property allows the `start()` function to identify which workflow to launch.

The IDs are generated exactly like in workflow mode to ensure they can be directly referenced at runtime.

> **Client mode is optional:** While recommended for better developer experience (automatic IDs and type safety), you can skip client mode and instead:
>
> - Manually construct workflow IDs using the pattern `workflow//{filepath}//{functionName}`
> - Use the workflow manifest file generated during build to lookup IDs
> - Pass IDs directly to `start()` as strings
>
> All framework integrations include client mode as a loader by default.

## Generated files

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, the file system, and the network 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 `POST` handler that accepts Web standard `Request` objects
- 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](/v4/docs/foundations/workflows-and-steps#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 `POST` handler that accepts Web standard `Request` objects
- 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()`](/v4/docs/api-reference/workflow/create-webhook).

**What it does:**

- Exports a `POST` handler 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 use App Router's dynamic routing, while other frameworks generate a single `webhook.js` or `webhook.mjs` handler.

## Why three modes?

The multi-mode transformation enables the Workflow SDK's durable execution model:

1. **Step mode** (required): Bundles executable step functions that can access the full runtime.
2. **Workflow mode** (required): Creates orchestration logic that can replay from event logs.
3. **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 with workflow references (when using client mode).

## Determinism and replay

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](/v4/docs/how-it-works/event-sourcing) after first execution

Learn more about [Workflows and Steps](/v4/docs/foundations/workflows-and-steps).

## ID generation

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//handleUserSignup`
- `step//workflows/user-signup.js//createUser`
- `step//workflows/payments/checkout.ts//processPayment`

**Key properties:**

- **Stable**: IDs don't change unless you rename files or functions.
- **Unique**: Each workflow or step has a unique identifier.
- **Portable**: IDs work across different runtimes and deployments.

> Although IDs can change when files are moved or functions are renamed, Workflow SDK functions assume [atomic versioning](/v4/docs/foundations/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.

## Framework integration

These transformations are framework-agnostic; they output standard JavaScript that works anywhere.

**For users**: Your framework handles all transformations automatically. See the [Getting Started](/v4/docs/getting-started) guide for your framework.

**For framework authors**: Learn how to integrate these transformations into your framework in [Building Framework Integrations](/v4/docs/how-it-works/framework-integrations).

## Debugging transformed code

If you need to debug transformation issues, you can inspect the generated files:

1. **Look in `.well-known/workflow/v1/`**: Check the generated `flow.js`, `step.js`, `webhook.js`, and other emitted debug files.
2. **Check build logs**: Most frameworks log transformation activity during builds.
3. **Verify directives**: Ensure `"use workflow"` and `"use step"` are the first statements in functions.
4. **Check file locations**: Transformations only apply to files in configured source directories.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)