> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/oven-sh/bun/llms.txt
> Use this file to discover all available pages before exploring further.

# Foreign Function Interface (FFI)

> Call native C/C++, Rust, and Zig libraries from JavaScript using bun:ffi.

<Warning>
  `bun:ffi` is **experimental**, with known bugs and limitations, and should not be relied on in production. The most stable way to interact with native code from Bun is to write a Node-API (NAPI) module.
</Warning>

Bun's built-in `bun:ffi` module lets you call native shared libraries directly from JavaScript. It supports any language that exposes a C ABI, including C, C++, Rust, Zig, C#, Nim, and Kotlin.

## How it works

When you call `dlopen`, Bun generates and JIT-compiles C bindings that efficiently convert values between JavaScript types and native types. To compile these bindings, Bun embeds [TinyCC](https://github.com/TinyCC/tinycc), a small and fast C compiler. This approach makes `bun:ffi` roughly **2–6x faster** than Node.js FFI via Node-API.

## Basic usage

Use `dlopen` to load a shared library and declare the symbols you want to call.

```typescript theme={null}
import { dlopen, FFIType, suffix } from "bun:ffi";

// `suffix` resolves to the platform-appropriate extension:
// "dylib" on macOS, "so" on Linux, "dll" on Windows
const { symbols } = dlopen(`libmylib.${suffix}`, {
  add: {
    args: [FFIType.i32, FFIType.i32],
    returns: FFIType.i32,
  },
});

console.log(symbols.add(1, 2)); // 3
```

### Calling SQLite

A complete example: print the SQLite version number.

```typescript theme={null}
import { dlopen, FFIType, suffix } from "bun:ffi";

const {
  symbols: { sqlite3_libversion },
} = dlopen(`libsqlite3.${suffix}`, {
  sqlite3_libversion: {
    args: [],
    returns: FFIType.cstring,
  },
});

console.log(`SQLite version: ${sqlite3_libversion()}`);
```

## Compiling native libraries

<Tabs>
  <Tab title="Zig">
    ```zig add.zig theme={null}
    pub export fn add(a: i32, b: i32) i32 {
      return a + b;
    }
    ```

    ```bash theme={null}
    zig build-lib add.zig -dynamic -OReleaseFast
    ```

    ```typescript theme={null}
    import { dlopen, FFIType, suffix } from "bun:ffi";

    const lib = dlopen(`libadd.${suffix}`, {
      add: {
        args: [FFIType.i32, FFIType.i32],
        returns: FFIType.i32,
      },
    });

    console.log(lib.symbols.add(1, 2)); // 3
    ```
  </Tab>

  <Tab title="Rust">
    ```rust add.rs theme={null}
    #[no_mangle]
    pub extern "C" fn add(a: i32, b: i32) -> i32 {
        a + b
    }
    ```

    ```bash theme={null}
    rustc --crate-type cdylib add.rs
    ```
  </Tab>

  <Tab title="C++">
    ```cpp add.cpp theme={null}
    #include <cstdint>

    extern "C" int32_t add(int32_t a, int32_t b) {
        return a + b;
    }
    ```

    ```bash theme={null}
    zig build-lib add.cpp -dynamic -lc -lc++
    ```
  </Tab>
</Tabs>

## FFI types

Use `FFIType` to declare the native type for each argument and return value.

| `FFIType`    | C type         | Aliases                              |
| ------------ | -------------- | ------------------------------------ |
| `i8`         | `int8_t`       | `int8_t`                             |
| `i16`        | `int16_t`      | `int16_t`                            |
| `i32`        | `int32_t`      | `int32_t`, `int`                     |
| `i64`        | `int64_t`      | `int64_t`                            |
| `i64_fast`   | `int64_t`      |                                      |
| `u8`         | `uint8_t`      | `uint8_t`                            |
| `u16`        | `uint16_t`     | `uint16_t`                           |
| `u32`        | `uint32_t`     | `uint32_t`                           |
| `u64`        | `uint64_t`     | `uint64_t`                           |
| `u64_fast`   | `uint64_t`     |                                      |
| `f32`        | `float`        | `float`                              |
| `f64`        | `double`       | `double`                             |
| `bool`       | `bool`         |                                      |
| `char`       | `char`         |                                      |
| `ptr`        | `void*`        | `pointer`, `void*`, `char*`          |
| `cstring`    | `char*`        |                                      |
| `buffer`     | `char*`        | (must be `TypedArray` or `DataView`) |
| `function`   | `(void*)(*)()` | `fn`, `callback`                     |
| `napi_env`   | `napi_env`     |                                      |
| `napi_value` | `napi_value`   |                                      |

## Strings

JavaScript strings are UTF-16. C strings are null-terminated UTF-8. Bun provides `CString` to bridge the two.

`CString` extends JavaScript's built-in `String` and automatically reads until the null terminator, transcoding from UTF-8 to UTF-16 as needed.

```typescript theme={null}
import { CString } from "bun:ffi";

// Convert a null-terminated pointer to a JavaScript string
const str = new CString(ptr);

// Or specify a byte offset and length
const str2 = new CString(ptr, 0, byteLength);
```

`CString` clones the string content, so it's safe to use after the original pointer has been freed.

```typescript theme={null}
my_library_free(str.ptr);

// Safe — the string content was cloned on construction
console.log(str);
```

When `FFIType.cstring` is used as a return type, Bun automatically coerces the pointer to a JavaScript string. When used as an argument type, it behaves like `ptr`.

## Pointers

Bun represents native pointers as JavaScript `number` values.

<Note>
  64-bit processors support up to 52 bits of addressable space. JavaScript numbers support 53 bits of usable space, so all valid pointer addresses fit in a JavaScript `number` without precision loss.
</Note>

### Getting a pointer to a TypedArray

```typescript theme={null}
import { ptr } from "bun:ffi";

const buffer = new Uint8Array(32);
const myPtr = ptr(buffer);
```

### Converting a pointer to an ArrayBuffer

```typescript theme={null}
import { toArrayBuffer } from "bun:ffi";

// byteLength must be provided, or the pointer is treated as null-terminated
const arrayBuffer = toArrayBuffer(myPtr, 0, 32);
const view = new Uint8Array(arrayBuffer);
```

### Reading from a pointer

For long-lived pointers, use `DataView`:

```typescript theme={null}
import { toArrayBuffer } from "bun:ffi";

const view = new DataView(toArrayBuffer(myPtr, 0, 32));
console.log(view.getUint8(0), view.getUint8(1));
```

For short-lived reads, use the `read` helper — it's faster because it skips `ArrayBuffer` allocation:

```typescript theme={null}
import { read } from "bun:ffi";

console.log(read.u8(myPtr, 0));
console.log(read.u32(myPtr, 4));
console.log(read.f64(myPtr, 8));
```

**`read` functions by type:**

| Type  | Function   |
| ----- | ---------- |
| `ptr` | `read.ptr` |
| `i8`  | `read.i8`  |
| `i16` | `read.i16` |
| `i32` | `read.i32` |
| `i64` | `read.i64` |
| `u8`  | `read.u8`  |
| `u16` | `read.u16` |
| `u32` | `read.u32` |
| `u64` | `read.u64` |
| `f32` | `read.f32` |
| `f64` | `read.f64` |

### Passing a pointer as an argument

Where a native function expects a pointer, pass a `TypedArray` directly. Bun converts it automatically.

```typescript theme={null}
import { dlopen, FFIType } from "bun:ffi";

const { symbols: { encode_png } } = dlopen(myLibraryPath, {
  encode_png: {
    args: ["ptr", "u32", "u32"],
    returns: FFIType.ptr,
  },
});

const pixels = new Uint8ClampedArray(128 * 128 * 4);
pixels.fill(254);

const out = encode_png(pixels, 128, 128);
```

### Memory management

`bun:ffi` does not manage memory. You are responsible for freeing any memory allocated by native code.

To track when a `TypedArray` is garbage collected from JavaScript, use a [`FinalizationRegistry`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry).

To receive a callback when a `TypedArray` is freed from native code, pass a deallocator pointer to `toArrayBuffer`:

```typescript theme={null}
import { toArrayBuffer } from "bun:ffi";

toArrayBuffer(
  bytes,
  byteOffset,
  byteLength,
  deallocatorContext,      // optional context pointer
  jsTypedArrayBytesDeallocator, // pointer to cleanup callback
);
```

The expected signature matches JavaScriptCore's `JSTypedArrayBytesDeallocator`:

```c theme={null}
typedef void (*JSTypedArrayBytesDeallocator)(void *bytes, void *deallocatorContext);
```

## Callbacks

Use `JSCallback` to pass a JavaScript function to native code. The native library can then call back into JavaScript.

<Note>
  Async functions are not yet supported as `JSCallback` targets.
</Note>

```typescript theme={null}
import { dlopen, JSCallback, ptr, CString } from "bun:ffi";

const { symbols: { search }, close } = dlopen("libmylib", {
  search: {
    returns: "usize",
    args: ["cstring", "callback"],
  },
});

const searchIterator = new JSCallback(
  (ptr, length) => /hello/.test(new CString(ptr, length)),
  {
    returns: "bool",
    args: ["ptr", "usize"],
  },
);

const str = Buffer.from("find hello here\0", "utf8");
if (search(ptr(str), searchIterator)) {
  console.log("found a match");
}

// Clean up when done
setTimeout(() => {
  searchIterator.close();
  close();
}, 5000);
```

Always call `close()` on a `JSCallback` when you're done with it to free memory.

### Thread-safe callbacks

`JSCallback` has experimental support for thread-safe callbacks. Enable it with the `threadsafe` option:

```typescript theme={null}
const cb = new JSCallback(
  (ptr, length) => /hello/.test(new CString(ptr, length)),
  {
    returns: "bool",
    args: ["ptr", "usize"],
    threadsafe: true,
  },
);
```

<Note>
  Thread-safe callbacks currently work best when called from a thread that is already running JavaScript, such as a [`Worker`](/runtime/workers). Future versions of Bun will support calling them from any thread, including threads spawned by native libraries.
</Note>

<Tip>
  For a slight performance boost, pass `JSCallback.prototype.ptr` directly instead of the `JSCallback` object itself:

  ```typescript theme={null}
  setOnResolve(onResolve.ptr); // faster
  setOnResolve(onResolve);     // slightly slower
  ```
</Tip>

## Function pointers

Use `CFunction` to call a function pointer you already have a reference to — for example, one obtained from a Node-API module.

```typescript theme={null}
import { CFunction } from "bun:ffi";

const getVersion = new CFunction({
  returns: "cstring",
  args: [],
  ptr: myNativeLibraryGetVersion,
});

console.log(getVersion());
```

To define multiple function pointers at once, use `linkSymbols`:

```typescript theme={null}
import { linkSymbols } from "bun:ffi";

const [majorPtr, minorPtr, patchPtr] = getVersionPtrs();

const lib = linkSymbols({
  getMajor: { returns: "cstring", args: [], ptr: majorPtr },
  getMinor: { returns: "cstring", args: [], ptr: minorPtr },
  getPatch: { returns: "cstring", args: [], ptr: patchPtr },
});

const version = `${lib.symbols.getMajor()}.${lib.symbols.getMinor()}.${lib.symbols.getPatch()}`;
```

## Platform suffixes

The `suffix` export resolves to the correct shared library extension for the current platform.

| Platform | `suffix` |
| -------- | -------- |
| macOS    | `dylib`  |
| Linux    | `so`     |
| Windows  | `dll`    |

```typescript theme={null}
import { suffix } from "bun:ffi";

const lib = dlopen(`libcrypto.${suffix}`, { /* ... */ });
```

<Warning>
  On Windows, the `HANDLE` type does not represent a virtual address. Do not use `ptr` for Windows `HANDLE` values — use `u64` instead.
</Warning>
