Skip to main content
JSG (JavaScript Glue) is the macro-driven C++/V8 binding layer that bridges C++ types with JavaScript. It provides automatic type conversion and exposes C++ classes as JavaScript-visible resources.

Overview

JSG uses C++ macros to declare types, methods, and properties that should be visible from JavaScript. The system handles:
  • Type conversion between C++ and JavaScript
  • Memory management and garbage collection
  • Promise integration
  • Error handling
  • Module system
For a complete reference, see src/workerd/jsg/README.md in the workerd repository.

Core concepts

Resource types

Resource types are reference-counted C++ objects exposed to JavaScript:
class Response: public jsg::Object {
public:
  static jsg::Ref<Response> constructor(
      jsg::Optional<Body> body,
      jsg::Optional<ResponseInit> init);

  int getStatus() { return status; }
  kj::String getStatusText() { return statusText; }

  JSG_RESOURCE_TYPE(Response) {
    JSG_METHOD(constructor);
    JSG_READONLY_PROTOTYPE_PROPERTY(status, getStatus);
    JSG_READONLY_PROTOTYPE_PROPERTY(statusText, getStatusText);
  }

private:
  int status;
  kj::String statusText;
};
Key points:
  • Inherit from jsg::Object
  • Use jsg::Ref<T> for references
  • Allocate with js.alloc<T>(args...)
  • Must implement visitForGc() if holding GC-visitable types

Struct types

Structs are value types that are deep-copied between C++ and JavaScript:
struct RequestInit {
  jsg::Optional<kj::String> method;
  jsg::Optional<Headers> headers;
  jsg::Optional<Body> body;

  JSG_STRUCT(method, headers, body);
};

Type mapping

Primitive types

C++ TypeJavaScript Type
boolboolean
doublenumber
int, int32_tnumber
int64_t, uint64_tbigint
kj::Stringstring
kj::DateDate

Container types

C++ TypeJavaScript TypeNotes
kj::Array<T>ArrayConverts to/from JS arrays
kj::Maybe<T>T | nullnull represents empty
jsg::Optional<T>T | undefinedundefined represents empty
jsg::Dict<V>Record<string, V>Plain object with string keys
kj::OneOf<T...>T1 | T2 | ...Union type with Web IDL validation

Advanced types

C++ TypePurpose
jsg::Promise<T>JavaScript promises
jsg::Function<Ret(Args...)>JavaScript functions callable from C++
jsg::V8Ref<T>Persistent reference to V8 value
jsg::BufferSourceArrayBuffer or TypedArray
jsg::Sequence<T>Any iterable that produces T

Binding macros

Resource type declaration

JSG_RESOURCE_TYPE(TypeName) {
  // Binding declarations
}
Optionally accepts compatibility flags:
JSG_RESOURCE_TYPE(TypeName, CompatibilityFlags::Reader flags) {
  if (flags.getExperimentalFeature()) {
    JSG_METHOD(experimentalMethod);
  }
}

Methods

JSG_METHOD(methodName);
JSG_METHOD_NAMED(jsName, cppMethod);
JSG_STATIC_METHOD(staticMethod);
Example:
class MyAPI: public jsg::Object {
  kj::String greet(kj::String name) {
    return kj::str("Hello, ", name);
  }

  static jsg::Ref<MyAPI> create() {
    return jsg::alloc<MyAPI>();
  }

  JSG_RESOURCE_TYPE(MyAPI) {
    JSG_METHOD(greet);
    JSG_STATIC_METHOD(create);
  }
};

Properties

// Read-only property on prototype
JSG_READONLY_PROTOTYPE_PROPERTY(name, getter);

// Read-write property on prototype
JSG_PROTOTYPE_PROPERTY(name, getter, setter);

// Read-only property on instance
JSG_READONLY_INSTANCE_PROPERTY(name, getter);

// Static constant on constructor
JSG_STATIC_CONSTANT(name);
Prefer JSG_PROTOTYPE_PROPERTY over JSG_INSTANCE_PROPERTY. Instance properties break GC optimization and should only be used with specific justification.
Example:
class Request: public jsg::Object {
  kj::String getUrl() { return url; }
  void setUrl(kj::String newUrl) { url = kj::mv(newUrl); }

  JSG_RESOURCE_TYPE(Request) {
    JSG_PROTOTYPE_PROPERTY(url, getUrl, setUrl);
  }

private:
  kj::String url;
};

Inheritance

