| 1 | --- |
| 2 | title: Worker Loader |
| 3 | description: Load and run ephemeral Workers at runtime from inline source — each in its own sandboxed isolate, with optional outbound-network control and typed RPC entrypoints. |
| 4 | sidebar: |
| 5 | order: 24 |
| 6 | --- |
| 7 | |
| 8 | A Worker Loader lets a deployed Worker spin up other Workers at |
| 9 | runtime from inline JavaScript source. Each loaded Worker runs in its |
| 10 | own isolate with full sandboxing, which makes the binding the |
| 11 | platform primitive for evaluating user-provided code, running |
| 12 | untrusted plugins, or executing Workers an AI agent just wrote. |
| 13 | |
| 14 | ## Bind a loader to a Worker |
| 15 | |
| 16 | `yield*` `Cloudflare.WorkerLoader(name)` in the Worker's Construction phase. |
| 17 | It registers the `worker_loader` binding on the deployed Worker and |
| 18 | returns the runtime handle in one step. There is no layer to provide: |
| 19 | |
| 20 | ```typescript |
| 21 | // src/worker.ts |
| 22 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 23 | import * as Effect from "effect/Effect"; |
| 24 | import { HttpServerRequest } from "effect/http/HttpServerRequest"; |
| 25 | |
| 26 | export default Cloudflare.Worker( |
| 27 | "LoaderWorker", |
| 28 | { main: import.meta.url }, |
| 29 | Effect.gen(function* () { |
| 30 | const loader = yield* Cloudflare.WorkerLoader("LOADER"); |
| 31 | |
| 32 | return { |
| 33 | fetch: Effect.gen(function* () { |
| 34 | const request = yield* HttpServerRequest; |
| 35 | |
| 36 | const worker = yield* loader.load({ |
| 37 | compatibilityDate: "2026-01-28", |
| 38 | mainModule: "worker.js", |
| 39 | modules: { |
| 40 | "worker.js": `export default { |
| 41 | async fetch() { |
| 42 | return Response.json({ ok: true }); |
| 43 | } |
| 44 | }`, |
| 45 | }, |
| 46 | }); |
| 47 | |
| 48 | return yield* worker.fetch(request).pipe(Effect.orDie); |
| 49 | }), |
| 50 | }; |
| 51 | }), |
| 52 | ); |
| 53 | ``` |
| 54 | |
| 55 | `loader.load()` takes a compatibility date, a main module name, and a |
| 56 | map of module names to source strings, and returns a stub for the |
| 57 | freshly loaded Worker. `worker.fetch` speaks Effect-native HTTP. It |
| 58 | accepts an `HttpClientRequest` (or, as here, forwards the incoming |
| 59 | server request) and resolves to an `HttpClientResponse`. |
| 60 | |
| 61 | ## Sandbox the loaded Worker |
| 62 | |
| 63 | By default a dynamic Worker can make outbound `fetch` calls like any |
| 64 | other Worker. Set `globalOutbound: null` to block its network access |
| 65 | entirely: |
| 66 | |
| 67 | ```typescript |
| 68 | const worker = yield* loader.load({ |
| 69 | compatibilityDate: "2026-01-28", |
| 70 | mainModule: "worker.js", |
| 71 | modules: { |
| 72 | "worker.js": `export default { |
| 73 | async fetch(req) { |
| 74 | // fetch() calls from here will fail |
| 75 | const result = (0, eval)(await req.text()); |
| 76 | return new Response(String(result)); |
| 77 | } |
| 78 | }`, |
| 79 | }, |
| 80 | globalOutbound: null, |
| 81 | }); |
| 82 | ``` |
| 83 | |
| 84 | Untrusted code now runs in an isolate that can compute but cannot |
| 85 | exfiltrate. Passing a `Fetcher` (for example another Worker's service |
| 86 | binding) instead of `null` routes every outbound request through that |
| 87 | Worker, so you can allowlist, rewrite, or log the traffic. |
| 88 | |
| 89 | ## Reuse a Worker by name |
| 90 | |
| 91 | `loader.get(name, getCode)` addresses a dynamic Worker by name: if an |
| 92 | isolate with that name is already warm it is reused, and `getCode` is |
| 93 | only invoked to (re)supply the source on a cold start. Pass `null` |
| 94 | as the name for an anonymous, always-fresh Worker: |
| 95 | |
| 96 | ```typescript |
| 97 | const worker = yield* loader.get("tenant-42", () => ({ |
| 98 | compatibilityDate: "2026-01-28", |
| 99 | mainModule: "worker.js", |
| 100 | modules: { "worker.js": tenantSource }, |
| 101 | })); |
| 102 | ``` |
| 103 | |
| 104 | `getCode` may also return an `Effect`, which is useful when the source |
| 105 | lives in R2 or KV and fetching it is itself effectful. |
| 106 | |
| 107 | ## Call typed entrypoints |
| 108 | |
| 109 | If the dynamic Worker exports named entrypoints, `getEntrypoint` |
| 110 | returns a typed stub whose methods ride the platform's native JSRPC |
| 111 | channel: |
| 112 | |
| 113 | ```typescript |
| 114 | const worker = yield* loader.load({ ... }); |
| 115 | |
| 116 | const api = worker.getEntrypoint<{ |
| 117 | greet: (name: string) => Effect.Effect<string>; |
| 118 | }>("api"); |
| 119 | |
| 120 | const greeting = yield* api.greet("world"); |
| 121 | ``` |
| 122 | |
| 123 | The caller supplies the shape as a type argument since there is no |
| 124 | statically bound class to infer it from. What makes a member callable |
| 125 | and what crosses the wire is covered in |
| 126 | [Schemaless RPC](/cloudflare/apis/schemaless-rpc). |
| 127 | |
| 128 | ## Declare the binding as env metadata |
| 129 | |
| 130 | Workers whose `main` module exports a plain async `fetch` handler |
| 131 | declare the loader on `env` instead and get Cloudflare's native |
| 132 | `WorkerLoader` type through `InferEnv`: |
| 133 | |
| 134 | ```typescript |
| 135 | // alchemy.run.ts |
| 136 | export const Worker = Cloudflare.Worker("Worker", { |
| 137 | main: "./src/worker.ts", |
| 138 | env: { LOADER: Cloudflare.WorkerLoader() }, |
| 139 | }); |
| 140 | |
| 141 | export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>; |
| 142 | ``` |
| 143 | |
| 144 | ```typescript |
| 145 | // src/worker.ts |
| 146 | import type { WorkerEnv } from "../alchemy.run.ts"; |
| 147 | |
| 148 | export default { |
| 149 | async fetch(req: Request, env: WorkerEnv) { |
| 150 | const worker = env.LOADER.get("demo", () => ({ |
| 151 | compatibilityDate: "2026-01-28", |
| 152 | mainModule: "worker.js", |
| 153 | modules: { |
| 154 | "worker.js": "export default { fetch: () => new Response('ok') }", |
| 155 | }, |
| 156 | })); |
| 157 | return worker.getEntrypoint().fetch(req); |
| 158 | }, |
| 159 | }; |
| 160 | ``` |
| 161 | |
| 162 | ## Where next |
| 163 | |
| 164 | - [Workers](/cloudflare/compute/workers) — the two-phase Worker model |
| 165 | the binding lives inside. |
| 166 | - [Schemaless RPC](/cloudflare/apis/schemaless-rpc) — typed calls into |
| 167 | dynamically loaded Workers. |
| 168 | - [Workers for Platforms](/cloudflare/compute/workers-for-platforms) — |
| 169 | deploy tenant Workers as persistent resources instead of loading |
| 170 | them at runtime. |
| 171 | - [WorkerLoader API reference](/providers/cloudflare/workers/workerloader) — |
| 172 | every option on `load` and `get`. |