---
title: Bindgen
description: Bindgen for Bun
---

<Note>This document is for maintainers and contributors to Bun, and describes internal implementation details.</Note>

The bindings generator scans for `*.bind.ts` files to find function and class
definitions, and generates glue code to interop between JavaScript and native
code.

There are other code generators and systems that achieve similar purposes;
the following will all eventually be phased out in favor of this one:

- "Classes generator", converting `*.classes.ts` for custom classes.
- "JS2Native", allowing ad-hoc calls from `src/js` to native code.

## Creating JS Functions in Rust

Given a file implementing a function, such as `add`:

```rust src/jsc/bindgen_test.rs
use crate::{JSGlobalObject, JsResult};
use crate::r#gen::bindgen_test as generated;

pub fn add(global: &JSGlobalObject, a: i32, b: i32) -> JsResult<i32> {
    match a.checked_add(b) {
        Some(v) => Ok(v),
        None => {
            // Binding functions can propagate out-of-memory and JS exceptions
            // directly; other failures (like this integer overflow) must be
            // converted into a thrown error. Remember to be descriptive.
            Err(global.throw(format_args!("Integer overflow while adding")))
        }
    }
}
```

Then describe the API schema using a `.bind.ts` file. The binding file goes
next to the Rust file.

```ts src/jsc/bindgen_test.bind.ts icon="/icons/typescript.svg"
import { fn, t } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    a: t.i32,
    b: t.i32.default(-1),
  },
  ret: t.i32,
});
```

This function declaration is equivalent to:

```ts
/**
 * Throws if zero arguments are provided.
 * Wraps out of range numbers using modulo.
 */
declare function add(a: number, b: number = -1): number;
```

The code generator emits a C++ thunk that validates and coerces the JS
arguments, then calls the Rust implementation. On the Rust side bindgen emits
nothing; both the dispatch shim the thunk calls
(`bindgen_Bindgen_test_dispatchAdd1` in `src/runtime/hw_exports.rs`, which
calls `add`) and the `create_*_callback` module in
`src/jsc/bindings/GeneratedBindings.rs` are hand-written. The module is
reachable as `crate::r#gen::<basename>` (for `bindgen_test.bind.ts`, that's
`crate::r#gen::bindgen_test`). To construct a `JSFunction` wrapping the
native implementation, use `generated::create_add_callback(global)`:

```rust
use crate::r#gen::bindgen_test as generated;

let js_fn: JSValue = generated::create_add_callback(global);
```

In JS files in `src/js/`, `$bindgenFn("bindgen_test.bind.ts", "add")` returns
a handle to the implementation, through a hand-written
`js2native_bindgen_<basename>_<fn>` export in `src/runtime/hw_exports.rs`.

Exported bindgen functions are snake_cased on the Rust side
(`requiredAndOptionalArg` → `required_and_optional_arg`). The hand-written
callback constructor follows the same convention
(`create_required_and_optional_arg_callback`).

## Strings

