UnFFI

Schema & types

How to describe native symbols and what TypeScript infers from your schema.

The schema you pass to dlopen is the source of truth. TypeScript infers every argument and return type at compile time — no codegen, no decorators, no type annotations to maintain.

Hover the identifiers to see what TypeScript knows:

import { ,  } from 'unffi'

const  = await ('./math.dylib', {
  :   { : [., .], : . },
  : { : [.],    : . },
})

const sum = ..(2, 3)
const sum: number
const hello = ..('world')
const hello: string

sum is number, hello is string. TypeScript derives this from t.i32 and t.cstring in the schema alone.

What types are

Each t.* value is a CType<T> token — it carries the C type name and the JS type T as a phantom parameter. dlopen maps the whole schema through InferLibrary<S>:

import {  } from 'unffi'

const i32 = .
const i32: CType<number>
const cstr = .
const cstr: CType<string>
const cb = .([., .], .)
const cb: CCallback<readonly [CType<number>, CType<number>], CType<number>>

Primitives

TypeC typeTypeScript
t.voidvoidundefined
t.boolboolboolean
t.i8int8_tnumber
t.u8uint8_tnumber
t.i16int16_tnumber
t.u16uint16_tnumber
t.i32int32_tnumber
t.u32uint32_tnumber
t.i64int64_tbigint
t.u64uint64_tbigint
t.f32floatnumber
t.f64doublenumber
t.cstringconst char*string (UTF-8, NUL-terminated)
t.pointervoid*bigint | null
t.buffervoid*ArrayBufferView (zero-copy)

t.cstring — decoded to a JS string on output. On input, unffi encodes the string to a temporary NUL-terminated buffer for the call's duration.

t.pointer — raw address as bigint, or null for a null pointer.

t.buffer — pass any TypedArray or DataView; unffi hands C the backing pointer directly, no copy.

Symbol definition

Each key in the schema is a SymbolDef:

import type { SymbolDef } from 'unffi'

type Sym = SymbolDef
type Sym = SymbolDef

async: true makes the bound function return a Promise. Supported on all backends (Bun thread pool, Deno nonblocking, koffi worker pool).

Function pointers — t.fn

t.fn(argTypes, returnType) describes a C function pointer. Pass a plain JS function at the call site — unffi wraps it automatically.

import { ,  } from 'unffi'

const  = await ('./sort.dylib', {
  : {
    : [., ., .([., .], .)],
    : .,
  },
})

..(new (4), 4, (a, ) => {
a: bigint
return < ? -1 : > ? 1 : 0 })

The comparator's a and b are typed as bigint because they correspond to t.i64 — JS matches C exactly.

Async symbols

import { ,  } from 'unffi'

const  = await ('./codec.dylib', {
  : { : [., .], : ., : true },
  :     { : [., .],    : . },
})

const promise = ..(new (0), 0)
const promise: Promise<number>
const value = ..(1, 2)
const value: number

compress is async (thread-pool call). fast is synchronous.

Generated schemas

Shipped OS modules and generated bindings use the same SymbolsSchema shape. You can inspect the exported schema constants, pass them to manual dlopen, or rely on the open*() helpers.

import { coreFoundationSchema } from 'unffi/macos/CoreFoundation'
import { libcSchema } from 'unffi/linux/libc'

const cfReturn = coreFoundationSchema.CFStringGetTypeID.returns

const libcArgs = libcSchema.strlen.args

See System libraries for shipped modules and Binding generation for project-local headers.

Runtime-specific types

TypeTypeScriptNotes
t.bun.i64_fastnumber | bigintSkip BigInt alloc when value ≤ 2^53. Loses precision above it.
t.bun.u64_fastnumber | bigintSame, unsigned.
t.bun.napi_envopaqueNode-API environment pointer.
t.bun.napi_valueopaqueNode-API value pointer.
TypeTypeScriptNotes
t.deno.usizebigintsize_t — pointer-sized unsigned.
t.deno.isizebigintssize_t — pointer-sized signed.
t.deno.struct(fields)Uint8ArrayStruct by value. Pass CType[] tokens.

Helpers:

t.deno.ptrOf(view)                         // zero-copy pointer from TypedArray
t.deno.readCString(ptr, offset?)           // decode NUL-terminated string
t.deno.readArrayBuffer(ptr, len, offset?)  // read raw bytes
TypeTypeScriptNotes
t.koffi.struct(def) / (name, def)objectC struct.
t.koffi.array(ref, len)arrayFixed-length array.
t.koffi.pointer(ref)pointerTyped pointer.
t.koffi.out(ref)[T]Output param — writes to index 0. Pointer wrapping is internal.
t.koffi.inout(ref)[T]Read/write param.
t.koffi.opaque(name?)opaqueForeign handle.
t.koffi.str16stringUTF-16 wide string (Windows APIs).
t.koffi.uintptr / intptrbigintPointer-sized integers.

Utilities: t.koffi.encode, t.koffi.decode, t.koffi.free, t.koffi.alias, t.koffi.raw.

Using a runtime-specific type on the wrong runtime throws at dlopen time with a clear message pointing to the right namespace.

On this page