---
title: Dynamic Workers
description: Spin up isolated Workers on demand to execute code.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Dynamic Workers

Spin up Workers at runtime to execute code on-demand in a secure, sandboxed environment.

Dynamic Workers let you spin up an unlimited number of Workers to execute arbitrary code specified at runtime. Dynamic Workers can be used as a lightweight alternative to containers for securely sandboxing code you don't trust.

Dynamic Workers are the lowest-level primitive for spinning up a Worker, giving you full control over defining how the Worker is composed, which bindings it receives, whether it can reach the network, and more.

### Get started

Deploy the [Dynamic Workers Playground ↗](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground) to create and run Workers dynamically from code you write or import from GitHub, with real-time logs and observability.

[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/dinasaur404/dynamic-workers-playground)

## Use Dynamic Workers for

Use this pattern when code needs to run quickly in a secure, isolated environment.

* **AI Agent "Code Mode"**: LLMs are trained to write code. Instead of supplying an agent with tool calls to perform tasks, give it an API and let it write and execute code. Save up to 80% in inference tokens and cost by allowing the agent to programmatically process data instead of sending it all through the LLM.
* **AI-generated applications / "Vibe Code"**: Run generated code for prototypes, projects, and automations in a secure, isolated sandboxed environment.
* **Fast development and previews**: Load prototypes, previews, and playgrounds in milliseconds.
* **Custom automations**: Create custom tools on the fly that execute a task, call an integration, or automate a workflow.
* **Platforms**: Run applications uploaded by your users.

## Features

Because you compose the Worker that runs the code at runtime, you control how that Worker is configured and what it can access.