To receive a string, use [`t.DOMString`](https://webidl.spec.whatwg.org/#idl-DOMString), [`t.ByteString`](https://webidl.spec.whatwg.org/#idl-ByteString), or [`t.USVString`](https://webidl.spec.whatwg.org/#idl-USVString). These map directly to their WebIDL counterparts and have slightly different conversion logic. Bindgen passes `bun_core::String` to native code in all cases.

When in doubt, use DOMString.

`t.UTF8String` works in place of `t.DOMString`, but eagerly converts to UTF-8.
The native callback receives a `&[u8]` slice (WTF-8 data) that is
freed after the function returns.

TLDRs from the WebIDL spec:

- ByteString can only contain valid latin1 characters. It is not safe to assume `bun_core::String` is already in 8-bit format, but it is extremely likely.
- USVString does not contain invalid surrogate pairs, so its text can be represented correctly in UTF-8.
- DOMString is the loosest but also the most recommended strategy.

## Function Variants

The `variants` key declares multiple variants (also known as overloads) of a function.

```ts
import { fn, t } from "bindgen";

export const action = fn({
  variants: [
    {
      args: {
        a: t.i32,
      },
      ret: t.i32,
    },
    {
      args: {
        a: t.DOMString,
      },
      ret: t.DOMString,
    },
  ],
});
```

Each variant gets a numbered Rust function:

```rust
pub fn action1(a: i32) -> i32 {
    a
}

pub fn action2(a: bun_core::String) -> bun_core::String {
    a
}
```

## `t.dictionary`

A `dictionary` describes a JavaScript object, typically a function input. For function outputs, prefer a class type so you can add methods and support destructuring.

## Enumerations

`t.stringEnum` creates a [WebIDL enumeration](https://webidl.spec.whatwg.org/#idl-enums) and generates a new enum type for it.

An example of `stringEnum` from `fmt_jsc.bind.ts` / `bun:internal-for-testing`:

```ts
export const Formatter = t.stringEnum("highlight-javascript", "highlight-javascript-redacted", "escape-powershell");

export const fmtString = fn({
  args: {
    global: t.globalObject,
    code: t.UTF8String,
    formatter: Formatter,
  },
  ret: t.DOMString,
});
```

On the Rust side, the enum is mirrored as a `#[repr(u8)]` enum. Bindgen
**sorts `t.stringEnum` values alphabetically** before emitting the C++
`enum class`, so discriminants must match the generated header's order, not
the `.bind.ts` declaration order:

```rust
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Formatter {
    EscapePowershell = 0,
    HighlightJavascript = 1,
    HighlightJavascriptRedacted = 2,
}

pub fn fmt_string(
    global: &JSGlobalObject,
    code: &[u8],
    formatter_id: Formatter,
) -> JsResult<bun_core::String> {
    // ...
}
```

WebIDL strongly encourages kebab-case for enumeration values, to be consistent with existing Web APIs.

## `t.oneOf`

A `oneOf` is a union of two or more types. It is represented as a Rust
`enum` with one variant per member type.

## Attributes

You can chain attributes onto `t.*` types. On all types:

- `.required`, in dictionary parameters only
- `.optional`, in function arguments only
- `.default(T)`

When a value is `.optional`, it is lowered to a Rust `Option<T>`:

```ts
export const requiredAndOptionalArg = fn({
  args: {
    a: t.boolean,
    b: t.usize.optional,
    c: t.i32.enforceRange(0, 100).default(42),
    d: t.u8.optional,
  },
  ret: t.i32,
});
```

```rust
pub fn required_and_optional_arg(a: bool, b: Option<usize>, c: i32, d: Option<u8>) -> i32 {
    // ...
}
```

Depending on the type, more attributes are available. See the type definitions
in auto-complete for details. You can apply only one of these three attributes,
and you must apply it last.

### Integer Attributes

Integer types take `clamp` or `enforceRange` to customize overflow behavior:

```ts
import { fn, t } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    // enforce in i32 range
    a: t.i32.enforceRange(),
    // clamp to u16 range
    b: t.u16,
    // enforce in arbitrary range, with a default if not provided
    c: t.i32.enforceRange(0, 1000).default(5),
    // clamp to arbitrary range, or None
    d: t.u16.clamp(0, 10).optional,
  },
  ret: t.i32,
});
```

Node.js validator functions such as `validateInteger` and `validateNumber`
are also available. Use these when implementing Node.js APIs so the error
messages match Node exactly.

Unlike `enforceRange`, which is taken from WebIDL, the `validate*` functions
are much stricter about the input they accept. For example, Node's numerical
validator checks `typeof value === 'number'`, while WebIDL uses `ToNumber` for
lossy conversion.

```ts
import { fn, t } from "bindgen";

export const add = fn({
  args: {
    global: t.globalObject,
    // throw if not given a number
    a: t.f64.validateNumber(),
    // valid in i32 range
    b: t.i32.validateInt32(),
    // f64 within safe integer range
    c: t.f64.validateInteger(),
    // f64 in given range
    d: t.f64.validateNumber(-10000, 10000),
  },
  ret: t.i32,
});
```

## Callbacks

TODO

## Classes

TODO
