Building Custom Extensions for Turso with ExtensionApi: A Complete Guide
Turso exposes a Rust-native ExtensionApi that lets you build dynamic SQLite extensions as shared libraries, registering scalar functions, aggregates, virtual tables, and VFS hooks without writing unsafe C glue code.
Turso is a lightweight, SQLite-compatible database engine written in Rust. Building custom extensions for Turso with ExtensionApi involves creating dynamic libraries that export a standardized entry point, allowing the runtime to inject custom functions and storage backends through a safe Rust abstraction layer.
Understanding the ExtensionApi Architecture
The ExtensionApi Struct
In extensions/core/src/lib.rs (lines 34-43), the ExtensionApi struct serves as the bridge between Turso's runtime and your extension. It holds a raw context pointer and a set of registration callbacks for scalar functions, aggregates, virtual-table modules, and optional VFS interfaces.
Entry Point and Registration
Every extension must export an ExtensionEntryPoint symbol—specifically register_extension—that Turso calls during loading. This C-compatible function receives a pointer to the ExtensionApi and returns a ResultCode. Upon invocation, your extension uses the callbacks provided in the ExtensionApi to register its capabilities with the database engine.
Extension Lifecycle and Runtime Mechanics
The lifecycle follows four distinct phases:
- Dynamic Loading: Turso's
load_extensionroutine opens the shared object and locates the exportedregister_extensionsymbol. - Registration: The entry point invokes registration callbacks from the
ExtensionApito publish scalar functions, aggregates, or virtual-table modules. - Execution: Once registered, functions behave like native SQLite primitives—callable via
SELECT my_func(arg), usable as aggregates, or exposing virtual tables viaCREATE VIRTUAL TABLE ... USING my_vtab. - Cleanup: When the database connection closes, the runtime drops the loaded library, and the framework cleans up any Rust state attached through the
Statetype inScalarFunc.
Creating Your First Turso Extension
Project Setup
Create a new Cargo library and configure it to produce a C-compatible dynamic library:
# Cargo.toml
[package]
name = "my_turso_ext"
version = "0.1.0"
edition = "2021"
[dependencies]
turso_ext = { path = "../turso/extensions/core" }
[lib]
crate-type = ["cdylib"]
Implementing a Scalar Function
The ScalarFunc trait in extensions/core/src/functions.rs (lines 64-80) defines the interface for Rust-native functions. Use the #[scalar] attribute from macros/src/lib.rs to automatically generate C wrappers:
// src/lib.rs
use turso_ext::{register_extension, scalar, Value, ResultCode};
#[scalar]
pub fn str_len(arg: Value) -> Value {
let txt = arg.as_text();
Value::from_int64(txt.len() as i64)
}
register_extension! {
scalars: { str_len }
}
Loading and Testing
Compile the extension, then load it into a Turso session:
SELECT load_extension('/path/to/libmy_turso_ext.so');
SELECT str_len('hello world'); -- Returns 11
Implementing Advanced Features
Virtual Table Modules
Turso supports custom virtual-table implementations through the register_vtab_module callback in ExtensionApi. These modules allow you to expose external data sources as queryable SQL tables, as implemented in core/vtab.rs.
Custom VFS Hooks
When the vfs feature is enabled, the ExtensionApi exposes vfs_interface for registering custom virtual file systems. Define a struct implementing VfsExtension and use the #[VfsDerive] macro from extensions/core/src/vfs_modules.rs:
use turso_ext::{register_extension, VfsDerive, VfsExtension, VfsFile, ResultCode};
#[derive(Default)]
pub struct MyVfs;
#[VfsDerive]
impl VfsExtension for MyVfs {
const NAME: &'static str = "myvfs";
fn open_file(&self, path: &str, flags: i32, _direct: bool)
-> Result<VfsFile, ResultCode> {
// Implementation logic here
unimplemented!()
}
}
register_extension! {
vfs: { MyVfs }
}
Summary
- ExtensionApi in
extensions/core/src/lib.rsprovides the core registration callbacks for building custom extensions for Turso. - Extensions export a
register_extensionentry point that receives the API struct and registers functions through type-safe callbacks. - The
#[scalar]and#[aggregate]attributes inmacros/src/lib.rseliminate manual C wrapper code, whileregister_extension!generates the required entry point. - Compile extensions as
cdylibcrate types and load them at runtime usingSELECT load_extension('path'). - Optional VFS support enables custom storage backends via the
VfsExtensiontrait inextensions/core/src/vfs_modules.rs.
Frequently Asked Questions
What is the ExtensionApi in Turso?
The ExtensionApi is a Rust struct defined in extensions/core/src/lib.rs that exposes registration callbacks for scalar functions, aggregates, virtual tables, and VFS hooks. It serves as the bridge between Turso's SQLite-compatible runtime and custom extension libraries written in Rust.
How do I load a custom extension into Turso?
Compile your Rust code as a cdylib (shared library), then use the SQL command SELECT load_extension('/path/to/library.so'); in your Turso session. Turso locates the register_extension symbol, calls it with an ExtensionApi pointer, and registers your extension's functions automatically.
Can I write Turso extensions without using unsafe Rust?
Yes. The turso_ext crate provides safe abstractions through the ScalarFunc and AggFunc traits in extensions/core/src/functions.rs, and the #[scalar] attribute macro in macros/src/lib.rs generates the necessary unsafe extern "C" wrappers automatically. You only write idiomatic Rust code.
What types of extensions can I build with the ExtensionApi?
You can build scalar functions, aggregate functions, virtual-table modules, and custom VFS implementations. The ExtensionApi exposes specific registration callbacks for each type: register_scalar_function, register_aggregate_function, register_vtab_module, and vfs_interface (when the vfs feature is enabled).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →