|
| 1 | +--- |
| 2 | +title: Dynamic Workflows |
| 3 | +description: Create and run Workflow logic dynamically while keeping Workflow steps durable. |
| 4 | +pcx_content_type: example |
| 5 | +sidebar: |
| 6 | + order: 3 |
| 7 | +--- |
| 8 | + |
| 9 | +import { TypeScriptExample, WranglerConfig } from "~/components"; |
| 10 | + |
| 11 | +Use this pattern when your Worker needs to create and run many different Workflows dynamically, especially one-off Workflows that are generated for a single task. |
| 12 | + |
| 13 | +For example, an AI agent might decide at runtime to create a Workflow with these steps: |
| 14 | + |
| 15 | +1. Summarize a support ticket |
| 16 | +2. Wait for human approval |
| 17 | +3. Send a follow-up message |
| 18 | + |
| 19 | +With Dynamic Workers and Workflows, the Worker Loader, which loads Dynamic Workers, can create that Workflow on the spot, run it durably, and let Workflows handle retries, sleeping, and waiting for events. You do not need to pre-register a separate Workflow class for every run. |
| 20 | + |
| 21 | +## When to use this pattern |
| 22 | + |
| 23 | +Use this pattern when your workflow logic is not known at deploy time. |
| 24 | + |
| 25 | +This is useful when: |
| 26 | + |
| 27 | +- An agent generates workflow code for a specific task |
| 28 | +- End customers create workflows in a multi-tenant application |
| 29 | +- You need many short-lived or one-off Workflows without deploying a new Worker for each one |
| 30 | + |
| 31 | +If your workflow logic is fixed, a standard Workflow is generally recommended. |
| 32 | + |
| 33 | +## Understand the model |
| 34 | + |
| 35 | +This setup has two parts: |
| 36 | + |
| 37 | +- **Worker Loader**: Worker which receives the request and creates (or loads) the Dynamic Worker. |
| 38 | +- **Workflow class**: Your registered Workflow class which receives the persisted params, reloads the same Dynamic Worker code, and calls its `run()` method with the `WorkflowStep` object. |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +Once the Worker Loader has [loaded the Dynamic Worker](/dynamic-workers/getting-started/#run-a-dynamic-worker), the Dynamic Worker calls `env.WORKFLOW.create()` to trigger an instance of the Workflow. The Worker Loader then stores the `workerId` of the Dynamic Worker in case the isolate spins down during the Workflow's exeuction. |
| 43 | + |
| 44 | +If the instance is long-running -- for example, if it includes a step.waitForEvent() -- then the Workflow engine may hibernate, and the Dynamic Worker isolate will shut down until the event occurs. When the Workflow resumes, the Worker Loader reloads its code using the workerId of the `Dynamic Worker`. |
| 45 | + |
| 46 | +Following its normal durable execution model, the workflow instance continues where it left off, and the Dynamic Worker executes the remaining steps. The instance reaches completion with all steps durably executed, and the Dynamic Worker stops running. |
| 47 | + |
| 48 | +## Configure your Worker |
| 49 | + |
| 50 | +Your Worker needs two things: |
| 51 | + |
| 52 | +- A Worker Loader binding |
| 53 | +- A Workflow binding that points to the Workflow class |
| 54 | + |
| 55 | +<WranglerConfig> |
| 56 | + |
| 57 | +```jsonc |
| 58 | +{ |
| 59 | + "name": "dynamic-workflow-loader", |
| 60 | + "main": "src/index.ts", |
| 61 | + "compatibility_date": "$today", |
| 62 | + "worker_loaders": [ |
| 63 | + { |
| 64 | + "binding": "LOADER", |
| 65 | + }, |
| 66 | + ], |
| 67 | + "workflows": [ |
| 68 | + { |
| 69 | + "name": "dynamic-workflow", |
| 70 | + "binding": "WORKFLOW", |
| 71 | + "class_name": "DynamicWorkflow", |
| 72 | + }, |
| 73 | + ], |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +</WranglerConfig> |
| 78 | + |
| 79 | +## Run the Dynamic Worker from your Workflow class |
| 80 | + |
| 81 | +Although the Workflow is not statically defined, you still deploy one normal Workflow class. Its job is to load the dynamic code for the current run and call it with the `WorkflowStep` object. This class is what makes the [Workflows API](/workflows/build/workers-api/), including `step.do()`, `step.sleep()`, and `step.waitForEvent()`, continue to work as normal. |
| 82 | + |
| 83 | +Use `env.LOADER.get(id, callback)` via the [Worker Loader API](/dynamic-workers/api-reference/#load) to create the Dynamic Worker. If the Dynamic Worker has already been created, the Worker Loader uses its `workerId` to reload the code and use the same Dynamic Worker. |
| 84 | + |
| 85 | +<TypeScriptExample> |
| 86 | + |
| 87 | +```ts |
| 88 | +import { |
| 89 | + WorkflowEntrypoint, |
| 90 | + type WorkflowStep, |
| 91 | + type WorkflowEvent, |
| 92 | +} from "cloudflare:workers"; |
| 93 | + |
| 94 | +type Params = { |
| 95 | + script: string; |
| 96 | +}; |
| 97 | + |
| 98 | +export class DynamicWorkflow extends WorkflowEntrypoint<Env, Params> { |
| 99 | + async run(event: WorkflowEvent<Params>, step: WorkflowStep) { |
| 100 | + const { script } = event.payload; |
| 101 | + |
| 102 | + const worker = this.env.LOADER.get( |
| 103 | + `dyn-wf-${event.instanceId}`, |
| 104 | + async () => ({ |
| 105 | + mainModule: "index.js", |
| 106 | + modules: { "index.js": script }, |
| 107 | + compatibilityDate: "2025-09-27", |
| 108 | + compatibilityFlags: ["nodejs_compat"], |
| 109 | + }), |
| 110 | + ); |
| 111 | + |
| 112 | + const entrypoint = worker.getEntrypoint() as unknown as { |
| 113 | + run(event: Record<string, unknown>, step: WorkflowStep): Promise<unknown>; |
| 114 | + }; |
| 115 | + const result = await entrypoint.run({}, step); |
| 116 | + |
| 117 | + return result; |
| 118 | + } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +<TypeScriptExample> |
| 123 | + |
| 124 | +```ts |
| 125 | +function loadDynamicWorkflow(env: Env, scriptId: string, script: string) { |
| 126 | + return env.LOADER.get(`dynamic-workflow:${scriptId}`, async () => ({ |
| 127 | + compatibilityDate: "$today", |
| 128 | + mainModule: "index.js", |
| 129 | + modules: { |
| 130 | + "index.js": script, |
| 131 | + }, |
| 132 | + globalOutbound: null, |
| 133 | + })); |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +</TypeScriptExample> |
| 138 | + |
| 139 | +## Write the dynamic workflow code |
| 140 | + |
| 141 | +The dynamic code defines the steps for one run. It is regular Worker code loaded at runtime. |
| 142 | + |
| 143 | +This example implements the support-ticket flow described earlier: |
| 144 | + |
| 145 | +1. Summarize the ticket |
| 146 | +2. Wait for approval |
| 147 | +3. Send a follow-up |
| 148 | + |
| 149 | +<TypeScriptExample> |
| 150 | + |
| 151 | +```ts |
| 152 | +import { WorkerEntrypoint } from "cloudflare:workers"; |
| 153 | + |
| 154 | +export class DynamicWorkflowLogic extends WorkerEntrypoint { |
| 155 | + async run(event, step) { |
| 156 | + const summary = await step.do("summarize ticket", async () => { |
| 157 | + const ticket = event.payload.ticket; |
| 158 | + return `Summary: ${ticket.subject}`; |
| 159 | + }); |
| 160 | + |
| 161 | + await step.waitForEvent("wait for approval", { |
| 162 | + type: "ticket-approved", |
| 163 | + timeout: "24 hours", |
| 164 | + }); |
| 165 | + |
| 166 | + return step.do("send follow-up", async () => { |
| 167 | + return { |
| 168 | + sent: true, |
| 169 | + message: `Approved follow-up sent for: ${summary}`, |
| 170 | + }; |
| 171 | + }); |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +</TypeScriptExample> |
| 177 | + |
| 178 | +## Related resources |
| 179 | + |
| 180 | +- [Workers API](/workflows/build/workers-api/) |
| 181 | +- [Trigger Workflows](/workflows/build/trigger-workflows/) |
| 182 | +- [Events and parameters](/workflows/build/events-and-parameters/) |
| 183 | +- [Dynamic Workers getting started](/dynamic-workers/getting-started/) |
| 184 | +- [Dynamic Worker Loaders](/workers/runtime-apis/bindings/worker-loader/) |
| 185 | +- [Bindings with Dynamic Workers](/dynamic-workers/usage/bindings/) |
0 commit comments