Skip to main content
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.
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, 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.

Calling SQLite

A complete example: print the SQLite version number.

Compiling native libraries

add.zig

FFI types

Use FFIType to declare the native type for each argument and return 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.
CString clones the string content, so it’s safe to use after the original pointer has been freed.
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.
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.

Getting a pointer to a TypedArray

Converting a pointer to an ArrayBuffer

Reading from a pointer

For long-lived pointers, use DataView:
For short-lived reads, use the read helper — it’s faster because it skips ArrayBuffer allocation:
read functions by type:

Passing a pointer as an argument

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

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. To receive a callback when a TypedArray is freed from native code, pass a deallocator pointer to toArrayBuffer:
The expected signature matches JavaScriptCore’s JSTypedArrayBytesDeallocator:

Callbacks

Use JSCallback to pass a JavaScript function to native code. The native library can then call back into JavaScript.
Async functions are not yet supported as JSCallback targets.
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:
Thread-safe callbacks currently work best when called from a thread that is already running JavaScript, such as a Worker. Future versions of Bun will support calling them from any thread, including threads spawned by native libraries.
For a slight performance boost, pass JSCallback.prototype.ptr directly instead of the JSCallback object itself:

Function pointers

Use CFunction to call a function pointer you already have a reference to — for example, one obtained from a Node-API module.
To define multiple function pointers at once, use linkSymbols:

Platform suffixes

The suffix export resolves to the correct shared library extension for the current platform.
On Windows, the HANDLE type does not represent a virtual address. Do not use ptr for Windows HANDLE values — use u64 instead.