Configure
Add a Worker Loader binding to thewrangler.json file:
wrangler.json
binding key. Deploy refuses limits and tails in this entry. Set them in the code object that you load instead.
Load and call code
Callenv.LOADER.get with an ID and a function that returns the code. Then call an entrypoint of the loaded Worker:
env.LOADER.get(id, getCode): compiles the code at the first use of the ID in an isolate. Later calls with the same ID use the compiled code. ThegetCodefunction can be async, and it runs only to compile. An error ingetCodeappears at the first call.env.LOADER.load(code): compiles the code at each call, with no cache.stub.dispose(): releases a loaded Worker and its ID.
The code object
The following table lists the fields of the object thatgetCode returns, or that you pass to load:
celld refuses the
allowExperimental field.
Call entrypoints
A loaded Worker exposes the following entrypoints:worker.getEntrypoint()returns a Fetcher for the default export.worker.getEntrypoint("NAME", { props, limits })returns an RPC stub for aWorkerEntrypointexport namedNAME. Thepropsvalue becomesthis.ctx.propsin the loaded code.worker.getDurableObjectClass("NAME", { props })returns a class that you can run as a facet. For more information, see Durable Object facets.
Run user code under the CPU limit
Top-level code of a module can run outside thelimits.cpuMs limit. A loop at the top level of user code can then use a CPU core with no limit. To prevent this, don’t import user code statically. Load your own runner module with the user code, and import the user code dynamically inside the limited call:
runner.js
- Define a clear contract, for example “export default a function that takes the input and returns JSON.”
- Put each call in a
try...catchblock. User code throws, times out, and reaches limits. Return a 4xx response for these errors, not a 500. - Set your own timeouts.
limits.cpuMsstops a CPU loop, but not a wait on a timer. To stop afetch()to a loaded Worker, passsignal: AbortSignal.timeout(ms). To stop an RPC call, race it against your own timer. The loaded code continues to run until it ends. - Before you save user code, do a test load and a test call. Return syntax errors to the user.
Give the code capabilities
The loaded isolate has noenv.DB, no env.KV, and no Secrets. To give access to one of them, export a WorkerEntrypoint class from your App, and pass a stub with props in the env field of the code object. Each call from the loaded code comes back into your code, where you can check it:
index.js
ctx value is the ctx argument of the fetch handler, or this.ctx in a Durable Object:
Control network access
TheglobalOutbound field sets what the loaded code can reach:
null: no network.fetch()andconnect()throw. Use this value for untrusted code.- Omitted: the loaded code gets the network access of your App. Use this option only for trusted code.
- A Fetcher: each
fetchfrom the loaded code goes to that Fetcher, which can check it against an allowlist. For example, passctx.exports.Egress({ props: { tenant } }), whereEgressis aWorkerEntrypointclass with afetch(request)method. With a Fetcher,connect()and WebSockets always throw.
Read logs from loaded code
console.log calls in loaded code don’t go to the App logs. To collect them, export a tail entrypoint and pass it in the tails field:
index.js
Limits
The following table lists the limits of Dynamic Workers:
A load over the limit throws. Use stable IDs and the
dispose method. Don’t call load for each request.
Loaded Workers end with the isolate that loaded them. After a deploy, celld compiles them again.
Security boundary
The boundary is a separate V8 isolate in the same process. It isn’t a virtual machine. Always setlimits. For untrusted code, also set globalOutbound to null.
Differences from Cloudflare
Keep the following celld behavior in mind when you port Dynamic Worker code from Cloudflare:- The
worker_loadersentry accepts onlybinding. Setlimitsandtailsin the code object. - celld refuses the
allowExperimentalfield. - Top-level module code can run outside
limits.cpuMs. Import user code dynamically inside the limited call. console.login loaded code doesn’t reach the App logs. Use a tail.- A process holds a maximum of 256 live Dynamic Workers.