⌘K ctrl+k
Search Shortcut cmd + k | ctrl + k
Statically Linking Extensions

This page is for client and binding authors, and for anyone who embeds libduckdb in their own application. It describes how statically linking extensions changed in DuckDB v2.0 and what builders on top of DuckDB need to do.

This change is independent of the new C API. It applies even if you continue to build against the existing (v1) C API: the difference is in how extension archives are linked and registered, not in which API your bindings call.

What Changed in DuckDB v2.0

Before v2.0, the set of statically linked extensions was a compile-time decision. The sources were compiled with knowledge of which extensions were included, so a build with CORE_EXTENSIONS='json;parquet;icu' could not reuse the object files of a build with CORE_EXTENSIONS='json;parquet;icu;tpch', even though one is a subset of the other.

DuckDB v2.0 modularizes this into a link-time decision (duckdb#25814). Each extension is compiled once into its own static archive, for example:

libduckdb_static.a
libcore_functions_extension.a
libparquet_extension.a
libjson_extension.a
libicu_extension.a

The list of extensions to link in is now chosen when linking, not when compiling. As a follow-up, the extension loader and the built-in httplib also became opt-in at link time (duckdb#26188).

Two aspects that earlier builds relied on are gone:

  • The bundle-library Makefile target that merged everything into a single libduckdb_bundle.a. Extensions, httplib, and loadable extensions are now separate archives.
  • The generated loader that libduckdb_static.a used to register extensions automatically. libduckdb_static.a no longer registers extensions on its own.

Registering Statically Linked Extensions

Because extension selection moved to link time, a client that statically links extensions must now register them with the engine before any database is opened. Each extension archive carries a generated describe function, duckdb_extension_⟨name⟩_describe, that fills a duckdb_extension_descriptor declared in the new public header duckdb_static_extension.h. The descriptor carries a layout version, so the engine refuses an archive whose descriptor layout it does not support, with a clear message instead of misbehaving.

The engine exposes a C API call, duckdb_register_static_extension(describe), which validates the descriptor and registers the extension for every database opened afterwards. A generated loader (static_extension_loader.c) defines duckdb_register_static_extensions(), which passes every linked extension's describe function to duckdb_register_static_extension in turn. Call it once at startup, before any database is opened, from any language that can call C:

// Register the statically linked extensions before any database can be opened
if (duckdb_register_static_extensions() != 0) {
    throw std::runtime_error("Failed to register the statically linked DuckDB extensions");
}

If registration is skipped, the linked extensions (such as core_functions, icu, json, and parquet) are not loaded. Queries that depend on them then fail at runtime. For example, SELECT to_minutes(5) reports that the function exists in the core_functions extension, even though that extension was linked in.

A program can also skip the generated loader and call duckdb_register_static_extension directly on the describe functions it declares itself. An archive whose describe function is never registered contributes nothing, and a registered extension whose archive is missing fails the link.

Calling the Loader Before main

A C++ program that wants registration to happen automatically before main compiles extension/loader/static_extension_autoregister.cpp alongside the loader. Compile it into the program itself, not into an archive, or the linker drops it.

If you do package the loader and the auto-register object into an archive, keep the registration symbol from being discarded by forcing it onto the link line:

-Wl,-u,duckdb_register_static_extensions

Build Options

When building DuckDB or libduckdb, select the extensions to compile with BUILD_EXTENSIONS (or CORE_EXTENSIONS):

BUILD_EXTENSIONS='core_functions;json;parquet;icu' GEN=ninja make

Which of the built extensions are statically linked is a separate decision. By default the DuckDB targets link the extensions that the build configs mark for static linking, plus the ones named in BUILD_EXTENSIONS when you build with LINK_CORE_EXTENSIONS=1. To link an explicit set instead, set STATICALLY_LINK_EXTENSIONS (the older name for this flag is LINK_EXTENSIONS):

STATICALLY_LINK_EXTENSIONS='core_functions;parquet;json;icu;tpch' make

Changing the linked set only re-generates the loader, compiles it, and re-links; it does not recompile the extensions themselves. An extension that is built but not statically linked is still available: it is installed from the build's own extension repository and loaded the first time a query needs it.

Linking the Archives by Hand

Outside CMake, link one archive per extension, compile the generated loader, and put the engine archive last:

LINK_EXTENSIONS='parquet;json' make static_extension_loader
cc -I src/include main.c build/release/static_extension_loader.c libparquet_extension.a libjson_extension.a libduckdb_static.a -lstdc++

The order matters: each extension archive must come before libduckdb_static.a on the link line.

See Build Configuration and Building Extensions for the full set of flags.

Obtaining Prebuilt Archives

Rather than building libduckdb yourself, you can download the static and shared archives for DuckDB v2.0 and later using the duckdb-install-scripts tooling (duckdb-install-scripts#5). Setting DUCKDB_INSTALL=static installs the archives and headers under ~/.duckdb/lib/⟨version⟩/⟨build_type⟩_⟨platform⟩:

DUCKDB_INSTALL=static DUCKDB_VERSION=alpha

This installs, for example:

duckdb.h                      duckdb_v2.h
duckdb_extension.h            duckdb_extension_v2.h
duckdb_static_extension.h
libduckdb_static.a            libcore_functions_extension.a
libparquet_extension.a        libjson_extension.a
libicu_extension.a            libautocomplete_extension.a
libtpch_extension.a           libtpcds_extension.a

A pinned build can be installed with DUCKDB_STAGED, for example DUCKDB_STAGED=31adc8b766/v2.0.0-alpha42839 or simply DUCKDB_STAGED=31adc8b766.

For DuckDB v2.0 and later, most artifacts are distributed as .tar.gz files instead of .zip files (duckdb#24773).

Reference Implementations for Clients

The following pull requests adapt the official clients to the v2.0 linking model. They provide a path forward for anyone maintaining a binding or an application that links libduckdb.

Client Status Reference pull requests
Python Migrated duckdb-python#634
Rust Migrated duckdb-rs#834, duckdb-rs#839, duckdb-rs#883, duckdb-rs#835
Node.js Survey in progress duckdb-node-neo#476, duckdb-node-neo#508
Go Planned, not yet started duckdb-go-bindings#112
Wasm Not yet migrated pending (pinned to the DuckDB v1.5 v1.5-variegata line at the time of writing)

The Python pull request is the clearest end-to-end example: it restores static linking of the default BUILD_EXTENSIONS set by calling duckdb_register_static_extensions() and shows how to override the linked set.

See Also

© 2026 DuckDB Foundation, Amsterdam NL
DuckDB Home Code of Conduct Trademark Use Blog