# DuckDB Extensions in C#
Build native DuckDB extensions using C# and [.NET AOT compilation](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/).
## Getting Started
Clone with submodules to include the required extension packaging script:
```bash
git clone --recurse-submodules https://github.com/Giorgi/DuckDB.ExtensionKit.git
```
Or if already cloned:
```bash
git submodule update --init --recursive
```
## Projects
| Project | Description |
|---------|-------------|
| **DuckDB.ExtensionKit** | Core runtime library with DuckDB C API bindings, type-safe function registration, and vector data readers/writers |
| **DuckDB.ExtensionKit.Generators** | Source generator that auto-generates the native entry point boilerplate |
| **DuckDB.JWT** | Example extension implementing JWT functions (validates tokens, extracts claims) |
## Building an Extension
### 1. Create a project
Reference the toolkit packages and configure your extension name:
```xml
myextension
v1.2.0
true
```
### 2. Define your extension
Create a partial class with the `[DuckDBExtension]` attribute and implement `RegisterFunctions`:
```csharp
[DuckDBExtension]
public static partial class MyExtension
{
private static void RegisterFunctions(DuckDBConnection connection)
{
// Register a scalar function
connection.RegisterScalarFunction("string_length",
value => value?.Length ?? 0);
// Register a table function with expression-based projection
connection.RegisterTableFunction("get_items",
(string category) => GetItems(category),
(Item item) => new { name = item.Name, price = item.Price });
// Table function with named parameters
// SQL: SELECT * FROM get_items('toys', max_rows := 10)
connection.RegisterTableFunction("get_items_filtered",
(string category, [Named("max_rows")] int? limit) =>
GetItems(category).Take(limit ?? int.MaxValue),
(Item item) => new { name = item.Name, price = item.Price });
}
}
```
Named parameters use the `[Named]` attribute on lambda parameters. By default the SQL parameter name matches the C# name; use `[Named("custom_name")]` to override it. Named parameters are optional - omitted ones receive `null`.
The source generator automatically creates the native entry point (`myextension_init_c_api`).
See the **DuckDB.JWT** project in this repo for a complete example with scalar and table functions.
### 3. Build and publish
```bash
dotnet publish -c Release -r win-x64 # or linux-x64, osx-arm64, etc.
```
This also runs a post-publish Python script (`append_extension_metadata.py`) that appends DuckDB extension metadata to the native binary. This metadata is required for DuckDB to recognize and load the file as a valid extension.
The output is a `.duckdb_extension` file ready to load into DuckDB.
## Loading and Testing
Since community extensions are unsigned, start DuckDB with the `-unsigned` flag (see [Unsigned Extensions](https://duckdb.org/docs/stable/extensions/extension_distribution#unsigned-extensions)):
```bash
duckdb -unsigned
```
Then install and load your extension:
```sql
-- Install and load the extension
INSTALL 'path/to/jwt.duckdb_extension';
LOAD jwt;
-- Test scalar functions
SELECT is_jwt('eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImExZmIyY2NjN2FiMjBiMDYyNzJmNGUxMjIwZDEwZmZlIn0.eyJpc3MiOiJodHRwczovL2lkcC5sb2NhbCIsImF1ZCI6Im15X2NsaWVudF9hcHAiLCJuYW1lIjoiR2lvcmdpIERhbGFraXNodmlsaSIsInN1YiI6IjViZTg2MzU5MDczYzQzNGJhZDJkYTM5MzIyMjJkYWJlIiwiYWRtaW4iOnRydWUsImV4cCI6MTc2NjU5MTI2NywiaWF0IjoxNzY2NTkwOTY3fQ.N7h2xc4rgS4oPo8IO9wyG1lnr2wqTUC80YudWTXp7rXmU2JdsUiweKmuYVVbygdJAR4PJmbQtak4_VuZg2fZFILVpzDyLvGITfUW_18XuDQ_SIm3VlfAuHOVHfruuvvSAfjUkTW2Jlrv3ihFYgusV58vjhcVFHssOGMEbtMNo10Jf62dczVVGNZXh_OOLS0nTLffhY94sZddqQIE56W8xhLK5YMO4gO8voMzhUwDwucnVvyNfui38MPDNdTSKjn3Ab0hG8jzOVhbYSCHf0eQsbxPzGtXUCJobScWDb78IphFWec6W4ugIYp5CMh3C_noQi94NYjQg2P-AJ5FLCKzKA');
-- Returns: true
SELECT extract_claim_from_jwt('eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImExZmIyY2NjN2FiMjBiMDYyNzJmNGUxMjIwZDEwZmZlIn0.eyJpc3MiOiJodHRwczovL2lkcC5sb2NhbCIsImF1ZCI6Im15X2NsaWVudF9hcHAiLCJuYW1lIjoiR2lvcmdpIERhbGFraXNodmlsaSIsInN1YiI6IjViZTg2MzU5MDczYzQzNGJhZDJkYTM5MzIyMjJkYWJlIiwiYWRtaW4iOnRydWUsImV4cCI6MTc2NjU5MTI2NywiaWF0IjoxNzY2NTkwOTY3fQ.N7h2xc4rgS4oPo8IO9wyG1lnr2wqTUC80YudWTXp7rXmU2JdsUiweKmuYVVbygdJAR4PJmbQtak4_VuZg2fZFILVpzDyLvGITfUW_18XuDQ_SIm3VlfAuHOVHfruuvvSAfjUkTW2Jlrv3ihFYgusV58vjhcVFHssOGMEbtMNo10Jf62dczVVGNZXh_OOLS0nTLffhY94sZddqQIE56W8xhLK5YMO4gO8voMzhUwDwucnVvyNfui38MPDNdTSKjn3Ab0hG8jzOVhbYSCHf0eQsbxPzGtXUCJobScWDb78IphFWec6W4ugIYp5CMh3C_noQi94NYjQg2P-AJ5FLCKzKA', 'name');
-- Returns: Giorgi Dalakishvili
-- Test table function
SELECT * FROM extract_claims_from_jwt('eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImExZmIyY2NjN2FiMjBiMDYyNzJmNGUxMjIwZDEwZmZlIn0.eyJpc3MiOiJodHRwczovL2lkcC5sb2NhbCIsImF1ZCI6Im15X2NsaWVudF9hcHAiLCJuYW1lIjoiR2lvcmdpIERhbGFraXNodmlsaSIsInN1YiI6IjViZTg2MzU5MDczYzQzNGJhZDJkYTM5MzIyMjJkYWJlIiwiYWRtaW4iOnRydWUsImV4cCI6MTc2NjU5MTI2NywiaWF0IjoxNzY2NTkwOTY3fQ.N7h2xc4rgS4oPo8IO9wyG1lnr2wqTUC80YudWTXp7rXmU2JdsUiweKmuYVVbygdJAR4PJmbQtak4_VuZg2fZFILVpzDyLvGITfUW_18XuDQ_SIm3VlfAuHOVHfruuvvSAfjUkTW2Jlrv3ihFYgusV58vjhcVFHssOGMEbtMNo10Jf62dczVVGNZXh_OOLS0nTLffhY94sZddqQIE56W8xhLK5YMO4gO8voMzhUwDwucnVvyNfui38MPDNdTSKjn3Ab0hG8jzOVhbYSCHf0eQsbxPzGtXUCJobScWDb78IphFWec6W4ugIYp5CMh3C_noQi94NYjQg2P-AJ5FLCKzKA');
```
| claim_name | claim_value |
|------------|----------------------------------|
| iss | https://idp.local |
| aud | my_client_app |
| name | Giorgi Dalakishvili |
| sub | 5be86359073c434bad2da3932222dabe |
| admin | true |
| exp | 1766591267 |
| iat | 1766590967 |
## Unstable API
To use DuckDB's [unstable Extension C API functions](https://github.com/duckdb/extension-template-c#using-unstable-extension-c-api-functionality), set `UseUnstableApi` in your `.csproj`:
```xml
true
```
This changes the ABI type to `C_STRUCT_UNSTABLE` and suppresses the experimental warnings on unstable API functions. Note that using the unstable API pins your extension to the exact DuckDB version.
## How It Works
1. **Source Generator** - At compile time, the generator finds your `[DuckDBExtension]` class and generates a native entry point function (`{extension}_init_c_api`) marked with `[UnmanagedCallersOnly]`
2. **AOT Compilation** - .NET compiles your code to a native binary that exports the entry point, with no runtime dependency
3. **Extension Loading** - When DuckDB loads your extension, it calls the entry point which:
- Initializes the C API and receives function pointers to DuckDB's internal APIs
- Obtains a database connection and calls your `RegisterFunctions` method to register scalar/table functions
## Features
- **Type-safe APIs** - Register scalar and table functions with generic type parameters
- **Automatic marshalling** - Vector readers/writers handle DuckDB's columnar format
- **AOT compilation** - Produces standalone native binaries with no .NET runtime dependency
- **Cross-platform** - Build for Windows, Linux, and macOS (x64 and ARM64)