# workerd Error Handling and Exception Reporting: From C++ KJ Exceptions to JavaScript Errors

> Learn how workerd handles C++ KJ exceptions and JavaScript errors. See how exceptions are converted to V8 Error objects for seamless reporting and debugging.

- Repository: [Cloudflare/workerd](https://github.com/cloudflare/workerd)
- Tags: internals
- Published: 2026-03-18

---

**workerd bridges C++ and JavaScript errors by wrapping low-level `kj::Exception` objects with JSG macros that prefix error types, then converting them to native V8 Error objects via the `exceptionToJs` function in `src/workerd/jsg/util.c++`.**

The cloudflare/workerd runtime powers Cloudflare Workers by executing JavaScript in V8 isolates backed by high-performance C++ infrastructure. Understanding **workerd error handling and exception reporting** is essential for runtime developers extending native APIs or debugging boundary issues between C++ system code and Worker scripts.

## The Three-Layer Error Model in workerd

workerd implements a tiered architecture that translates low-level system failures into standard JavaScript exceptions that behave identically to native ECMAScript errors.

### Layer 1: KJ Exceptions for C++ Failures

At the foundation, workerd uses `kj::Exception` from the KJ C++ library to represent failures such as file I/O errors, assertion failures, and internal logic violations. This class is defined in the external [`src/kj/debug.h`](https://github.com/cloudflare/workerd/blob/main/src/kj/debug.h) dependency and serves as the canonical error type throughout the C++ codebase.

### Layer 2: JSG Macros as the Bridge

The **JSG (JavaScript Glue)** layer provides macros in [`src/workerd/jsg/exception.h`](https://github.com/cloudflare/workerd/blob/main/src/workerd/jsg/exception.h) that enrich `kj::Exception` objects with JavaScript-compatible metadata. These macros prepend specific prefixes like `jsg.TypeError:` or `jsg.Error:` to the exception description, allowing downstream code to instantiate the correct JavaScript error constructor.

```cpp
// src/workerd/jsg/exception.h
#define JSG_FAIL_REQUIRE(jsErrorType, ...) \
  KJ_FAIL_REQUIRE(kj::str(JSG_EXCEPTION(jsErrorType) ": ", ##__VA_ARGS__))

```

When you invoke `JSG_FAIL_REQUIRE(TypeError, "message")`, the macro generates a `kj::Exception` whose description begins with `jsg.TypeError: message`. Related macros like `JSG_REQUIRE`, `JSG_ASSERT`, and `JSG_WARN_ONCE` provide variations that either maintain process continuity or log warnings without throwing.

### Layer 3: V8 Error Object Conversion

The `exceptionToJs` function in `src/workerd/jsg/util.c++` performs the final transformation from C++ exceptions to V8 JavaScript objects. This function parses the prefix injected by JSG macros, strips it to extract the human-readable message, and invokes the appropriate V8 factory method:

```cpp
// src/workerd/jsg/util.c++ (conceptual)
v8::Local<v8::Value> exceptionToJs(
    v8::Isolate* isolate,
    kj::Exception&& exception,
    ExceptionToJsOptions options) {

  const kj::StringPtr desc = exception.getDescription();
  auto errType = parseJsErrorType(desc);  // extracts "jsg.TypeError"
  
  v8::Local<v8::String> msg = 
      v8::String::NewFromUtf8(isolate, desc.slice(errType.size() + 2).cStr())
          .ToLocalChecked();

  if (errType == "jsg.TypeError") {
    return v8::Exception::TypeError(msg);
  } else if (errType == "jsg.Error") {
    return v8::Exception::Error(msg);
  }
  // ... additional error type branches
}

```

### Schema Parsing Error Collection

For configuration errors encountered while loading Cap'n Proto schemas (such as malformed `workerd.capnp` files), workerd uses the `SchemaFileImpl::ErrorReporter` class defined in `src/workerd/server/workerd.c++`. This reporter collects validation failures in a vector for batch display to the user, distinguishing parsing-time errors from runtime exceptions.

## How `exceptionToJs` Transforms KJ Exceptions into V8 Errors

The conversion logic in `src/workerd/jsg/util.c++` handles several specialized exception categories beyond basic type mapping. The function checks for **do-not-log** markers via `isDoNotLogException()`, which prevents duplicate logging when a Worker script has already handled the error. It also detects **tunneled exceptions** and **internal errors** using `isTunneledException()` and `isInternal()` helpers declared in [`src/workerd/jsg/exception.h`](https://github.com/cloudflare/workerd/blob/main/src/workerd/jsg/exception.h).

For internal exceptions—operational failures that should not expose implementation details to user code—the function attaches hidden metadata to the V8 error object. This tagging enables the `LOG_EXCEPTION_IF_INTERNAL` macro (defined near line 1230 of [`src/workerd/jsg/exception.h`](https://github.com/cloudflare/workerd/blob/main/src/workerd/jsg/exception.h)) to write syslog entries for platform monitoring while presenting a generic message to the Worker script.

The final step in the error pipeline occurs in `src/workerd/io/worker.c++` around line 1701, where the runtime calls `isolate->ThrowException(exceptionToJs(...))` to abort the current JavaScript execution with the converted error object.

## Error Generation Points in the Codebase

workerd generates exceptions at three primary boundaries between native code and JavaScript execution.

### Runtime API Validation with JSG_REQUIRE

Most public API entry points validate arguments using `JSG_REQUIRE` or `JSG_FAIL_REQUIRE` before performing operations. For example, in `src/workerd/api/web-socket.c++`, an invalid `accept()` call triggers:

```cpp
JSG_FAIL_REQUIRE(TypeError, 
    "Can't return WebSocket in a Response after calling accept().");

```

This pattern ensures that type mismatches, range errors, and illegal state transitions surface as standard JavaScript exceptions that developers can catch with standard `try/catch` blocks.

### Configuration Parsing in SchemaFileImpl

During startup, when workerd loads Worker configurations from Cap'n Proto schemas, the `ErrorReporter` implementation in `src/workerd/server/workerd.c++` captures syntax errors and validation failures. Unlike runtime errors that throw immediately, these parsing errors accumulate in a collection for comprehensive reporting before the process exits.

### Promise Rejection Handling

For asynchronous operations, the promise wrapper in `src/workerd/jsg/promise.c++` intercepts any `kj::Exception` thrown from C++ callbacks. It converts the exception using `exceptionToJs` and rejects the associated JavaScript promise with the resulting `Error` object, ensuring that async/await code receives exceptions through standard promise rejection channels.

## How workerd Reports Errors to Worker Scripts

Once converted, errors propagate to Worker scripts through three distinct mechanisms depending on the execution context.

**Immediate synchronous throws** abort the current V8 call stack when `isolate->ThrowException()` is invoked during a JavaScript-to-C++ binding call. The V8 engine unwinds the stack until caught by a JavaScript `try/catch` block or the global error handler.

**Promise rejections** occur at asynchronous boundaries. The converted error becomes the rejection reason passed to `.catch()` handlers or the `catch` clause of an `async/await` sequence.

**Internal logging** happens conditionally through `LOG_EXCEPTION_IF_INTERNAL`. This macro evaluates whether an exception carries the internal flag set by `exceptionToJs`. User-script errors remain unlogged to prevent noise, while infrastructure failures write to Cloudflare's operational monitoring systems.

## Practical Code Examples for workerd Error Handling

### Raising TypeErrors from C++ APIs

When extending workerd with custom native APIs, use `JSG_REQUIRE` to enforce preconditions that generate catchable JavaScript errors:

```cpp
#include "workerd/jsg/exception.h"

void MyDatabase::query(jsg::Lock& js, kj::StringPtr sql) {
  // Enforce query length limits with a descriptive TypeError
  JSG_REQUIRE(sql.size() < 10000, TypeError, 
      "SQL query exceeds maximum length of 10000 characters, got ", sql.size());
  
  // ... execute query ...
}

```

When a Worker script calls this method with an oversized string, the runtime throws a JavaScript `TypeError` identical to one created via `new TypeError()` in standard ECMAScript.

### Handling Errors in JavaScript Workers

Worker scripts catch native errors using standard JavaScript exception handling:

```javascript
export default {
  async fetch(request, env) {
    try {
      env.myDatabase.query("SELECT * FROM ".repeat(1000));
    } catch (e) {
      // e.name === "TypeError"
      // e.message === "SQL query exceeds maximum length of 10000 characters, got 12000"
      return new Response(`Invalid query: ${e.message}`, { status: 400 });
    }
    return new Response("Success");
  }
}

```

### Distinguishing Internal and User Errors

For operations that should never be exposed to user scripts but require platform monitoring, use the standard error macros without special prefixes, allowing the internal tagging mechanism to classify the error:

```cpp
void SystemCache::evictAll() {
  // This operation is restricted at the infrastructure level
  JSG_FAIL_REQUIRE(Error, "Cache evictAll() disabled in production isolates");
}

```

According to the workerd source code, this generates an exception tagged as internal. The script receives a generic `Error` object, while `LOG_EXCEPTION_IF_INTERNAL` writes the detailed failure to Cloudflare's internal telemetry systems.

## Summary

- **workerd error handling** operates through a three-tier architecture: `kj::Exception` for C++, JSG macros for metadata enrichment, and `exceptionToJs` for V8 conversion.
- The **`exceptionToJs`** function in `src/workerd/jsg/util.c++` parses error type prefixes and instantiates appropriate V8 Error, TypeError, or RangeError objects.
- **JSG_REQUIRE** and **JSG_FAIL_REQUIRE** in [`src/workerd/jsg/exception.h`](https://github.com/cloudflare/workerd/blob/main/src/workerd/jsg/exception.h) provide the standard mechanism for C++ APIs to throw catchable JavaScript exceptions.
- Internal errors are distinguished from user errors via metadata flags, enabling selective logging through `LOG_EXCEPTION_IF_INTERNAL` while preventing information leakage.
- Schema parsing errors use the **ErrorReporter** pattern in `src/workerd/server/workerd.c++` for batch validation reporting during configuration loading.

## Frequently Asked Questions

### What is the difference between JSG_REQUIRE and JSG_FAIL_REQUIRE in workerd?

**`JSG_REQUIRE`** evaluates a condition and throws a `kj::Exception` only if the condition fails, similar to an assertion with custom error types. **`JSG_FAIL_REQUIRE`** immediately throws the specified error without evaluating a condition, functioning like an unconditional `throw` statement. Both macros prefix the error description with strings like `jsg.TypeError:` that `exceptionToJs` parses to create the correct JavaScript error constructor.

### How does workerd prevent internal error details from leaking to user scripts?

The `exceptionToJs` function checks for internal exceptions using the `isInternal()` helper and attaches a hidden flag to the V8 error object. While the script receives a standard JavaScript `Error`, the `LOG_EXCEPTION_IF_INTERNAL` macro detects this flag and routes detailed diagnostic information to Cloudflare's internal monitoring systems only, filtering it from user-visible outputs.

### Where does workerd convert C++ exceptions into JavaScript Promise rejections?

The conversion happens in `src/workerd/jsg/promise.c++`, where the promise wrapper catches `kj::Exception` objects from C++ callbacks and invokes `js.exceptionToJs()` to create the rejection reason. This ensures that asynchronous native operations reject promises with proper JavaScript Error objects rather than failing silently or crashing the isolate.

### Can I customize error messages when extending workerd's C++ APIs?

Yes, when implementing new APIs in the `src/workerd/api/` directory, use the `JSG_REQUIRE` or `JSG_FAIL_REQUIRE` macros with printf-style arguments to construct dynamic error messages. The macros support variable interpolation (e.g., `JSG_REQUIRE(value > 0, RangeError, "Expected positive value, got ", value)`), and these messages propagate intact through `exceptionToJs` to the JavaScript `Error.prototype.message` property.