JSG_INHERIT(BaseType);
JSG_INHERIT_INTRINSIC(v8::kErrorPrototype);
Example:
class CustomError: public jsg::Object {
  JSG_RESOURCE_TYPE(CustomError) {
    JSG_INHERIT_INTRINSIC(v8::kErrorPrototype);
    JSG_READONLY_PROTOTYPE_PROPERTY(code, getCode);
  }

  int getCode() { return errorCode; }

private:
  int errorCode;
};

Memory management

Garbage collection

Resource types that hold references to other GC-managed objects must implement visitForGc():
class MyResource: public jsg::Object {
  void visitForGc(jsg::GcVisitor& visitor) {
    visitor.visit(otherResource);
    visitor.visit(jsValue);
  }

private:
  jsg::Ref<OtherResource> otherResource;
  jsg::V8Ref<v8::Object> jsValue;
};
Types that must be visited:
  • jsg::Ref<T>
  • jsg::V8Ref<T>
  • jsg::JsRef<T>
  • jsg::Function<T>
  • jsg::Promise<T>
  • jsg::Promise<T>::Resolver
  • jsg::BufferSource
  • jsg::Name
  • kj::Maybe<T> when T is GC-visitable
Missing a visit() call causes GC corruption and hard-to-debug crashes.

Ownership rules

// GOOD: Pass by reference (borrowed)
void processRequest(Request& request);

// GOOD: Take ownership with jsg::Ref
void storeRequest(jsg::Ref<Request> request);

// BAD: Raw pointer with unclear lifetime
void processRequest(Request* request);

Error handling

Use JSG error macros for JavaScript-facing errors:
// Require a condition, throw if false
JSG_REQUIRE(value > 0, TypeError, "Value must be positive");

// Require non-null, throw if empty
auto& item = JSG_REQUIRE_NONNULL(maybeItem, 
    DOMNotFoundError, "Item not found");

// Unconditionally throw
JSG_FAIL_REQUIRE(DOMInvalidStateError, "Cannot call after closed");
Available error types:
  • TypeError - Wrong argument type
  • RangeError - Value out of range
  • Error - Generic error
  • DOMException types - DOMInvalidStateError, DOMNotSupportedError, etc.

Promise integration

Bridge between KJ promises and JavaScript promises:
// KJ promise → JS promise
jsg::Promise<Response> fetch(
    jsg::Lock& js,
    kj::String url) {
  auto kjPromise = httpClient.request(url);
  return js.awaitIo(kj::mv(kjPromise),
      [](jsg::Lock& js, HttpResponse response) {
    return js.alloc<Response>(kj::mv(response));
  });
}

// JS promise → KJ promise
kj::Promise<kj::String> processPromise(
    jsg::Lock& js,
    jsg::Promise<jsg::Ref<Response>> promise) {
  return js.awaitJs(kj::mv(promise)).then(
      [](jsg::Lock& js, jsg::Ref<Response> response) {
    return response->getText();
  });
}

Module system

Register modules that can be imported by workers:
class MyModule: public jsg::Object {
  static jsg::Ref<MyModule> constructor() {
    return jsg::alloc<MyModule>();
  }

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

  JSG_RESOURCE_TYPE(MyModule) {
    JSG_METHOD(constructor);
    JSG_METHOD(doSomething);
  }
};

// Register in module registry
registry.addBuiltinModule("my-module:core",
    [](jsg::Lock& js, const ModuleRegistry::Type& type) {
  return jsg::alloc<MyModule>();
});

Best practices

Use the correct macros

// GOOD: Prototype property (overridable, GC-friendly)
JSG_READONLY_PROTOTYPE_PROPERTY(status, getStatus);

// AVOID: Instance property (not overridable, GC-unfriendly)
JSG_READONLY_INSTANCE_PROPERTY(status, getStatus);

Follow Web IDL patterns

// GOOD: Accept union type
void send(kj::OneOf<kj::String, kj::Array<byte>> data);

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

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

Handle the Lock correctly

// GOOD: Take lock by reference in methods
void doAsync(jsg::Lock& js) {
  auto promise = asyncOperation();
  return js.awaitIo(kj::mv(promise));
}

// BAD: Store lock or pass into promise continuation
void doBad(jsg::Lock& js) {
  return asyncOperation().then([&js]() {  // BAD: captures lock
    // This is unsafe
  });
}

Next steps

I/O subsystem

Learn about the I/O and actor storage architecture

API layer

Explore the runtime API implementations

Build docs developers (and LLMs) love