Bringing State to Dynamically Loaded Workers

Earlier this month, Dynamic Workers landed on Cloudflare's Workers platform, letting developers load Worker code at runtime into a secure sandbox. Under the hood, this API exposes the basic compute isolation primitive that has always powered Workers: isolates, not containers. Because isolates are dramatically lighter-weight than containers, they can spin up roughly 100x faster while using 1/10 of the memory. That efficiency makes them effectively disposable — start one up to run a few lines of code, then discard it.

The initial use case focused on AI agents generating simple, single-use code to execute tasks — essentially a secure eval(). But what about scenarios where you want AI-generated code to persist — to create a small application with a custom UI, or to maintain state over multiple interactions? In those cases, you still want the safety of a sandbox, alongside durable storage.

One Object, Two Databases

The obvious approach is to give Dynamic Workers access to an external database by binding an RPC API that points to a remote SQL store, such as Cloudflare D1 or a Postgres instance reached through Hyperdrive. But Workers has a storage type purpose-built for this scenario: Durable Objects. A Durable Object is a uniquely named Worker with a single global instance per name, each with its own SQLite database residing on local disk. That local placement makes storage access effectively zero-latency — no network round-trips.

There's a catch, however. Configuring a Durable Object ordinarily requires that you write a class extending DurableObject, export it from the Worker's main module, provision storage by declaring it in wrangler.jsonc, and then wire up a namespace binding — usually through ctx.exports — to direct requests to it. None of this works cleanly when the code is loaded dynamically at runtime, outside the typical Cloudflare API provisioning flow.

Nor would you necessarily want it to. If AI-generated code could provision an entire namespace of Durable Objects, you'd lose all control over how many objects get spawned or how much storage is consumed. You'd probably want a familiar pattern: requests pass through your code first, where you add logging, metrics, limits, or billing before forwarding them onward.

Introducing Durable Object Facets

Durable Object Facets, now available in open beta, take the opposite approach. The platform developer writes a "normal" Durable Object that acts as a supervisor. That supervisor loads the dynamically generated code as a Dynamic Worker, then instantiates it as a facet of itself.

The dynamic code exports a Durable Object class just as it normally would — declaring a class extends DurableObject. That class gets its own SQLite database, accessible through standard Durable Object storage APIs. Both the supervisor and the facet databases are stored together as part of the same Durable Object, but the facet's database is fully isolated from the supervisor's. The dynamic code cannot access or read the supervisor's data.

Building a Dynamic App Platform

In a complete example, an AppRunner Durable Object manages one application per instance. It stores the app code, then loads it through Dynamic Workers on demand. The platform developer expects the app code to export a class named App. When a request arrives, AppRunner loads the code and runs it as a Durable Object Facet. The result is a single Durable Object composed of two separate SQLite databases — one for the AppRunner itself, one for the facet App.

To test this locally, copy the implementation into a worker.ts file with its corresponding wrangler.jsonc configuration, and run npx wrangler dev.

Availability

Durable Object Facets ship with Dynamic Workers and are in beta for Workers Paid plan users. Documentation for both Dynamic Workers and Facets is available on the Cloudflare developer site.