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 module.
Use the built-in bun:ffi module to efficiently call native libraries from JavaScript. It works with any language that supports the C ABI, including Zig, Rust, C/C++, C#, Nim, and Kotlin.

dlopen usage (bun:ffi)

To print the version number of sqlite3:

Performance

According to our benchmark, bun:ffi is roughly 2-6x faster than Node.js FFI through Node-API. Bun generates and just-in-time compiles C bindings that efficiently convert values between JavaScript types and native types. To compile C, Bun embeds TinyCC, a small and fast C compiler.

Usage

Zig

add.zig
To compile:
terminal
Pass a path to the shared library and a map of symbols to import into dlopen:

Rust

To compile:

C++

To compile:

FFI types

The following FFIType values are supported. buffer arguments must be a TypedArray or DataView.

Strings

JavaScript strings and C-like strings are different, and that complicates using strings with native libraries.
JavaScript strings:
  • UTF16 (2 bytes per letter) or potentially latin1, depending on the JavaScript engine & what characters are used
  • length stored separately
  • Immutable
C strings:
  • UTF8 (1 byte per letter), usually
  • The length is not stored. Instead, the string is null-terminated: its length is the index of the first \0
  • Mutable
To solve this, bun:ffi exports CString which extends JavaScript’s built-in String to support null-terminated strings and add a few extras:
To convert from a null-terminated string pointer to a JavaScript string:
To convert from a pointer with a known length to a JavaScript string:
The new CString() constructor clones the C string, so it is safe to continue using myString after ptr has been freed.
When used in returns, FFIType.cstring coerces the pointer to a JavaScript string. When used in args, FFIType.cstring is identical to ptr.

Function pointers

Async functions are not supported
To call a function pointer from JavaScript, use CFunction, for example with a pointer you got from a Node-API (napi) module you’ve already loaded.
To define multiple function pointers at once, use linkSymbols:

Callbacks

Use JSCallback to create JavaScript callback functions that you can pass to C/FFI functions, so native code can call back into your JavaScript or TypeScript. This is useful for asynchronous code.
When you’re done with a JSCallback, call close() to free the memory.

Experimental thread-safe callbacks

JSCallback has experimental support for thread-safe callbacks. You need this if you pass a callback function into a different thread from the one that created it. Enable it with the optional threadsafe parameter. Thread-safe callbacks work best when run from another thread that is running JavaScript code, that is, a Worker. A future version of Bun will enable them to be called from any thread, such as new threads spawned by your native library that Bun is not aware of.
⚡️ Performance tip: For a slight performance boost, pass JSCallback.prototype.ptr directly instead of the JSCallback object:

Pointers

Bun represents pointers as a number in JavaScript.
64-bit processors support up to 52 bits of addressable space. JavaScript numbers support 53 bits of usable space, which leaves about 11 bits of extra space.Why not BigInt? BigInt is slower. JavaScript engines allocate BigInts separately, so they can’t fit into a regular JavaScript value. If you pass a BigInt to a function, it is converted to a number.Windows Note: The Windows API type HANDLE does not represent a virtual address, and using ptr for it does not work as expected. Use u64 to safely represent HANDLE values.
To convert from a TypedArray to a pointer:
To convert from a pointer to an ArrayBuffer:
To read data from a pointer, you have two options. For long-lived pointers, use a DataView:
For short-lived pointers, use read:
The read function behaves similarly to DataView, but it’s usually faster because it doesn’t need to create a DataView or ArrayBuffer.

Memory management

bun:ffi does not manage memory for you. You must free the memory when you’re done with it.

From JavaScript

To track when a TypedArray is no longer in use from JavaScript, use a FinalizationRegistry.

From C, Rust, Zig, etc

To track when a TypedArray is no longer in use from C or FFI, pass a callback and an optional context pointer to toArrayBuffer or toBuffer. The callback is called later, once the garbage collector frees the underlying ArrayBuffer JavaScript object. The expected signature is the same as in JavaScriptCore’s C API:

Memory safety

Don’t use raw pointers outside of FFI. A future version of Bun may add a CLI flag to disable bun:ffi.

Pointer alignment

If an API expects a pointer sized to something other than char or u8, make sure the TypedArray is also that size. A u64* is not exactly the same as [8]u8* due to alignment.

Passing a pointer

Where FFI functions expect a pointer, pass a TypedArray of equivalent size:
The auto-generated wrapper converts the TypedArray to a pointer.
If you don’t want the automatic conversion, or you want a pointer to a specific byte offset within the TypedArray, get the pointer to the TypedArray directly:

Reading pointers