1---
2title: Worker Loader
3description: 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.
4sidebar:
5 order: 24
6---
7
8A Worker Loader lets a deployed Worker spin up other Workers at
9runtime from inline JavaScript source. Each loaded Worker runs in its
10own isolate with full sandboxing, which makes the binding the
11platform primitive for evaluating user-provided code, running
12untrusted 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.
17It registers the `worker_loader` binding on the deployed Worker and
18returns the runtime handle in one step. There is no layer to provide:
19
20```typescript
21// src/worker.ts
22import * as Cloudflare from "alchemy/Cloudflare";
23import * as Effect from "effect/Effect";
24import { HttpServerRequest } from "effect/http/HttpServerRequest";
25
26export 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
56map of module names to source strings, and returns a stub for the
57freshly loaded Worker. `worker.fetch` speaks Effect-native HTTP. It
58accepts an `HttpClientRequest` (or, as here, forwards the incoming
59server request) and resolves to an `HttpClientResponse`.
60
61## Sandbox the loaded Worker
62
63By default a dynamic Worker can make outbound `fetch` calls like any
64other Worker. Set `globalOutbound: null` to block its network access
65entirely:
66
67```typescript
68const 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
84Untrusted code now runs in an isolate that can compute but cannot
85exfiltrate. Passing a `Fetcher` (for example another Worker's service
86binding) instead of `null` routes every outbound request through that
87Worker, 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
92isolate with that name is already warm it is reused, and `getCode` is
93only invoked to (re)supply the source on a cold start. Pass `null`
94as the name for an anonymous, always-fresh Worker:
95
96```typescript
97const 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
105lives in R2 or KV and fetching it is itself effectful.
106
107## Call typed entrypoints
108
109If the dynamic Worker exports named entrypoints, `getEntrypoint`
110returns a typed stub whose methods ride the platform's native JSRPC
111channel:
112
113```typescript
114const worker = yield* loader.load({ ... });
115
116const api = worker.getEntrypoint<{
117 greet: (name: string) => Effect.Effect<string>;
118}>("api");
119
120const greeting = yield* api.greet("world");
121```
122
123The caller supplies the shape as a type argument since there is no
124statically bound class to infer it from. What makes a member callable
125and what crosses the wire is covered in
126[Schemaless RPC](/cloudflare/apis/schemaless-rpc).
127
128## Declare the binding as env metadata
129
130Workers whose `main` module exports a plain async `fetch` handler
131declare the loader on `env` instead and get Cloudflare's native
132`WorkerLoader` type through `InferEnv`:
133
134```typescript
135// alchemy.run.ts
136export const Worker = Cloudflare.Worker("Worker", {
137 main: "./src/worker.ts",
138 env: { LOADER: Cloudflare.WorkerLoader() },
139});
140
141export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
142```
143
144```typescript
145// src/worker.ts
146import type { WorkerEnv } from "../alchemy.run.ts";
147
148export 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`.