Skip to main content
The API layer provides JavaScript runtime APIs to worker code. These implementations bridge Web Standards, Node.js compatibility, and Cloudflare-specific features.

Overview

The API layer is located in src/workerd/api/ and contains:
  • Core Web APIs (HTTP, streams, crypto, encoding)
  • Node.js compatibility layer
  • Cloudflare-specific APIs
  • TypeScript type definitions

Directory structure

src/workerd/api/
├── crypto/          # Web Crypto API
├── streams/         # Streams API (842-line README)
├── node/            # Node.js compatibility (C++)
├── tests/           # Runtime API tests
└── *.{h,c++}        # Core API implementations

src/node/            # Node.js compatibility (TypeScript)
src/cloudflare/      # Cloudflare-specific APIs (TypeScript)

Core API patterns

Basic API structure

Most APIs follow this pattern:
// my-api.h
#pragma once

#include <workerd/jsg/jsg.h>

namespace workerd::api {

class MyAPI: public jsg::Object {
public:
  // Constructor
  static jsg::Ref<MyAPI> constructor(jsg::Lock& js);

  // Methods
  kj::String doSomething();
  jsg::Promise<int> doAsync(jsg::Lock& js);

  // Properties
  kj::String getName() { return name; }
  void setName(kj::String newName) { name = kj::mv(newName); }

  JSG_RESOURCE_TYPE(MyAPI) {
    JSG_METHOD(constructor);
    JSG_METHOD(doSomething);
    JSG_METHOD(doAsync);
    JSG_PROTOTYPE_PROPERTY(name, getName, setName);
  }

  void visitForGc(jsg::GcVisitor& visitor) {
    // Visit any GC-tracked fields
  }

private:
  kj::String name;
};

}  // namespace workerd::api

Implementation file

// my-api.c++
#include "my-api.h"

namespace workerd::api {

jsg::Ref<MyAPI> MyAPI::constructor(jsg::Lock& js) {
  return jsg::alloc<MyAPI>();
}

kj::String MyAPI::doSomething() {
  return kj::str("result");
}

jsg::Promise<int> MyAPI::doAsync(jsg::Lock& js) {
  auto& context = IoContext::current();
  auto promise = context.someAsyncOperation();

  return context.awaitIo(js, kj::mv(promise),
      [](jsg::Lock& js, Result result) {
    return processResult(result);
  });
}

}  // namespace workerd::api

HTTP APIs

Request and Response

class Request: public jsg::Object {
  static jsg::Ref<Request> constructor(
      jsg::Lock& js,
      kj::String url,
      jsg::Optional<RequestInit> init);

  // Properties
  kj::String getUrl() { return url; }
  kj::String getMethod() { return method; }
  jsg::Ref<Headers> getHeaders() { return headers; }

  // Body methods
  jsg::Promise<kj::String> getText(jsg::Lock& js);
  jsg::Promise<jsg::JsValue> getJson(jsg::Lock& js);
  jsg::Promise<kj::Array<byte>> getArrayBuffer(jsg::Lock& js);

  JSG_RESOURCE_TYPE(Request) {
    JSG_METHOD(constructor);
    JSG_READONLY_PROTOTYPE_PROPERTY(url, getUrl);
    JSG_READONLY_PROTOTYPE_PROPERTY(method, getMethod);
    JSG_READONLY_PROTOTYPE_PROPERTY(headers, getHeaders);
    JSG_METHOD_NAMED(text, getText);
    JSG_METHOD_NAMED(json, getJson);
    JSG_METHOD_NAMED(arrayBuffer, getArrayBuffer);
  }
};

fetch() implementation

jsg::Promise<jsg::Ref<Response>> fetch(
    jsg::Lock& js,
    kj::OneOf<jsg::Ref<Request>, kj::String> requestOrUrl,
    jsg::Optional<RequestInit> init) {
  // Parse request
  auto request = parseRequest(requestOrUrl, init);

  // Get HTTP client from IoContext
  auto& context = IoContext::current();
  auto& client = context.getHttpClient();

  // Make request
  auto promise = client.request(
      request.method,
      request.url,
      request.headers,
      kj::mv(request.body));

  // Bridge to JavaScript
  return context.awaitIo(js, kj::mv(promise),
      [](jsg::Lock& js, HttpResponse response) {
    return jsg::alloc<Response>(kj::mv(response));
  });
}

Streams API

The streams implementation is complex. See src/workerd/api/streams/README.md for details. Key classes:
class ReadableStream: public jsg::Object {
  // Standard stream interface
  jsg::Ref<ReadableStreamDefaultReader> getReader();
  jsg::Promise<void> cancel(jsg::Lock& js, jsg::Optional<jsg::JsValue> reason);

  // Internal implementation
  class Impl;
};

class WritableStream: public jsg::Object {
  jsg::Ref<WritableStreamDefaultWriter> getWriter();
  jsg::Promise<void> abort(jsg::Lock& js, jsg::Optional<jsg::JsValue> reason);
};

class TransformStream: public jsg::Object {
  jsg::Ref<ReadableStream> getReadable();
  jsg::Ref<WritableStream> getWritable();
};

Crypto API

Implemented in src/workerd/api/crypto/:
class SubtleCrypto: public jsg::Object {
  jsg::Promise<kj::Array<byte>> digest(
      jsg::Lock& js,
      kj::String algorithm,
      jsg::BufferSource data);

  jsg::Promise<jsg::Ref<CryptoKey>> generateKey(
      jsg::Lock& js,
      KeyAlgorithm algorithm,
      bool extractable,
      kj::Array<kj::String> keyUsages);

  jsg::Promise<kj::Array<byte>> encrypt(
      jsg::Lock& js,
      EncryptAlgorithm algorithm,
      jsg::Ref<CryptoKey> key,
      jsg::BufferSource data);

