# How to Add and Secure Bridge Commands Between Zig and TypeScript in the Native SDK

> Learn to add and secure bridge commands between Zig and TypeScript in the Native SDK. Expose Zig functionality to TypeScript with declared permissions and origin whitelists for robust security.

- Repository: [Vercel Labs/native](https://github.com/vercel-labs/native)
- Tags: how-to-guide
- Published: 2026-07-18

---

**You can expose Zig functionality to TypeScript by declaring bridge commands in `app.zon`, implementing the handler in Zig, and registering it with `native_sdk.BridgeDispatcher`, while the SDK enforces security through declared permissions and origin whitelists.**

The vercel-labs/native repository provides a Native SDK that enables Zig-based applications to communicate with JavaScript/TypeScript layers through a strictly controlled bridge. Understanding how to properly add and secure bridge commands between Zig and TypeScript ensures your native capabilities remain sandboxed while providing rich functionality to the frontend.

## Understanding the Bridge Architecture

The bridge system consists of several tightly integrated components that handle everything from manifest parsing to runtime security enforcement.

**`src/tooling/manifest.zig`** parses the `bridge_commands` array from your `app.zon` and creates `BridgeCommandMetadata` objects containing the command name, required permissions, and allowed origins.

**`src/tooling/templates.zig`** generates TypeScript stubs for each declared command, wiring them to the `native_sdk.BridgeDispatcher` runtime.

**`BridgeDispatcher`** (`native_sdk.BridgeDispatcher`) receives JSON payloads from the JavaScript side, validates security metadata, and routes requests to the corresponding Zig handlers.

**`BridgePolicy`** (`builtin_bridge`) determines which bridge capabilities are enabled (e.g., `js_bridge`) and can be customized per application.

**Security Metadata** includes `permissions` (native capabilities like `filesystem`) and `origins` (whitelisted URLs) declared per-command in `app.zon`.

When invoked, the flow follows this sequence: the TypeScript stub sends a JSON request over the bridge, the runtime validates the caller's origin and permissions, dispatches to the Zig handler, and returns the JSON response. Any security check failure results in a clear error that prevents privilege escalation.

## Declaring Bridge Commands in app.zon

Begin by defining your command in the application manifest. The `bridge.commands` array accepts objects containing the command name, required permissions, and authorized origins.

```zon
// app.zon
bridge = {
  commands = [
    {
      name = "native.ping"
      permissions = ["filesystem"]
      origins = ["zero://app"]
    }
  ]
}

```

The `name` field uses dot notation to organize commands namespaces. The `permissions` array lists native capabilities the command requires, while `origins` restricts which URLs can invoke the command.

## Implementing Zig Handlers

Create a Zig function that matches the expected bridge handler signature. The function receives an allocator for response memory and a JSON payload from the TypeScript caller.

```zig
// src/bridge.zig
const std = @import("std");
const native_sdk = @import("native_sdk");

pub fn native_ping(
    allocator: *std.mem.Allocator,
    payload: []const u8,
) ![]const u8 {
    // Example: simply echo the payload back as JSON
    const response = try std.json.stringifyAlloc(
        allocator,
        .{ .message = "pong", .payload = payload },
        .{},
    );
    return response;
}

```

Bridge handlers must return a JSON-formatted byte slice allocated with the provided allocator, which the SDK frees after sending the response to JavaScript.

## Registering Commands with BridgeDispatcher

Initialize the dispatcher and register your handler to make it available to the TypeScript runtime. This typically occurs during application startup.

```zig
// src/main.zig
const dispatcher = native_sdk.BridgeDispatcher.init(allocator);
try dispatcher.registerCommand("native.ping", native_ping);

```

The `registerCommand` method in `src/tooling/manifest.zig` consumes the parsed metadata from `app.zon` and maps the command string to your Zig function pointer.

## Calling Commands from TypeScript

Once registered, call the bridge command through the auto-generated TypeScript stubs. The stubs handle JSON serialization and communication with the Zig runtime.

```typescript
import { native } from "./bridge";

async function demo() {
  const result = await native.ping({ hello: "world" });
  console.log(result); // → { message: "pong", payload: {...} }
}

demo();

```

The [`bridge.ts`](https://github.com/vercel-labs/native/blob/main/bridge.ts) module exports type-safe wrappers that correspond directly to your Zig implementations, with the `native.ping` function internally sending JSON RPC messages to the dispatcher.

## Securing Bridge Commands with Permissions and Origins

Security enforcement happens automatically at runtime based on your `app.zon` declarations. The SDK validates both the calling origin and command permissions before executing the handler.

| Field | Purpose | Example Values |
|-------|---------|----------------|
| **permissions** | Native capabilities the command may access | `"filesystem"`, `"zero://app"` |
| **origins** | Allowed caller URLs | `"zero://app"`, `"https://myapp.com"` |

The runtime performs strict validation before dispatching:

```zig
if (!self.isOriginAllowed(request.origin, command.origins)) return error.ForbiddenOrigin;
if (!self.hasPermissions(request.caller, command.permissions)) return error.InsufficientPermissions;

```

To maintain security:
- Apply the **least privilege** principle by listing only required permissions
- Specify **exact origins** rather than wildcards to prevent cross-site invocation
- **Validate input** in your Zig handlers even after permission checks pass
- Run `zig build test` to execute the SDK's test suite (`tests/ts-core/*_e2e_tests.zig`), which verifies that permissions and origins match declared metadata

## Summary

- Declare bridge commands in `app.zon` with specific `permissions` and `origins` to establish the security boundary
- Implement handlers using the standard Zig signature accepting an allocator and JSON payload, returning allocated JSON responses
- Register handlers using `BridgeDispatcher.registerCommand()` to wire them to the TypeScript runtime
- Access commands through auto-generated TypeScript stubs that handle the JSON RPC communication
- Validate origins and permissions at runtime to prevent privilege escalation and unauthorized native capability access

## Frequently Asked Questions

### How does the Native SDK prevent unauthorized bridge access?

The SDK enforces security at the dispatcher level through origin validation and permission checks defined in `app.zon`. Before executing any handler, the runtime verifies that the calling page's origin appears in the command's whitelist and that the command's declared permissions cover the requested native capabilities. Any mismatch returns a `ForbiddenOrigin` or `InsufficientPermissions` error.

### What is the correct function signature for a Zig bridge handler?

Bridge handlers must accept a `*std.mem.Allocator` and a `[]const u8` JSON payload, returning `![]const u8`. According to the source code in `src/bridge.zig`, the standard pattern is `pub fn command_name(allocator: *std.mem.Allocator, payload: []const u8) ![]const u8`, where the returned slice must be JSON-formatted and allocated with the provided allocator.

### Where are the TypeScript bridge stubs generated?

The `src/tooling/templates.zig` file processes your `app.zon` declarations to generate TypeScript stubs in [`bridge.ts`](https://github.com/vercel-labs/native/blob/main/bridge.ts). These stubs export async functions (such as `native.ping()`) that serialize arguments to JSON and communicate with `native_sdk.BridgeDispatcher`, providing type-safe access to your Zig implementations.

### How do I test bridge command security locally?

Run `zig build test` to execute the end-to-end test suite located in `tests/ts-core/*_e2e_tests.zig`. These tests verify that the manifest parsing correctly associates permissions and origins with commands, and that the dispatcher properly rejects requests from unauthorized origins or attempts to access undeclared capabilities.