# How to Use bun:ffi to Call Native C Functions from JavaScript in Bun

> Master bun ffi to effortlessly call native C functions from JavaScript in Bun for near-native performance. Learn to dynamically load libraries and optimize function calls.

- Repository: [Bun/bun](https://github.com/oven-sh/bun)
- Tags: how-to-guide
- Published: 2026-02-28

---

**Bun's FFI (Foreign Function Interface) allows JavaScript to dynamically load shared libraries and invoke C functions with near-native performance by generating optimized call stubs at runtime.**

The `bun:ffi` module provides a zero-dependency mechanism for calling native code from the Bun JavaScript runtime. As implemented in the `oven-sh/bun` repository, this API eliminates the need for Node-API or C++ wrapper code by dynamically generating JavaScript-to-native bridges at load time.

## Core Architecture of bun:ffi

The implementation spans three distinct layers. The high-level JavaScript façade resides in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts), which manages type conversions and function generation. This interfaces with Zig primitives defined in `src/bun.js/api/ffi.zig` and C++ bindings in [`src/bun.js/bindings/ffi.cpp`](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/ffi.cpp) that connect directly to the JavaScriptCore engine.

### FFIType and Type Mapping

At the foundation of the type system is the **FFIType** definition found in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts) (lines 1-61). This mapping translates human-readable type names—such as `int`, `double`, `cstring`, `function`, and `ptr`—into internal numeric IDs consumed by the builder. When you declare a function signature, these types determine how JavaScript values are marshalled into C-compatible binary representations.

### Dynamic Stub Generation with FFIBuilder

Unlike generic FFI systems that rely on slow reflection, Bun uses the **FFIBuilder** class (lines 40-66 in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts)) to generate optimized call stubs. The builder concatenates JavaScript snippets from the **ffiWrappers** object (lines 59-124), which contain per-type conversion logic. It then uses `new Function()` to create an inline stub that converts arguments, calls the raw native pointer (`functionToCall`), and wraps return values.

## Loading Native Libraries with dlopen

The primary entry point for library interaction is `Bun.FFI.dlopen()`, implemented in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts) (lines 46-77). This function loads a shared library (`.so`, `.dylib`, or `.dll`) and replaces specified symbols with generated wrappers.

### Basic Function Signatures

For each symbol, provide an object containing `args` (an array of FFI types) and `returns` (a single FFI type). If no signature is provided, the raw native pointer is exposed without type conversion.

```javascript
const lib = Bun.FFI.dlopen("./libcalc.so", {
  add: { args: ["int", "int"], returns: "int" },
  mul: { args: ["int", "int"], returns: "int" },
  greet: { args: ["cstring"], returns: "cstring" }
});

console.log(lib.add(3, 4)); // 7

```

### Working with Pointers

The **`ptr`** helper (lines 68-70 in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts)) creates raw pointers from `ArrayBuffer` instances or numeric addresses. Use this when passing binary data to functions expecting `void*` or typed pointers.

```javascript
const buffer = new ArrayBuffer(1024);
const ptr = Bun.FFI.ptr(buffer);
lib.process_buffer(ptr, buffer.byteLength);

```

## Handling C Strings and Memory Management

The **CString** class (lines 178-234 in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts)) wraps null-terminated C strings returned from native code. It stores the native pointer and lazily creates an `ArrayBuffer` view when you call `toString()` or `toArrayBuffer()`.

**Critical:** `CString` does not copy memory or manage allocation lifetime. The underlying C memory must remain valid while JavaScript holds the reference, or you must manually free it via an exposed C function.

```javascript
const msg = lib.greet("Bun");
console.log(msg.toString()); // Accesses the C string
lib.free(msg.ptr); // Manual cleanup if required by the C API

```

## Creating Thread-Safe JavaScript Callbacks

When native code must call back into JavaScript, use the **JSCallback** class (lines 82-115 in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts)). This wraps a JavaScript function in a native function pointer that C code can store and invoke.

### Thread Safety Configuration

Pass `{ threadsafe: true }` as the first argument to enable safe invocation from any thread. Bun automatically marshals these calls back to the JavaScript event loop.

```javascript
const callback = new Bun.FFI.JSCallback(
  { threadsafe: true },
  (a, b) => a * b
);

lib.register_callback(callback);
// Later:
callback.close(); // Releases native resources

```

## Runtime C Compilation with cc

For scenarios requiring ad-hoc native code, `Bun.FFI.cc()` compiles C source at runtime via the Zig driver in `src/bun.js/api/ffi.zig`. This bypasses the need for a separate build step when prototyping or generating small native shims.

## Advanced Linking and CFunction

Use `Bun.FFI.linkSymbols()` (lines 27-42 in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts)) to add symbols to an already-loaded library without reopening the file. For standalone function pointers not tied to a shared object, the **CFunction** helper creates single-function wrappers and registers a `FinalizationRegistry` to close handles automatically when the JavaScript object is garbage collected.

## Summary

- **bun:ffi** lives in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts) and provides `dlopen()`, `cc()`, and callback APIs backed by Zig and C++ bindings.
- The **FFIBuilder** generates optimized JavaScript stubs at runtime using `new Function()` and **ffiWrappers** for minimal call overhead.
- **CString** wraps returned C strings without copying; you must manage the underlying native memory lifetime manually.
- **JSCallback** with `{ threadsafe: true }` allows C code to safely invoke JavaScript from any thread.
- Use **`ptr`** to pass `ArrayBuffer` data as native pointers, and **`linkSymbols`** to augment loaded libraries without reopening them.

## Frequently Asked Questions

### What types does bun:ffi support?

The **FFIType** enum in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts) defines support for `int`, `uint32`, `int64`, `double`, `float`, `bool`, `cstring`, `ptr`, and `function`. Complex structs are not directly supported; you must pass them as `ptr` to raw memory and manually manage offsets.

### How do I pass structs or complex types to C functions?

Pass structs as `ptr` values pointing to `ArrayBuffer` memory. Calculate field offsets manually or use a C helper function to return packed data. The **ptr** helper in [`src/js/bun/ffi.ts`](https://github.com/oven-sh/bun/blob/main/src/js/bun/ffi.ts) accepts `ArrayBuffer` instances or numeric addresses for this purpose.

### Is bun:ffi thread-safe for callbacks?

Yes, when you create a **JSCallback** with the `{ threadsafe: true }` option, the resulting native function pointer can be called from any thread. Bun automatically queues the invocation onto the JavaScript event loop. Without this flag, callbacks are restricted to the main thread.

### How do I free memory allocated by the C library?

The **CString** class and pointer helpers do not manage memory. You must expose a C function (such as `free()`) through the same library handle and call it from JavaScript, passing the pointer value obtained from `cstring.ptr` or the original pointer returned by the function.