  JSG_RESOURCE_TYPE(SubtleCrypto) {
    JSG_METHOD(digest);
    JSG_METHOD(generateKey);
    JSG_METHOD(encrypt);
    // ...
  }
};

Node.js compatibility

The Node.js compatibility layer has two parts:

C++ layer (src/workerd/api/node/)

Native modules implemented in C++:
namespace workerd::api::node {

class CryptoImpl: public jsg::Object {
  // Node.js crypto implementations
  kj::Array<byte> randomBytes(int size);
  void randomFill(jsg::BufferSource buffer);

  JSG_RESOURCE_TYPE(CryptoImpl) {
    JSG_METHOD(randomBytes);
    JSG_METHOD(randomFill);
  }
};

}  // namespace workerd::api::node
Register in src/workerd/api/node/node.h:
#define NODEJS_MODULES(V)                  \
  V(buffer)                                 \
  V(crypto)                                 \
  V(util)                                   \
  // ...

TypeScript layer (src/node/)

JavaScript/TypeScript implementations:
// src/node/buffer.ts
import { BufferImpl } from 'node-internal:buffer';

export class Buffer extends Uint8Array {
  static from(value: any): Buffer {
    return BufferImpl.from(value);
  }

  static alloc(size: number): Buffer {
    return BufferImpl.alloc(size);
  }
}
Module types:
  • BUILTIN - Runtime modules (node:*, cloudflare:*)
  • INTERNAL - Only importable by builtins (node-internal:*)
  • BUNDLE - User code

Cloudflare-specific APIs

Implemented in src/cloudflare/:
// src/cloudflare/workers-types.ts
export interface Fetcher {
  fetch(request: Request | string): Promise<Response>;
}

export interface DurableObjectStub {
  fetch(request: Request | string): Promise<Response>;
  id: DurableObjectId;
}

export interface DurableObjectStorage {
  get<T = unknown>(key: string): Promise<T | undefined>;
  put<T = unknown>(key: string, value: T): Promise<void>;
  delete(key: string): Promise<boolean>;
  list<T = unknown>(options?: ListOptions): Promise<Map<string, T>>;
}

TypeScript definitions

Generated from C++ using JSG RTTI system:
class Response: public jsg::Object {
  JSG_RESOURCE_TYPE(Response) {
    JSG_TS_ROOT();  // Mark as root for generation
    JSG_TS_OVERRIDE(type Response = {
      json<T = any>(): Promise<T>;
    });

    JSG_METHOD(constructor);
    // ...
  }
};
Generate types:
just generate-types
Output: types/defines/

Compatibility flags

Conditionally enable features:
JSG_RESOURCE_TYPE(MyAPI, CompatibilityFlags::Reader flags) {
  JSG_METHOD(standardMethod);

  if (flags.getExperimentalFeature()) {
    JSG_METHOD(experimentalMethod);
  }
}
Flags defined in src/workerd/io/compatibility-date.capnp:
struct CompatibilityFlags @0x... {
  experimentalFeature @0 :Bool;
  # Enabled by default on 2024-01-15
}

Testing APIs

Test in .wd-test files:
# my-api-test.wd-test
using Workerd = import "/workerd/workerd.capnp";

const unitTests :Workerd.Config = (
  services = [(
    name = "my-api-test",
    worker = (
      modules = [(name = "worker", esModule = embed "my-api-test.js")],
      compatibilityDate = "2024-01-01",
    ),
  )],
);
// my-api-test.js
import { strictEqual } from 'node:assert';

export default {
  async test() {
    const api = new MyAPI();
    const result = api.doSomething();
    strictEqual(result, 'expected');
  }
};

Best practices

Follow Web Standards

The project has a high bar for non-standard APIs. Prefer implementing existing standards.
// GOOD: Implement standard API
class TextEncoder: public jsg::Object {
  // Match MDN/WHATWG specification
};

// AVOID: Custom non-standard API
class CustomEncoder: public jsg::Object {
  // Cloudflare-specific behavior
};

Handle errors correctly

// GOOD: Use appropriate error types
JSG_REQUIRE(value > 0, RangeError, "Value must be positive");
JSG_REQUIRE(key != nullptr, TypeError, "Key must be a string");
JSG_FAIL_REQUIRE(DOMInvalidStateError, "Cannot read after closed");

// BAD: Generic error
JSG_REQUIRE(condition, Error, "Something went wrong");

Document behavior

class MyAPI: public jsg::Object {
  // Processes the input data and returns the result.
  // Throws TypeError if data is not a string or ArrayBuffer.
  // Throws RangeError if data exceeds maximum size.
  jsg::Promise<kj::Array<byte>> process(
      jsg::Lock& js,
      kj::OneOf<kj::String, kj::Array<byte>> data);
};

Use appropriate types

// GOOD: Union type for flexible input
void send(kj::OneOf<kj::String, kj::Array<byte>> data);

// GOOD: Optional parameter
void fetch(kj::String url, jsg::Optional<FetchOptions> options);

// GOOD: Return promise for async
jsg::Promise<Response> fetchAsync(jsg::Lock& js, kj::String url);

Adding a new API

  1. Discuss first - File an issue or discussion before coding
  2. Choose location - api/ for standard APIs, api/node/ for Node.js
  3. Implement - Follow patterns from existing APIs
  4. Add tests - Create .wd-test file with test cases
  5. Update docs - Add TypeScript definitions if needed
  6. Register - Add to module registry or global scope
See docs/api-updates.md for detailed guidelines.

Next steps

Contributing overview

Review contribution guidelines

Code style

Learn about code style requirements

Build docs developers (and LLMs) love