# How to Add a New CDP Domain to Lightpanda: A Step-by-Step Guide

> Learn how to add a new CDP domain to Lightpanda. Follow our step-by-step guide to implement processMessage and register your domain in cdp.zig.

- Repository: [Lightpanda/browser](https://github.com/lightpanda-io/browser)
- Tags: how-to-guide
- Published: 2026-03-14

---

**To add a new CDP domain to Lightpanda, create a domain module in `src/cdp/domains/<Domain>.zig` implementing the `processMessage` function, then register it in `src/cdp/cdp.zig` by extending the length-based switch dispatcher using the `asUint` helper.**

Lightpanda implements the Chrome DevTools Protocol (CDP) by routing JSON-RPC commands to specialized domain modules. Adding a new CDP domain requires implementing the message processing logic and wiring it into the central dispatcher located in `src/cdp/cdp.zig`.

## Creating the Domain Module

Start by creating a new file in `src/cdp/domains/<YourDomain>.zig`. This file must export a `processMessage` function that parses the action, validates parameters, and dispatches to the appropriate handler.

The standard pattern uses `std.meta.stringToEnum` to match the action name, `cmd.params` to deserialize JSON parameters, and `cmd.sendResult` to return responses:

```zig
const std = @import("std");
const log = @import("../log.zig");

pub fn processMessage(cmd: anytype) !void {
    const action = std.meta.stringToEnum(enum { echo }, cmd.input.action) orelse return error.UnknownMethod;
    switch (action) {
        .echo => {
            const params = (try cmd.params(struct {
                message: []const u8,
            })) orelse return error.InvalidParams;
            try cmd.sendResult(.{ .message = params.message }, .{});
        },
    }
}

```

Key implementation details from the Lightpanda source code:
- **Action parsing**: Use an anonymous enum (e.g., `enum { echo }`) with `std.meta.stringToEnum` to convert the `cmd.input.action` string into a typed value.
- **Parameter extraction**: The `cmd.params` method takes a struct type reflecting the expected JSON schema and returns an optional pointer.
- **Response handling**: Use `cmd.sendResult` for success responses and `cmd.sendError` for protocol errors.

## Registering the Domain in the Dispatcher

Edit `src/cdp/cdp.zig` and locate the `dispatchCommand` function. Lightpanda uses a compile-time optimized dispatcher that matches domain names by their byte length to avoid runtime string comparisons.

For a domain named `"Example"` (7 characters), extend the existing `case 7` block using the `asUint` helper to convert the string into a `u56` integer:

```zig
7 => switch (@as(u56, @bitCast(domain[0..7].*))) {
    asUint(u56, "Runtime") => return @import("domains/runtime.zig").processMessage(command),
    asUint(u56, "Example") => return @import("domains/example.zig").processMessage(command),
    else => {},
},

```

The `asUint` macro (defined at lines 13-15 in `src/cdp/cdp.zig`) enables compile-time constant generation. The integer width must match the domain length (e.g., `u56` for 7 bytes, `u24` for 3 bytes).

## Testing the New Domain

Add unit tests in `src/cdp/testing.zig` to validate the domain's behavior. The testing harness provides `testing.context()` and assertion methods like `expectSentResult`:

```zig
test "cdp: Example.echo" {
    var ctx = testing.context();
    defer ctx.deinit();

    try ctx.processMessage(.{
        .id = 1,
        .method = "Example.echo",
        .params = .{ .message = "hello world" },
    });
    try ctx.expectSentResult(.{ .message = "hello world" }, .{ .id = 1, .index = 0 });
}

```

Run the full test suite to verify integration:

```bash
zig build test

```

## Architectural Details

The CDP implementation in `src/cdp/cdp.zig` (lines 22-38) extracts the domain name from the `method` string (`"<Domain>.<Action>"`) and routes commands via the `dispatchCommand` function. Each domain module follows a consistent contract:

- **File location**: `src/cdp/domains/<name>.zig`
- **Entry point**: `processMessage(cmd: anytype) !void`
- **Error handling**: Return `error.UnknownMethod` for unrecognized actions or `error.InvalidParams` for malformed input

Existing domains like `Runtime` (`src/cdp/domains/runtime.zig`) and `Network` serve as reference implementations for the pattern.

## Summary

- Create a new domain file in `src/cdp/domains/<Domain>.zig` implementing `processMessage` with action enum parsing and parameter validation.
- Register the domain in `src/cdp/cdp.zig` by adding a case to the length-based `switch` in `dispatchCommand` using the appropriate `asUint` width.
- Write tests in `src/cdp/testing.zig` using the `testing.context()` harness and run `zig build test` to verify.
- Document the domain's methods and parameters for downstream developers.

## Frequently Asked Questions

### What is the maximum length for a CDP domain name in Lightpanda?

The dispatcher supports domain names up to the maximum integer width used in the switch cases (typically `u56` for 7 characters or `u64` for 8 characters). For longer domain names, you would need to extend the `dispatchCommand` switch with a new case block using a wider integer type (e.g., `u64` for 8 bytes) following the existing pattern in `src/cdp/cdp.zig`.

### Why does Lightpanda use length-based matching instead of string comparison?

The dispatcher converts domain name bytes into integers (e.g., `u56` for 7-character names) and matches them at compile time using the `asUint` helper. This eliminates runtime string hashing and comparison overhead, resulting in faster command dispatch with zero-allocation parsing for the domain routing logic.

### How do I handle errors in a new CDP domain?

Return `error.UnknownMethod` when `std.meta.stringToEnum` fails to match the action, and `error.InvalidParams` when `cmd.params` returns null (indicating missing or malformed JSON parameters). The dispatcher automatically converts these into proper CDP error responses via `cmd.sendError`.

### Is it required to add tests in `src/cdp/testing.zig`?

While not strictly enforced by the compiler, the Lightpanda repository requires comprehensive test coverage for all CDP domains. You should add tests in `src/cdp/testing.zig` or create a dedicated test file that imports the testing harness. Use `testing.context()` to simulate CDP sessions and `expectSentResult` to verify JSON responses.