Worker Loader バインディングを使うと、任意のコードを含む追加の Worker を実行時に読み込めます。
Isolate(分離コンテキスト)は軽量なコンテナーのようなものです。Workers プラットフォームは、コンテナーや VM の代わりに Isolate を使います。そのため、すべての Worker はすでに Isolate 内で動いています。一方、Worker Loader バインディングを使うと、任意のコードをオンデマンドで読み込む追加の Isolate を、自分の Worker から作成できます。
Isolate はコンテナーよりはるかに安価です。ミリ秒単位で起動でき、コード断片を実行してすぐに破棄しても問題ありません。コンテナーのように、Isolate をプールしたり、ウォームな Isolate を再利用したりする必要はありません。
Worker Loader はコードの サンドボックス化 も可能です。コードが実行できることを厳しく制限できます。特に次ができます。
- 内部の Worker が行うすべてのネットワークリクエストを傍受するか、単純にブロックできます。
- サンドボックス化した Worker に、アクセスを許可する特定リソースを表すカスタムバインディングを渡せます。
サンドボックスを適切に設定すれば、信頼できないコードでも動的 Isolate で安全に実行できます。
Dynamic Worker Loader の主な用途の 1 つが、Agents SDK の Code Mode です。Code Mode はツールを型付き TypeScript API に変換し、LLM に「コードを書く」ツールを 1 つ渡します。生成されたコードは隔離された Worker サンドボックスで動き、AI エージェントは 1 回の実行で複数のツール呼び出しを連鎖でき、モデル経由の往復を減らせます。
Code Mode では、AI SDK のツール、MCP クライアント接続 のツール、OpenAPI オペレーション を、モデルが書いたコードから利用できます。
Worker Loader は get() メソッドだけを持つバインディングです。Isolate を読み込みます。使用例は次のとおりです。
let id = "foo";
// Get the isolate with the given ID, creating it if no such isolate exists yet.
let worker = env.LOADER.get(id, async () => {
// If the isolate does not already exist, this callback is invoked to fetch
// the isolate's Worker code.
return {
compatibilityDate: "2025-06-01",
// Specify the worker's code (module files).
mainModule: "foo.js",
modules: {
"foo.js":
"export default {\n" +
" fetch(req, env, ctx) { return new Response('Hello'); }\n" +
"}\n",
},
// Specify the dynamic Worker's environment (`env`). This is specified
// as a JavaScript object, exactly as you want it to appear to the
// child Worker. It can contain basic serializable types as well as
// Service Bindings (see below).
env: {
SOME_ENV_VAR: 123,
},
// To block the worker from talking to the internet using `fetch()` or
// `connect()`, set `globalOutbound` to `null`. You can also set this
// to any service binding, to have calls be intercepted and redirected
// to that binding.
globalOutbound: null,
};
});
// Now you can get the Worker's entrypoint and send requests to it.
let defaultEntrypoint = worker.getEntrypoint();
await defaultEntrypoint.fetch("http://example.com");
// You can get non-default entrypoints as well, and specify the
// `ctx.props` value to be delivered to the entrypoint.
let someEntrypoint = worker.getEntrypoint("SomeEntrypointClass", {
props: { someProp: 123 },
});動的 Worker Loader バインディングを Worker に追加するには、Wrangler 設定に次のように追加します。
{
"worker_loaders": [
{
"binding": "LOADER",
},
],
}[[worker_loaders]]
binding = "LOADER"get(id string, getCodeCallback () => Promise<WorkerCode> ): WorkerStub
指定した ID の Worker を読み込み、Worker を呼び出すために使える WorkerStub を返します。
便宜上、ローダーは Isolate をキャッシュします。新しい ID を初めて見ると、新しい Isolate を読み込みます。その Isolate はしばらくメモリ上でウォームに保たれることがあります。あとから同じ ID をリクエストすると、新しい Isolate を作らず、既存の Isolate が返ることがあります。ただし保証はありません。同じ ID での後続の呼び出しが、最初から新しい Isolate を起動することもあります。
システムが新しい Isolate の起動が必要だと判断し、コードのキャッシュ済みコピーを持っていない場合、codeCallback を呼び出して Worker のコードを取得します。これは非同期コールバックなので、必要ならリモートストレージからコードを読み込めます。コールバックは WorkerCode オブジェクト(後述)を返します。
キャッシュがあるため、同じ ID に対してコールバックは常にまったく同じ内容を返すようにしてください。内容が少しでも変わる場合は、新しい ID を使う必要があります。内容が変わっていない場合は、キャッシュを活かすために同じ ID を再利用するのが望ましいです。WorkerCode が毎回異なる場合は、ランダムな ID を渡せます。
たとえば、コードが変わるたびにバージョン番号を増やす <worker-name>:<version-number> 形式の ID を使えます。または、コードと設定のハッシュから ID を計算し、変更があれば新しい ID になるようにできます。
get() は、読み込んだ Worker へリクエストを送るために使える WorkerStub を返します。スタブは同期的に返されます。await する必要はありません。Worker がまだ読み込まれていなくても、スタブへのリクエストは Worker の読み込み完了を待ってから配信されます。読み込みに失敗すると、リクエストは例外をスローします。
2 つのリクエストが同じ Isolate に行く保証は一切ありません。同じ WorkerStub で複数回リクエストしても、別の Isolate で実行されることがあります。loader.get() に渡したコールバックは、何回呼び出されても構いません(2 回以上呼ばれることは通常はありません)。
getCodeCallback が返す、Worker を表す構造です。
Worker の 互換性日付 です。Wrangler 設定ファイルの compatibility_date と同じ意味です。
互換性日付を補う、省略可能な compatibility flags の一覧です。Wrangler 設定ファイルの compatibility_flags と同じ意味です。
true の場合、compatibilityFlags に実験的な compatibility flags を含められます。これを設定するには、ローダーを呼び出す Worker 自体に compatibility flag "experimental" が必要です。実験的フラグは本番では有効にできません。
Worker のメインモジュール名です。modules に列挙したモジュールのいずれかである必要があります。
モジュール名を文字列の内容へ対応付ける辞書オブジェクトです。モジュール内容がプレーンな文字列の場合、モジュール名には型を示す拡張子(.js または .py)が必要です。
モジュール内容はオブジェクトとしても指定でき、名前とは独立して型を指定できます。使えるオブジェクトは次のとおりです。
{js: string}: JavaScript モジュール。import と export は ES modules 構文を使います。{cjs: string}: CommonJS モジュール。import はrequire()構文を使います。{py: string}: Python モジュール。ただし、次の警告を参照してください。{text: string}: import 可能な文字列値です。{data: ArrayBuffer}: import 可能なArrayBuffer値です。{json: object}: import 可能なオブジェクトです。値は JSON シリアライズ可能である必要があります。ただし、値はパース済みオブジェクトとして渡され、パース済みオブジェクトとして届きます。どちらの側も、実際の JSON シリアライズは見えません。
動的 Worker がネットワークにアクセスできるかどうかを制御します。グローバルの fetch() と connect()(それぞれ HTTP リクエストと TCP 接続用)をブロックまたはリダイレクトし、Worker を隔離できます。
globalOutbound を指定しない場合のデフォルトは、親のネットワークアクセスを継承することです。通常、動的 Worker はパブリックインターネットへフルアクセスできます。
globalOutbound が null の場合、動的 Worker はネットワークから完全に切り離されます。fetch() と connect() はどちらも例外をスローします。
globalOutbound には任意のサービスバインディングも設定できます。親 Worker の env にあるサービスバインディングや、ctx.exports からのループバックバインディング を含みます。
ctx.exports は特に便利です。サンドボックスへ戻すべき ctx.props の値を設定し、そのサンドボックス向けにバインディングをさらにカスタマイズできます。props には、リクエストを行った特定の動的 Worker を識別する情報を含められます。
例:
import { WorkerEntrypoint } from "cloudflare:workers";
export class Greeter extends WorkerEntrypoint {
fetch(request) {
return new Response(`Hello, ${this.ctx.props.name}!`);
}
}
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
// Redirect the worker's global outbound to send all requests
// to the `Greeter` class, filling in `ctx.props.name` with
// the name "Alice", so that it always responds "Hello, Alice!".
globalOutbound: ctx.exports.Greeter({ props: { name: "Alice" } }),
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};動的 Worker に渡す環境オブジェクトです。
これを使い、Worker にカスタムバインディングを提供できます。
env はシリアライズされて動的 Worker へ転送され、そこでの env の値としてそのまま使われます。含められるものは次のとおりです。
2 つ目が、カスタムバインディングを作る鍵です。RPC API を実装する WorkerEntrypoint クラス を定義し、Service Binding として動的 Worker に渡せば、任意の API を持つバインディングを定義できます。
さらに、ctx.exports のループバックバインディングを使い、ctx.props を設定すれば、上の globalOutbound と同じように、特定の動的 Worker 向けにバインディングをカスタマイズできます。
import { WorkerEntrypoint } from "cloudflare:workers";
// Implement a binding which can be called by the dynamic Worker.
export class Greeter extends WorkerEntrypoint {
greet() {
return `Hello, ${this.ctx.props.name}!`;
}
}
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
env: {
// Provide a binding which has a method greet() which can be called
// to receive a greeting. The binding knows the Worker's name.
GREETER: ctx.exports.Greeter({ props: { name: "Alice" } }),
},
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};動的に読み込んだ Worker の実行に関するコンソールログ、エラー、そのほかの詳細を観測する Tail Workers を 1 つ以上指定できます。動的に読み込んだ Worker へのリクエストが完了すると、Tail Worker へ tail イベントが配信されます。いつものように、Tail Worker は親 Worker の別エントリポイントとして実装し、ctx.exports で参照できます。
import { WorkerEntrypoint } from "cloudflare:workers";
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
// Send logs, errors, etc. to `LogTailer`. We pass `name` in the
// `ctx.props` so that `LogTailer` knows what generated the logs.
// (You can pass anything you want in `props`.)
tails: [ctx.exports.LogTailer({ props: { name: "alice" } })],
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};
export class LogTailer extends WorkerEntrypoint {
async tail(events) {
let name = this.ctx.props.name;
// Send the logs off to our log endpoint, specifying the worker name in
// the URL.
//
// Note that `events` will always be an array of size 1 in this scenario,
// describing the event delivered to the dynamically-loaded Worker.
await fetch(`https://example.com/submit-logs/${name}`, {
method: "POST",
body: JSON.stringify(events),
});
}
}