Quick start
This page builds a minimal extension called my_ext. The repository's own example extension is
duckfn — see the example extension for one that covers every
feature.
To start from a working skeleton with CI already in place instead, see Create a project.
The four steps below, end to end:
1. Create the crate
[package]
name = "my_ext"
version = "0.1.0"
edition = "2024"
rust-version = "1.86"
[lib]
crate-type = ["cdylib"]
[dependencies]
duckfn = "0.0.18"
quack-rs = "0.16.0"
libduckdb-sys = { version = ">=1.4.4, <2", features = ["loadable-extension"] }
2. Write the extension
use duckfn::{duck_error, duck_scalar_function, duckfn_entrypoint, DuckOptionResult};
#[duck_scalar_function]
pub fn double_it(v: Option<i64>) -> DuckOptionResult<i64> {
if v == Some(13) {
return Err(duck_error("unlucky input"));
}
Ok(v.map(|x| x * 2))
}
duckfn_entrypoint!("my_ext");
Three things are worth noticing:
- The attribute both registers the function and generates its FFI wrapper — there is no manual registration step.
Option<i64>in andDuckOptionResult<i64>out are the way to expressNULL. See Errors and panics for the full set of return shapes.duckfn_entrypoint!("my_ext")must match the extension name; see below.
3. Build it
The official DuckDB flow builds a cdylib, appends extension metadata, and lays the result out where
DuckDB expects it:
make configure # one-time: creates the Python venv used by the test runner
make debug # -> build/debug/extension/my_ext/my_ext.duckdb_extension
make release does the same with optimisations. Both come from DuckDB's extension-ci-tools
makefiles, which the repository includes.
This is the recommended build path. The repository wraps it in its Justfile — just build runs
make configure && make debug, and just sql "SELECT ..." / just repl load the resulting
build/debug/my_ext.duckdb_extension, so the daily commands and the official toolchain are the same
flow.
4. Load and call it
duckdb -unsigned -c "
LOAD './build/debug/extension/my_ext/my_ext.duckdb_extension';
SELECT double_it(21);
"
42
| SQL | Result |
|---|---|
SELECT double_it(21); | 42 |
SELECT double_it(NULL); | NULL |
SELECT double_it(13); | error: unlucky input |
-unsigned is required because the extension is built against DuckDB's unstable C API.
The entry point name
duckfn_entrypoint!("my_ext") generates the symbol my_ext_init_c_api, which is the symbol
DuckDB looks up when the extension is loaded. The name must be non-empty and may only contain
lowercase ASCII letters, digits and underscores — anything else fails at compile time, and a wrong
symbol name makes the extension unloadable.
Next steps
- Attributes — every attribute and macro argument in one place.
- Scalar functions — return shapes, overloads and function sets.
- Type mapping — the Rust types accepted as arguments and results.
- The example extension — runnable SQL for every feature.