Preloaded extensions
Runnable SQL blocks can name the extensions they need
(Runnable SQL blocks), but a docs site that documents one
extension should not make every example name it. The dfkExtensions plugin
takes an ordered preload list instead:
- at dev/build startup it fetches every GitHub-release source into the site's static directory — cached locally, re-downloaded only when the release asset changes;
- it injects the list into every page;
- the kit's runtime loads the extensions, in order, while DuckDB initialises — starting in the background as soon as a page with a runnable block opens, so the first Run click is fast.
A block can then simply call into the extension — nothing to declare:
SELECT version() AS engine, double_it5(21) AS doubled;
Configuring
import {dfkExtensions} from 'duckfn-docs-kit/sql/extensions';
plugins: [
dfkExtensions({
// CI builds the release assets without DuckDB's signing keys — the same
// reason local development runs `duckdb -unsigned`.
allowUnsignedExtensions: true,
preload: [
// The extension this site documents, served same-origin. With `release`
// the build fetches the asset from that repository's latest release;
// drop `release` to serve a file placed under static/ instead.
{
url: 'duckdb-extensions/duckfn.duckdb_extension.wasm',
release: {
repository: 'shijianjs/duckfn',
asset: 'duckfn-wasm_eh.duckdb_extension.wasm',
},
},
],
}),
],
That fetch happens for both npm start and the production build — but only for entries that carry
release. An entry with just {url} is left alone, so a site can serve its own build locally and
switch to the release only where it deploys: duckfn's own site does exactly that, keying the switch
off DOCS_EXTENSION_FROM_RELEASE (see its docs/README.md), which is why its local runs never touch
a release.
The three source kinds
- A name —
'json': the runtime runsLOAD jsonagainst the official repository. - Name + repository —
{name: 'h3', repository: 'community'}: the repository may becommunity,coreor a URL. The runtime records the source withINSTALL … FROMand thenLOADs the name. On WebAssemblyINSTALLwrites nothing to disk — there is no persistent storage to install into — it only records whereLOADshould fetch from, which is also why the record stays attached to this one extension instead of polluting later loads. - A file —
{url: …, release?: …}: the site serves the wasm file itself. Withrelease, the build fetches the asset from that GitHub repository's latest release intostatic/<url>(the same path the runtime loads); without it, the file is placed by hand understatic/<url>and the build only checks that it is there.urlmay also be an absolutehttp(s)URL.
GitHub-release sources
For a {url, release} entry the plugin:
- asks the GitHub API for the repository's latest release and finds the asset by name (a miss lists the available names);
- compares the asset's sha256 — GitHub's own
digestfield — with the local cache and downloads only when they differ; - verifies what it downloads against that digest, stores it under
<siteDir>/.cache/duckfn-docs-kit/and copies it tostatic/<url>; - falls back to the cached copy with a warning when the network is down, so offline development keeps working (CI always starts empty and fails loudly).
GITHUB_TOKEN (or the plugin's token option) lifts the anonymous API rate
limit; neither is needed for public repositories on a normal workstation.
The file-name contract
The text before the first dot of the file name is the entry symbol DuckDB
looks up — duckfn.duckdb_extension.wasm loads through
duckfn_init_c_api. Release assets carry the platform suffix
(duckfn-wasm_eh.duckdb_extension.wasm), so they are renamed on the way in:
the destination in url decides the file name, and both the plugin and the
runtime reject names whose base is not a valid extension identifier.
Versions and signing
- Platform: the preloaded file must match the runtime bundle's WebAssembly
platform. Sites that let
selectBundle()pick serve thewasm_ehasset; this is what the docs site does. - DuckDB version: WebAssembly extensions are only loadable by a DuckDB-Wasm
build with a compatible C API. The site pins
@duckdb/duckdb-wasmto an exact version whose bundled engine matches theduckdb_versionthat CI builds the extension against; when either side moves, re-check these pages' live blocks. - Signing: third-party release assets are not signed with DuckDB's keys, so
a site that preloads them needs
allowUnsignedExtensions: true(the WebAssembly equivalent of running the CLI with-unsigned). Community extensions are signed and load without it.
Where it lands in the page
The plugin injects the resolved list as one JSON <script> tag into every
page:
{"allowUnsignedExtensions":true,"preload":[{"url":"/duckdb-extensions/duckfn.duckdb_extension.wasm"}]}
The tag keeps its id="dfk-sql-runtime" and can be inspected in the page
source when debugging. A malformed list fails DuckDB initialisation with a
readable error in the first block that runs.