Skip to content

API reference

This page describes the Worker Loader binding API, assuming you have configured such a binding as env.LOADER.

load

env.LOADER.load(code WorkerCode ) WorkerStub

Loads a Worker from the provided WorkerCode and returns a WorkerStub which may be used to invoke the Worker.

Unlike get(), load() does not cache by ID. Each call creates a fresh Worker.

Use load() when the code is always new, such as for one-off AI-generated tool calls.

get

env.LOADER.get(id string , getCodeCallback () => Promise<WorkerCode> ): WorkerStub

Loads a Worker with the given ID, returning a WorkerStub which may be used to invoke the Worker.

As a convenience, the loader implements caching of isolates. When a new ID is seen the first time, a new isolate is loaded. But, the isolate may be kept warm in memory for a while. If later invocations of the loader request the same ID, the existing isolate may be returned again, rather than create a new one. But there is no guarantee: a later call with the same ID may instead start a new isolate from scratch.

Whenever the system determines it needs to start a new isolate, and it does not already have a copy of the code cached, it will invoke codeCallback to get the Worker's code. This is an async callback, so the application can load the code from remote storage if desired. The callback returns a WorkerCode object (described below).

Because of the caching, you should ensure that the callback always returns exactly the same content, when called for the same ID. If anything about the content changes, you must use a new ID. But if the content hasn't changed, it's best to reuse the same ID in order to take advantage of caching. If the WorkerCode is different every time, you can pass a random ID.

You could, for example, use IDs of the form <worker-name>:<version-number>, where the version number increments every time the code changes. Or, you could compute IDs based on a hash of the code and config, so that any change results in a new ID.

get() returns a WorkerStub, which can be used to send requests to the loaded Worker. Note that the stub is returned synchronously—you do not have to await it. If the Worker is not loaded yet, requests made to the stub will wait for the Worker to load before being delivered. If loading fails, the request will throw an exception.

It is never guaranteed that two requests will go to the same isolate. Even if you use the same WorkerStub to make multiple requests, they could execute in different isolates. The callback passed to loader.get() could be called any number of times (although it is unusual for it to be called more than once).

WorkerCode

This is the structure returned by getCodeCallback to represent a worker.

compatibilityDate string

The compatibility date for the Worker. This has the same meaning as the compatibility_date setting in a Wrangler config file.

compatibilityFlags string[] Optional

An optional list of compatibility flags augmenting the compatibility date. This has the same meaning as the compatibility_flags setting in a Wrangler config file.

allowExperimental boolean Optional

If true, then experimental compatibility flags will be permitted in compatibilityFlags. In order to set this, the worker calling the loader must itself have the compatibility flag "experimental" set. Experimental flags cannot be enabled in production.

mainModule string

The name of the Worker's main module. This must be one of the modules listed in modules.

modules Record<string, string | Module>

A dictionary object mapping module names to their string contents. If the module content is a plain string, then the module name must have a file extension indicating its type: either .js or .py.

A module's content can also be specified as an object, in order to specify its type independent from the name. The allowed objects are:

  • {js: string}: A JavaScript module, using ES modules syntax for imports and exports.
  • {cjs: string}: A CommonJS module, using require() syntax for imports.
  • {py: string}: A Python module. See warning below.
  • {text: string}: An importable string value.
  • {data: ArrayBuffer}: An importable ArrayBuffer value.
  • {json: object}: An importable object. The value must be JSON-serializable. However, note that value is provided as a parsed object, and is delivered as a parsed object; neither side actually sees the JSON serialization.

globalOutbound ServiceStub | null Optional

Controls whether the dynamic Worker has access to the network. The global fetch() and connect() functions (for making HTTP requests and TCP connections, respectively) can be blocked or redirected to isolate the Worker.

If globalOutbound is not specified, the default is to inherit the parent's network access, which usually means the dynamic Worker will have full access to the public Internet.

If globalOutbound is null, then the dynamic Worker will be totally cut off from the network. Both fetch() and connect() will throw exceptions.

globalOutbound can also be set to any service binding, including service bindings in the parent worker's env as well as loopback bindings from ctx.exports.

Using ctx.exports is particularly useful as it allows you to customize the binding further for the specific sandbox, by setting the value of ctx.props that should be passed back to it. The props can contain information to identify the specific dynamic Worker that made the request.

For example:

JavaScript
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);
},
};

env object

The environment object to provide to the dynamic Worker.

Using this, you can provide custom bindings to the Worker.

env is serialized and transferred into the dynamic Worker, where it is used directly as the value of env there. It may contain:

The second point is the key to creating custom bindings: you can define a binding with any arbitrary API, by defining a WorkerEntrypoint class implementing an RPC API, and then giving it to the dynamic Worker as a Service Binding.

Moreover, by using ctx.exports loopback bindings, you can further customize the bindings for the specific dynamic Worker by setting ctx.props, just as described for globalOutbound, above.

JavaScript
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);
},
};

tails ServiceStub[] Optional

You may specify one or more Tail Workers which will observe console logs, errors, and other details about the dynamically-loaded worker's execution. A tail event will be delivered to the Tail Worker upon completion of a request to the dynamically-loaded Worker. As always, you can implement the Tail Worker as an alternative entrypoint in your parent Worker, referring to it using ctx.exports:

JavaScript
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),
});
}
}