* **[Bindings](https://developers.cloudflare.com/dynamic-workers/usage/bindings/)**: Decide which bindings and structured data the dynamic Worker receives.
* **[Observability](https://developers.cloudflare.com/dynamic-workers/usage/observability/)**: Attach Tail Workers and capture logs for each run.
* **[Network access](https://developers.cloudflare.com/dynamic-workers/usage/egress-control/)**: Intercept or block Internet access for outbound requests.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}}]}
```

---

---
title: Getting started
description: Load and run a dynamic Worker.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/getting-started.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Getting started

You can create a Worker that spins up other Workers, called Dynamic Workers, at runtime to execute code on-demand in a secure, sandboxed environment. You provide the code, choose which bindings the Dynamic Worker can access, and control whether the Dynamic Worker can reach the network.

Dynamic Workers support two loading modes:

* `load(code)` creates a fresh Dynamic Worker for one-time execution.
* `get(id, callback)` caches a Dynamic Worker by ID so it can stay warm across requests.

`load()` is best for one-time code execution, for example when using [Codemode](https://developers.cloudflare.com/agents/api-reference/codemode/). `get(id, callback)` is better when the same code will receive subsequent requests, for example when you are building applications.

### Try it out

#### Dynamic Workers Starter

[![Deploy to Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers)

Use this "hello world" [starter ↗](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers) to get a Worker deployed that can load and execute Dynamic Workers.

#### Dynamic Workers Playground

[![Deploy to Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground)

You can also deploy the [Dynamic Workers Playground ↗](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground), where you can write or import code, bundle it at runtime with `@cloudflare/worker-bundler`, execute it through a Dynamic Worker, and see real-time responses and execution logs.

## Configure Worker Loader

In order for a Worker to be able to create Dynamic Workers, it needs a Worker Loader binding. Unlike most Workers bindings, this binding doesn't point at any external resource in particular; it simply provides access to the Worker Loader API.

Configure it like so, in your Worker's `wrangler.jsonc`:

* [  wrangler.jsonc ](#tab-panel-4577)
* [  wrangler.toml ](#tab-panel-4578)

```

{

  "worker_loaders": [

    {

      "binding": "LOADER",

    },

  ],

}


```

```

[[worker_loaders]]

binding = "LOADER"


```

Your Worker will then have access to the Worker Loader API via `env.LOADER`.

## Run a Dynamic Worker

Use `env.LOADER.load()` to create a Dynamic Worker and run it:

* [  JavaScript ](#tab-panel-4581)
* [  TypeScript ](#tab-panel-4582)

JavaScript

```

export default {

  async fetch(request, env) {

    // Load a worker.

    const worker = env.LOADER.load({

      compatibilityDate: "$today",


      mainModule: "src/index.js",

      modules: {

        "src/index.js": `

          export default {

            fetch(request) {

              return new Response("Hello from a dynamic Worker");

            },

          };

        `,

      },


      // Block all outbound network access from the Dynamic Worker.

      globalOutbound: null,

    });


    // Get the Dynamic Worker's `export default` entrypoint.

    // (A Worker can also export separate, named entrypoints.)

    let entrypoint = worker.getEntrypoint();


    // Forward the HTTP request to it.

    return entrypoint.fetch(request);

  },

};


```

TypeScript

```

export default {

  async fetch(request: Request, env: Env): Promise<Response> {

    // Load a worker.

    const worker = env.LOADER.load({

      compatibilityDate: "$today",


      mainModule: "src/index.js",

      modules: {

        "src/index.js": `

          export default {

            fetch(request) {

              return new Response("Hello from a dynamic Worker");

            },

          };

        `,

      },


      // Block all outbound network access from the Dynamic Worker.

      globalOutbound: null,

    });


    // Get the Dynamic Worker's `export default` entrypoint.

    // (A Worker can also export separate, named entrypoints.)

    let entrypoint = worker.getEntrypoint();


    // Forward the HTTP request to it.

    return entrypoint.fetch(request);

  },

};


```

In this example, `env.LOADER.load()` creates a Dynamic Worker from the code defined in `modules` and returns a stub that represents it.

`worker.getEntrypoint().fetch(request)` sends the incoming request to the Dynamic Worker's `fetch()` handler, which processes it and returns a response.

### Reusing a Dynamic Worker across requests

If you expect to load the exact same Worker more than once, use [get(id, callback)](https://developers.cloudflare.com/dynamic-workers/api-reference/#get) instead of `load()`. The `id` should be a unique string identifying the particular code you intend to load. When the runtime sees the same `id` again, it can reuse the existing Worker instead of creating a new one, if it hasn't been evicted yet.

The callback you provide will only be called if the Worker is not already loaded. This lets you skip loading the code from storage when the Worker is already running.

* [  JavaScript ](#tab-panel-4579)
* [  TypeScript ](#tab-panel-4580)

JavaScript

```

const worker = env.LOADER.get("hello-v1", async () => {

  // Callback only runs if there is not already a warm

  // instance available.


  // Load code from storage.

  let code = await env.MY_CODE_STORAGE.get("hello-v1");


  // Return the same format as `env.LOADER.load()` accepts.

  return {

    compatibilityDate: "$today",

    mainModule: "index.js",

    modules: { "index.js": code },

    globalOutbound: null,

  };

});


```

TypeScript

```

const worker = env.LOADER.get("hello-v1", async () => {

  // Callback only runs if there is not already a warm

  // instance available.


  // Load code from storage.

  let code = await env.MY_CODE_STORAGE.get("hello-v1");


  // Return the same format as `env.LOADER.load()` accepts.

  return {

    compatibilityDate: "$today",

    mainModule: "index.js",

    modules: { "index.js": code, },

    globalOutbound: null,

  };

});


```

## Supported languages

Dynamic Workers support JavaScript (ES modules and CommonJS) and Python. The code is passed as strings in the `modules` object. There is no build step, so languages like TypeScript must be compiled to JavaScript before being passed to `load()` or `get()`.

For the full list of supported module types, refer to the [API reference](https://developers.cloudflare.com/dynamic-workers/api-reference/#modules).

### Using TypeScript and npm dependencies

If your Dynamic Worker needs TypeScript compilation or npm dependencies, the code must be transpiled and bundled before passing to the Worker Loader.

[@cloudflare/worker-bundler ↗](https://www.npmjs.com/package/@cloudflare/worker-bundler) is a library that handles this for you. Use it to bundle source files into a format that `load()` and `get()` accept:

TypeScript

```

import { createWorker } from "@cloudflare/worker-bundler";


const worker = env.LOADER.get("my-worker", async () => {

  const { mainModule, modules } = await createWorker({

    files: {

      "src/index.ts": `

        import { Hono } from 'hono';

        const app = new Hono();

        app.get('/', (c) => c.text('Hello from Hono!'));

        export default app;

      `,

      "package.json": JSON.stringify({

        dependencies: { hono: "^4.0.0" },

      }),

    },

  });


  return { mainModule, modules, compatibilityDate: "2026-01-01" };

});


```

`createWorker()` handles TypeScript compilation, dependency resolution from npm, and bundling. It returns `mainModule` and `modules` ready to pass directly to `load()` or `get()`.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/getting-started/","name":"Getting started"}}]}
```

---

---
title: API reference
description: Reference for the Worker Loader binding and the WorkerCode object.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/api-reference.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# API reference

This page describes the Worker Loader binding API, assuming you have [configured such a binding](https://developers.cloudflare.com/dynamic-workers/getting-started/#configure-worker-loader) 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](https://developers.cloudflare.com/workers/configuration/compatibility-dates/) 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](https://developers.cloudflare.com/workers/configuration/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](https://developers.cloudflare.com/workers/languages/python/). 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.

Warning

While Dynamic Workers support Python, Python Workers are currently much slower to start than JavaScript Workers. For one-off AI-generated code, we strongly recommend using JavaScript. AI can write either language equally well.

#### `` 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](https://developers.cloudflare.com/workers/runtime-apis/context/#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:

* [Structured clonable types ↗](https://developer.mozilla.org/en-US/docs/Web/API/Web%5FWorkers%5FAPI/Structured%5Fclone%5Falgorithm).
* [Service Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings), including [loopback bindings from ctx.exports](https://developers.cloudflare.com/workers/runtime-apis/context/#exports).

The second point is the key to creating custom bindings: you can define a binding with any arbitrary API, by defining a [WorkerEntrypoint class](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc) 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](https://developers.cloudflare.com/workers/observability/logs/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),

    });

  }

}


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/api-reference/","name":"API reference"}}]}
```

---

---
title: Pricing
description: Dynamic Workers pricing is based on requests, CPU time, and the number of unique Dynamic Workers created per day.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/pricing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Pricing

Dynamic Workers pricing is based on three dimensions: Dynamic Workers created daily, requests, and CPU time.

Dynamic Workers are currently only available on the [Workers Paid plan](https://developers.cloudflare.com/workers/platform/pricing/).

| Included                          | Additional usage                       |                                     |
| --------------------------------- | -------------------------------------- | ----------------------------------- |
| **Dynamic Workers created daily** | 1,000 unique Dynamic Workers per month | +$0.002 per Dynamic Worker per day  |
| **Requests** ¹                    | 10 million per month                   | +$0.30 per million requests         |
| **CPU time** ¹                    | 30 million CPU milliseconds per month  | +$0.02 per million CPU milliseconds |

¹ Uses [Workers Standard rates](https://developers.cloudflare.com/workers/platform/pricing/#workers) and will appear as part of your existing Workers bill, not as separate Dynamic Workers charges.

Pricing availability

The Dynamic Workers created daily charge is not yet active — you will not be billed for the number of Dynamic Workers created at this time. Pricing information is shared in advance so you can estimate future costs.

Dynamic Workers requests and CPU time are already billed as part of your Workers plan — they count toward your Workers requests and CPU usage.

## Dynamic Workers created daily

You are billed for each unique Dynamic Worker created in a day. A Dynamic Worker is uniquely identified by its **Worker ID** and **code** — if either changes, it counts as a new Dynamic Worker. The count resets daily.

| Scenario                                   | Counted as                        |
| ------------------------------------------ | --------------------------------- |
| Same code, same ID, invoked multiple times | 1 Dynamic Worker                  |
| Same code, different IDs                   | 1 Dynamic Worker per ID           |
| Same ID, different code versions           | 1 Dynamic Worker per code version |
| No ID provided or .load(code) used         | 1 Dynamic Worker per invocation   |

Note

If your application sends multiple requests to the same Worker, use `.get()` with a stable ID to avoid being billed for multiple creations.

## Requests

Dynamic Workers reuse [Workers Standard request pricing](https://developers.cloudflare.com/workers/platform/pricing/).

A request is counted each time a Dynamic Worker is invoked:

* Each `fetch()` call into a Dynamic Worker
* Each RPC method call on a Dynamic Worker stub (billed the same way as [Durable Objects](https://developers.cloudflare.com/durable-objects/platform/pricing/))

If an RPC method returns a stub (an object that extends `RpcTarget`), those returned stubs share the same RPC session as the original call. Subsequent calls on the returned stub are not billed as separate requests.

## CPU time

CPU time is billed at the same rate as [Workers Standard](https://developers.cloudflare.com/workers/platform/pricing/).

Unlike standard Workers (where only execution time is billed), Dynamic Workers bill for two components of CPU time:

* **Startup time**: The compute required to initialize the isolate and parse your code.
* **Execution time**: The compute time your code spends actively processing logic, excluding time spent waiting on I/O.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/pricing/","name":"Pricing"}}]}
```

---

---
title: Codemode
description: Read the Codemode reference in Cloudflare Agents docs.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/examples/codemode.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Codemode

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/dynamic-workers/examples/codemode/","name":"Codemode"}}]}
```

---

---
title: Dynamic Workers Playground
description: Explore a playground built with Dynamic Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/examples/dynamic-workers-playground.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Dynamic Workers Playground

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/dynamic-workers/examples/dynamic-workers-playground/","name":"Dynamic Workers Playground"}}]}
```

---

---
title: Dynamic Workers Starter
description: A starter template for building with Dynamic Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/examples/dynamic-workers-starter.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Dynamic Workers Starter

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/dynamic-workers/examples/dynamic-workers-starter/","name":"Dynamic Workers Starter"}}]}
```

---

---
title: Bindings
description: Give Dynamic Workers access to external APIs.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/usage/bindings.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Bindings

Bindings are a way to grant Dynamic Workers access to specific APIs and resources. They are similar to [regular Workers bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/). However, Dynamic Worker bindings don't typically point at regular Workers platform resources like KV namespaces or R2 buckets. Instead, they point to **anything you want**.

When using Dynamic Workers, you can invent your own bindings to give to the Worker, by defining arbitrary [Workers RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/) interfaces.

## Capability-based Sandboxing

Workers RPC — also known as [Cap'n Web ↗](https://github.com/cloudflare/capnweb) — is an RPC system designed to make it easy to present rich TypeScript interfaces across a security boundary. Cap'n Web implements a capability-based security model. That means, it supports passing objects "by reference" across RPC boundaries. When you receive an object reference (also known as a "stub") over RPC, you are implicitly granted the ability to call that object's methods; doing so makes further RPC calls back to the original object. Objects do not have any URL or global identifier, so the only way to address one is to have received a stub pointing to it — if you haven't received a stub, you can't call the object.

Capability-based Security is essential to the design of most, if not all, successful sandboxes, though it is often an implementation detail that users and even developers don't see. Android has Binder, Chrome has Mojo, and Cloudflare Workers has Cap'n Proto and Cap'n Web.

Dynamic Workers directly expose this power to you, the developer, so that you can build your own strong sandbox.

## Custom Bindings with Dynamic Workers

Imagine you are using Dynamic Workers to implement an agent that can post messages to a chat room. Different agents will be able to post to different chat rooms, but any particular agent is only allowed to post to one specific chat room. The agent writes code, which you run in a Dynamic Worker.

You want to make sure the code can only access the _specific_ chat room that the given agent is authorized for. One way to do this would be to pass the chat room name into the Dynamic Worker (or to the agent), and then verify that all requests coming out of the worker are addressed to the correct room, blocking them if not. However, Dynamic Workers offers a better approach: **give the Worker a binding that represents the _specific_ chat room.**

To define a custom binding, your parent worker needs to implement a [WorkerEntrypoint class](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc#the-workerentrypoint-class) and export it. In this case, we will be defining a class called `ChatRoom`. Of course, we don't want to export a new class for every possible chat room. Instead, we can specialize the interface for a specific room using [ctx.props](https://developers.cloudflare.com/workers/runtime-apis/context#props).

TypeScript

```

import { WorkerEntrypoint } from "cloudflare:workers";


// Define the ChatRoom RPC interface.

//

// This MUST be exported from the top-level module of the

// parent worker.

export class ChatRoom extends WorkerEntrypoint<Cloudflare.Env, ChatRoomProps> {

  // Any methods defined on this class will be callable

  // by the Dynamic Worker.


  // Method to post a message to chat.

  async post(text: string): Promise<void> {

    let { apiKey, botName, roomName } = this.ctx.props;


    // Prefix the message with the bot's name.

    text = `[${botName}]: ${text}`;


    // Send it to the chat service.

    await postToChat(apiKey, roomName, text);

  }

}


// Define a props type which specializes our `ChatRoom` for

// a particular client. This can be any serializable object

// type.

type ChatRoomProps = {

  // API key to the remote chat API.

  apiKey: string;


  // Name of the room to post to.

  roomName: string;


  // Name of the bot posting.

  botName: string;

};


```

Now we can load a Dynamic Worker and give it a Chat Room. To create the chat room RPC stub, we use [ctx.exports](https://developers.cloudflare.com/workers/runtime-apis/context#exports), then we simply pass it into the Dynamic Worker Loader in the `env` object:

TypeScript

```

// Let's say our agent wrote this code.

let codeFromAgent = `

  export class Agent extends WorkerEntrypoint {

    async run() {

      await this.env.CHAT_ROOM.post("Hello!");

    }

  }

`;


// Set up the props for our agent.

let props: ChatRoomProps = {

  apiKey,

  roomName: "#bot-chat",

  botName: "Robo",

};


// Create a service stub representing our chat room

// capability. The system automatically creates

// `ctx.exports.ChatRoom` because our top-level module

// exported a `WorkerEntrypoint` called `ChatRoom`.

let chatRoom = ctx.exports.ChatRoom({ props });


// `chatRoom` is now an RPC service stub. We could

// call methods on it, like `chatRoom.post()`.


let worker = env.LOADER.load({

  // We can define the child Worker's `env` to be

  // any serializable object. Service stubs are

  // serializable, so we'll pass in our stub.

  env: {

    CHAT_ROOM: chatRoom,

  },


  // Specify code and other options as usual...

        compatibilityDate: "$today",

  mainModule: "index.js",

  modules: { "index.js": codeFromAgent },

  globalOutbound: null,

});


return worker.getEntrypoint("Agent").run();


```

We have achieved an elegant sandbox:

* The agent can only post to the desired room.
* The posts are made using an API key, but the API key is never visible to the agent.
* We rewrite the messages to include the agent's identity (this is just an example; we could perform any rewrite).
* All this happens without any cooperation from the agent itself. It doesn't even know any of this is happening!

### Tip: Tell your agent TypeScript types

In order for an AI agent to write code against your bindings, you have to tell it what interface they implement. The best way to do this is to give your agent TypeScript types describing the API, complete with comments documenting each declaration. Modern LLMs understand TypeScript well, having trained on a huge quantity of it, making it by far the most concise way to describe a JavaScript API. Note that even if the agent is actually writing plain JavaScript, you can still explain the interface to them using TypeScript.

Of course, you should declare your `WorkerEntrypoint` class to extend the TypeScript type, ensuring that it actually matches.

## Passing normal Workers bindings

Sometimes, you may simply want to pass a standard Workers binding, like a KV namespace, R2 bucket, etc., into a Dynamic Worker. At this time, this is not directly supported. However, you can of course create a wrapper RPC interface, using the approach outlined above, which emulates a regular Workers binding, forwarding to a real binding in its implementation. Such a wrapper may even be preferable as it offers the opportunity to narrow the scope of the binding, filter or rewrite requests, etc. That said, in the future, we plan to support passing the bindings directly.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/usage/","name":"Usage"}},{"@type":"ListItem","position":4,"item":{"@id":"/dynamic-workers/usage/bindings/","name":"Bindings"}}]}
```

---

---
title: Egress control
description: Restrict, intercept, and audit outbound network access for dynamic Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/usage/egress-control.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Egress control

When you run untrusted or AI-generated code in a dynamic Worker, you need to control what it can access on the network. You might want to:

* block all outbound access so the dynamic Worker can only use the [bindings](https://developers.cloudflare.com/dynamic-workers/usage/bindings/) you give it
* restrict outbound requests to a specific set of allowed destinations
* inject credentials into outbound requests without exposing secrets to the dynamic Worker
* log or audit every outbound request for observability

The `globalOutbound` option in the `WorkerCode` object returned by `get()` or passed to `load()` controls all of this. It intercepts every `fetch()` and `connect()` call the dynamic Worker makes.

## Block all outbound access

Set `globalOutbound` to `null` to fully isolate the dynamic Worker from the network:

JavaScript

```

return {

  mainModule: "index.js",

  modules: { "index.js": code },

  globalOutbound: null,

};


```

This causes any `fetch()` or `connect()` request from the dynamic Worker to throw an exception.

In this mode, you can still give the Dynamic Worker direct access to specific resources and services using [bindings](https://developers.cloudflare.com/dynamic-workers/usage/bindings/). This is the cleanest and most secure way to design your sandbox: block the Internet, then constructively offer specific capabilities via bindings.

That said, if you need to offer compatibility with existing HTTP client libraries running directly inside your Dynamic Worker sandbox, then blocking `fetch()` may be infeasible, and you may prefer to intercept requests instead.

## Intercept outbound requests

To intercept outbound requests, define a `WorkerEntrypoint` class in the loader Worker that acts as a gateway. Every `fetch()` and `connect()` call the dynamic Worker makes goes through this gateway instead of hitting the network directly. Pass the gateway to the dynamic Worker with `globalOutbound` and `ctx.exports`:

JavaScript

```

import { WorkerEntrypoint } from "cloudflare:workers";


export class HttpGateway extends WorkerEntrypoint {

  async fetch(request) {

    // Every outbound fetch() from the dynamic Worker arrives here.

    // Inspect, modify, block, or forward the request.

    return fetch(request);

  }

}


export default {

  async fetch(request, env, ctx) {

    const worker = env.LOADER.get("my-worker", async () => {

      return {

        compatibilityDate: "$today",

        mainModule: "index.js",

        modules: { "index.js": code },


        // Pass the gateway as a service binding.

        // The dynamic Worker's fetch() and connect() calls

        // are routed through HttpGateway instead of going

        // to the network directly.

        globalOutbound: ctx.exports.HttpGateway(),

      };

    });


    return worker.getEntrypoint().fetch(request);

  },

};


```

From here, you can add any logic to the gateway, such as restricting destinations, injecting credentials, or logging requests.

## Inject credentials

A common pattern is attaching credentials to outbound requests so the dynamic Worker never sees the secret. Similar to [custom bindings](https://developers.cloudflare.com/dynamic-workers/usage/bindings/#custom-bindings-with-dynamic-workers), you can use [ctx.props](https://developers.cloudflare.com/workers/runtime-apis/context/#props) to pass per-tenant or per-request context to the gateway.

The dynamic Worker calls `fetch()` normally. `HttpGateway` intercepts the request, attaches the token from the loader Worker's environment, and forwards it. The dynamic Worker never has access to `API_TOKEN`.

JavaScript

```

import { WorkerEntrypoint } from "cloudflare:workers";


export class HttpGateway extends WorkerEntrypoint {

  async fetch(request) {

    let url = new URL(request.url);

    const headers = new Headers(request.headers);


    // For requests to api.example.com, inject credentials.

    if (url.hostname === "api.example.com") {

      headers.set("Authorization", `Bearer ${this.env.API_TOKEN}`);

      headers.set("X-Tenant-Id", this.ctx.props.tenantId);

    }


    return fetch(request, { headers });

  }

}


export default {

  async fetch(request, env, ctx) {

    const tenantId = getTenantFromRequest(request);


    const worker = env.LOADER.get(`tenant:${tenantId}`, async () => {

      return {

        mainModule: "index.js",

        modules: {

          "index.js": `

            export default {

              async fetch() {

                const resp = await fetch("https://api.example.com/data");

                return new Response(await resp.text());

              },

            };

          `,

        },

        globalOutbound: ctx.exports.HttpGateway({

          props: { tenantId },

        }),

      };

    });


    return worker.getEntrypoint().fetch(request);

  },

};


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/usage/","name":"Usage"}},{"@type":"ListItem","position":4,"item":{"@id":"/dynamic-workers/usage/egress-control/","name":"Egress control"}}]}
```

---

---
title: Observability
description: Capture, retrieve, and forward logs from dynamic Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/dynamic-workers/usage/observability.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Observability

Dynamic Workers support logs with `console.log()` calls, exceptions, and request metadata captured during execution. To access those logs, you attach a [Tail Worker](https://developers.cloudflare.com/workers/observability/logs/tail-workers/), a callback that runs after the Dynamic Worker finishes that passes along all the logs, exceptions, and metadata it collected.

This guide will show you how to:

* Store Dynamic Worker logs so you can search, filter, and query them
* Collect logs during execution and return them in real time, for development and debugging

## Capture logs with Tail Workers

To save logs emitted by a Dynamic Worker, you need to capture them and write them somewhere they can be stored. Setting this up requires three steps:

1. Enabling [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) on the loader Worker so that log output is saved.
2. Defining a Tail Worker that receives logs from the Dynamic Worker and writes them to Workers Logs.
3. Attaching the Tail Worker to the Dynamic Worker when you create it.

Note

Tail Workers run asynchronously after the Dynamic Worker has already sent its response, so they do not add latency to the request.

### Enable Workers Logs on the loader Worker

Enable [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) by adding the `observability` setting to the loader Worker's Wrangler configuration. However, Workers Logs only captures log output from the loader Worker itself. Dynamic Workers are separate, so their `console.log()` calls are not included automatically. To get Dynamic Worker logs into Workers Logs, you need to define a Tail Worker that receives logs from the Dynamic Worker and writes them into the loader Worker's Workers Logs.

* [  wrangler.jsonc ](#tab-panel-4583)
* [  wrangler.toml ](#tab-panel-4584)

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "observability": {

    "enabled": true,

    "head_sampling_rate": 1

  }

}


```

```

[observability]

enabled = true

head_sampling_rate = 1


```

### Define the Tail Worker

When a Dynamic Worker runs, the runtime collects all of its `console.log()` calls, exceptions, and request metadata. By default, those logs are discarded after the Dynamic Worker finishes.

To keep them, you define a Tail Worker on the loader Worker. A Tail Worker is a class with a `tail()` method. This is where you write the code that decides what happens with the logs. The runtime will call this method after the Dynamic Worker finishes, passing in everything it collected during execution.

Inside `tail()`, you write each log entry to Workers Logs by calling `console.log()` with a JSON object. Include a `workerId` field in each entry so you can tell which Dynamic Worker produced each log and use it to filter and search the logs by Dynamic Worker later on.

JavaScript

```

import { WorkerEntrypoint } from "cloudflare:workers";


export class DynamicWorkerTail extends WorkerEntrypoint {

  async tail(events) {

    for (const event of events) {

      for (const log of event.logs) {

        console.log({

          source: "dynamic-worker-tail",

          workerId: this.ctx.props.workerId,

          level: log.level,

          message: log.message,

        });

      }

    }

  }

}


```

The Tail Worker reads `workerId` from `this.ctx.props.workerId`. You set this value when you attach the Tail Worker to the Dynamic Worker in the next step.

Since the Tail Worker is defined within the loader Worker, its `console.log()` output is saved to Workers Logs along with the loader Worker's own logs.

### Attach the Tail Worker to the Dynamic Worker

When you create the Dynamic Worker, pass the Tail Worker in the [tails](https://developers.cloudflare.com/dynamic-workers/api-reference/#tails) array. This tells the runtime: after this Dynamic Worker finishes, send its collected logs to the Tail Worker you defined.

To reference the `DynamicWorkerTail` class you defined in the previous step, use [ctx.exports](https://developers.cloudflare.com/workers/runtime-apis/context/#exports). `ctx` is the third parameter in the loader Worker's `fetch(request, env, ctx)` handler. `ctx.exports` gives you access to classes that are exported from the loader Worker. Because the Dynamic Worker runs in a separate context and cannot access the class directly, you use `ctx.exports.DynamicWorkerTail()` to create a reference that the runtime can wire up to the Dynamic Worker.

You also need to tell the Tail Worker which Dynamic Worker it is logging for. Since the Tail Worker runs separately from the loader Worker's `fetch()` handler, it does not have access to your local variables. To pass it information, use the [props](https://developers.cloudflare.com/workers/runtime-apis/context/#props) option when you create the instance. `props` is a plain object of key-value pairs that you set when attaching the Tail Worker and that the Tail Worker can read at `this.ctx.props` when it runs. In this case, you pass the `workerId` so the Tail Worker knows which Dynamic Worker produced the logs.

JavaScript

```

const worker = env.LOADER.get(workerId, () => ({

  mainModule: WORKER_MAIN,

  modules: {

    [WORKER_MAIN]: WORKER_SOURCE,

  },

  tails: [

    ctx.exports.DynamicWorkerTail({

      props: { workerId },

    }),

  ],

}));


return worker.getEntrypoint().fetch(request);


```

## Return logs in real time

The setup above stores logs for later, but sometimes you need logs right away for real-time development. The challenge is that the Tail Worker and the loader Worker's `fetch()` handler run separately. The Tail Worker has the logs, but the `fetch()` handler is the one building the response. You need a shared place where the Tail Worker can write the logs and the `fetch()` handler can read them.

A [Durable Object](https://developers.cloudflare.com/durable-objects/) works well for this. Both the Tail Worker and the `fetch()` handler can look up the same Durable Object instance by name. The Tail Worker writes logs into it after the Dynamic Worker finishes, and the `fetch()` handler reads them out and includes them in the response.

The pattern works like this:

1. The `fetch()` handler creates a log session in a Durable Object before running the Dynamic Worker.
2. The Dynamic Worker runs and produces logs.
3. After the Dynamic Worker finishes, the Tail Worker writes the collected logs to the same Durable Object.
4. The `fetch()` handler reads the logs from the Durable Object and returns them in the response.

JavaScript

```

import { exports } from "cloudflare:workers";


// 1. Create a log session before running the Dynamic Worker.

const logSession = exports.LogSession.getByName(workerName);

const logWaiter = await logSession.waitForLogs();


// 2. Run the Dynamic Worker.

const response = await worker.getEntrypoint().fetch(request);


// 3. Wait up to 1 second for the Tail Worker to deliver logs.

const logs = await logWaiter.getLogs(1000);


```

For a full working implementation, refer to the [Dynamic Workers Playground example ↗](https://github.com/cloudflare/agents/tree/main/examples/dynamic-workers-playground).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/dynamic-workers/","name":"Dynamic Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/dynamic-workers/usage/","name":"Usage"}},{"@type":"ListItem","position":4,"item":{"@id":"/dynamic-workers/usage/observability/","name":"Observability"}}]}
```
