Skip to main content
Extensions provide additional capabilities to all workers in a workerd instance. They allow you to define custom JavaScript modules that can be imported directly or used to implement sophisticated constructs like wrapped bindings and custom events.

What are extensions?

Extensions are collections of JavaScript modules that:
  • Extend the runtime: Add new APIs available to all workers
  • Internal or public: Can be internal (only for other extension modules) or public (importable by user code)
  • Enable advanced patterns: Support wrapped bindings, custom event types, and middleware
  • Late-linked: Prepared separately and linked at configuration time

Defining extensions

Extensions are defined in the extensions list of your top-level configuration:
using Workerd = import "/workerd/workerd.capnp";
using MyExtension = import "my-extension.capnp";

const config :Workerd.Config = (
  services = [(name = "main", worker = .mainWorker)],
  sockets = [( name = "http", address = "*:8080", http = (), service = "main" )],
  
  extensions = [
    MyExtension.extension,
  ],
);

Extension structure

An extension consists of a list of modules:
using Workerd = import "/workerd/workerd.capnp";

const extension :Workerd.Extension = (
  modules = [
    ( name = "my-extension:api",
      esModule = embed "api.js",
      internal = false
    ),
    ( name = "my-extension:internal",
      esModule = embed "internal.js",
      internal = true
    ),
  ],
);

Extension module fields

name
Text
required
Full JavaScript module name. Convention is to use a namespace prefix like "extension-name:module-name".
name = "burrito-shop:api",
esModule
Text
required
Raw source code of the ES module. Use embed to include external files.
esModule = embed "api.js",
internal
Bool
default:false
Whether this module is internal. Internal modules:
  • Can be imported by other extension modules only
  • Cannot be imported by user code
  • Useful for implementation details and helpers
internal = true,

Using extensions in workers

Public modules

Public extension modules (with internal = false) can be imported directly in worker code:
// Import public extension module
import { createBurrito } from "burrito-shop:api";

export default {
  async fetch(request, env, ctx) {
    const burrito = createBurrito({ filling: "chicken" });
    return new Response(JSON.stringify(burrito));
  }
};

Wrapped bindings

Extensions are commonly used to implement wrapped bindings. The extension module exports a function that accepts inner bindings and returns an API object:
using Workerd = import "/workerd/workerd.capnp";

const extension :Workerd.Extension = (
  modules = [
    ( name = "burrito-shop:binding",
      esModule = embed "binding.js",
      internal = true
    ),
  ],
);

Complete example

Here’s a complete example of an extension that provides a custom logging API:
using Workerd = import "/workerd/workerd.capnp";

const extension :Workerd.Extension = (
  modules = [
    # Public API module
    ( name = "logging:api",
      esModule = embed "logging-api.js",
      internal = false
    ),
    
    # Internal helper module
    ( name = "logging:formatter",
      esModule = embed "logging-formatter.js",
      internal = true
    ),
    
    # Wrapped binding module
    ( name = "logging:binding",
      esModule = embed "logging-binding.js",
      internal = true
    ),
  ],
);

Use cases for extensions

Custom API wrappers

Wrap complex APIs or external services with a simpler interface:
// Extension provides simplified database API
import { Database } from "database:api";

export default {
  async fetch(request, env, ctx) {
    const db = new Database(env.DATABASE_URL);
    const users = await db.query("SELECT * FROM users");
    return Response.json(users);
  }
};

Middleware patterns

Implement reusable middleware through extensions:
// Extension provides middleware helpers
import { compose, cors, auth, logging } from "middleware:api";

export default compose(
  cors(),
  auth({ required: true }),
  logging({ level: "info" }),
  async (request, env, ctx) => {
    return new Response("Hello World");
  }
);

Configuration management

Centralize configuration logic in extensions:
// Extension provides config management
import { getConfig } from "config:api";

export default {
  async fetch(request, env, ctx) {
    const config = getConfig(env);
    return Response.json({
      environment: config.environment,
      features: config.features
    });
  }
};

Observability

Add tracing, metrics, and monitoring through extensions:
// Extension provides observability
import { trace, metric } from "observability:api";

export default {
  async fetch(request, env, ctx) {
    return await trace("handle-request", async () => {
      metric.increment("requests");
      const response = await handleRequest(request);
      metric.histogram("response-time", Date.now() - request.startTime);
      return response;
    });
  }
};

Best practices

Naming convention: Use a consistent namespace prefix for your extension modules (e.g., "my-extension:module-name") to avoid conflicts with other extensions.
Internal vs public: Mark implementation details as internal = true and only expose the public API. This gives you flexibility to change internals without breaking user code.
Error handling: Extension modules should have robust error handling since they’re shared across all workers.
Documentation: Document your extension APIs clearly since they become part of the runtime API surface for your workers.
Extension modules run in the same isolate as workers, so they must be efficient and secure. Avoid expensive operations in module initialization.

Limitations

Extensions have some limitations to be aware of:
  • Extension modules cannot perform async I/O during module evaluation
  • Extensions are loaded once per isolate, not per worker
  • Extension state is shared across all workers using the extension
  • Internal modules cannot be imported by user code, only by other extension modules

Next steps

Bindings

Learn about wrapped bindings

Workers

Configure workers that use extensions

Build docs developers (and LLMs) love