Skip to main content
workerd is built on a set of guiding principles that inform architectural decisions and API design. These principles aim to create a runtime that is secure, performant, and developer-friendly.

Server-first

workerd is designed specifically for server environments, not for command-line tools or graphical user interfaces. This focus means:
  • Optimized for long-running processes
  • Designed for handling concurrent requests
  • Built-in support for operational concerns like metrics and logging
  • No assumptions about interactive user input
Unlike Node.js, which evolved from browser JavaScript, or Deno, which emphasizes CLI tooling, workerd is purpose-built for running server applications at scale.

Standard-based

Built-in APIs are based on web platform standards wherever possible. workerd implements:
  • fetch() API for HTTP requests
  • Web Streams API for data streaming
  • Web Crypto API for cryptographic operations
  • URL and URLSearchParams APIs
  • TextEncoder/TextDecoder for encoding
  • Standard JavaScript features (ES modules, async/await, etc.)
This approach provides several benefits:
  • Familiarity: Developers already know these APIs from browser development
  • Interoperability: Code can often run in both browsers and workerd
  • Future-proof: Standards evolve with community input and broad implementation
  • Documentation: Abundant resources exist for standard APIs
workerd maintains a high bar for non-standard APIs. Custom APIs are only added when:
  • No suitable standard exists
  • The functionality is fundamentally server-specific
  • The API provides significant value to the platform

Nanoservices

workerd enables splitting applications into components that are decoupled and independently deployable like microservices, but with the performance of a local function call. When one nanoservice calls another:
  • The callee runs in the same thread and process
  • No network latency or serialization overhead
  • Sub-millisecond invocation time
  • Shared memory space when appropriate
This architecture provides the benefits of microservices (modularity, independent deployment) without the costs (network latency, operational complexity). Example using service bindings:
export default {
  async fetch(request, env) {
    // Call to another worker - as fast as a function call
    const response = await env.AUTH_SERVICE.fetch(request);
    
    if (response.status === 200) {
      return env.BACKEND_SERVICE.fetch(request);
    }
    
    return response;
  }
}

Homogeneous deployment

Instead of deploying different microservices to different machines, deploy all nanoservices to every machine in your cluster. This approach simplifies:
  • Load balancing: Any machine can handle any request
  • Failover: No single point of failure for any service
  • Scaling: Add capacity by adding machines, not by orchestrating specific services
  • Routing: No service discovery needed
workerd makes this practical by:
  • Keeping workers lightweight (fast startup, low memory)
  • Isolating workers for security
  • Managing resources efficiently across many workers

Capability bindings

workerd uses capabilities instead of global namespaces to connect services and resources. Traditional approach (global namespace):
// Worker can access ANY URL
const response = await fetch('https://internal.company.com/secrets');
Capability-based approach:
export default {
  async fetch(request, env) {
    // Worker can ONLY access bound services
    const response = await env.BACKEND_SERVICE.fetch(request);
    return response;
  }
}
Configuration specifies exactly what each worker can access:
const myWorker :Workerd.Worker = (
  serviceWorkerScript = embed "worker.js",
  compatibilityDate = "2024-01-01",
  bindings = [
    (name = "BACKEND_SERVICE", service = "backend"),
    (name = "KV_STORE", kvNamespace = "my-kv")
  ]
);
Benefits of this approach:

Composability

The same worker code can connect to different resources by changing only the configuration:
# Development
bindings = [(name = "DB", service = "dev-database")]

# Production  
bindings = [(name = "DB", service = "prod-database")]

Security

Capability-based security makes certain attack classes impossible:
  • SSRF prevention: Workers cannot access arbitrary URLs by default
  • Principle of least privilege: Grant only the specific access needed
  • Audit trail: Configuration explicitly documents all resource access
To allow a worker to access the public internet, you must explicitly grant it:
const myWorker :Workerd.Worker = (
  serviceWorkerScript = embed "worker.js",
  compatibilityDate = "2024-01-01",
  globalOutbound = "internet"  # Explicit grant
);

Testability

Easy to inject mock services for testing:
const testWorker :Workerd.Worker = (
  inherit = "myWorker",
  bindings = [
    (name = "API", service = "mock-api")  # Override for testing
  ]
);

Always backwards compatible

Updating workerd to a newer version will never break your JavaScript code. workerd’s version number is simply a date, corresponding to the maximum compatibility date supported by that version. You can always configure your worker to use a past date, and workerd will emulate the API as it existed on that date. Every worker must specify a compatibility date:
const myWorker :Workerd.Worker = (
  serviceWorkerScript = embed "worker.js",
  compatibilityDate = "2023-02-28"  # Required
);
This system provides:

Strong compatibility guarantees

  • Your worker will work the same way forever
  • You can update workerd without testing every worker
  • No surprise breakage from runtime updates

Controlled breaking changes

When workerd needs to fix a bug or change behavior:
  1. The fix is implemented behind a compatibility flag
  2. The flag is enabled for all compatibility dates after a specific date
  3. Workers with older dates continue using the old behavior
  4. Workers can opt in early using explicit compatibility flags

Progressive modernization

You can update compatibility dates incrementally:
  1. Update one worker at a time
  2. Test the behavior with the new date
  3. Roll back by reverting the config if needed
  4. Update remaining workers when ready
Example from compatibility-date.capnp:
formDataParserSupportsFiles @0 :Bool
    $compatEnableFlag("formdata_parser_supports_files")
    $compatEnableDate("2021-11-03")
    $compatDisableFlag("formdata_parser_converts_files_to_strings");
# Original implementation mistakenly turned files into strings.
# Fixed for workers with compatibilityDate >= "2021-11-03".
This approach allows workerd to evolve and improve while maintaining the stability that production systems require.

Build docs developers (and LLMs) love