Compiling Isn't Running: Functionally Testing DuckDB-WASM Extensions

rustyconover1 pts0 comments

Compiling Isn't Running: Functionally Testing DuckDB-WASM Extensions — Rusty Conover

DuckDB June 14, 2026<br>Compiling Isn't Running: Functionally Testing DuckDB-WASM Extensions<br>A DuckDB extension that compiles for WebAssembly has only proven that it compiles. Whether it loads, and whether it actually runs, are separate questions. I built a Node harness to ask them across 124 community extensions. Here's what it found and the fixes that came out of it.<br>When the WebAssembly build of a DuckDB extension passes CI, it’s tempting to read that as “the extension works.” It doesn’t mean that. It means the code compiled: emcc accepted the source, produced a .duckdb_extension.wasm, and the file got uploaded to the catalog. Whether that file loads into a running engine, and whether it does anything useful once loaded, are questions the build never asks.

For the WASM extensions in Haybarn, Query.Farm’s DuckDB distribution, nobody was asking them either. So I went looking, and the first extension I checked had been broken for weeks while its badge stayed green.

So I built a harness to run all of them. Here’s the short version of this whole post — every WASM-enabled community extension, run against the published engine and graded on its own test suite:

58

43

pass · 58<br>fail · 43<br>crash · 1<br>skip · 9<br>no tests · 5<br>not deployed · 8

124 WASM-enabled community extensions, run on 13 June 2026. Green passed their own sqllogictest suites; red failed them (darkest red crashed the engine); gray never produced a verdict — no tests, unsupported test directives, or no artifact on the catalog. Of the 102 that actually ran tests, 58 passed.

That gap matters for two kinds of people. If you publish a DuckDB extension, the WASM build is the one target you probably can’t test by hand, and a green badge can hide a binary that throws the instant a browser user calls a function. If you build on DuckDB-WASM, an extension you rely on may be quietly missing in the browser while working everywhere else. Either way, the failure surfaces at your users instead of your CI, which is the worst place to find it.

Why Query Farm cares about WASM

Running a full DuckDB engine in a browser tab — no server, no install — is one of the most useful things the project can do, and we've put real work into making it solid. Alongside the Haybarn WASM distribution, we've worked on engine fixes like an interrupt API for long-running queries, a LOAD deadlock on cross-origin-isolated (threaded) builds, a rewritten HTTP layer, correct TIMESTAMPTZ handling over Arrow IPC, and the jump from emscripten 3.1 to 5.0. This extension testing is part of the same bet: if we're going to ship DuckDB to the browser, the extensions have to actually run there.

The canary

The jsonata extension lets you run JSONata expressions over JSON inside SQL. Its WASM build compiled fine, shipped to the catalog, and installed without complaint:

INSTALL jsonata FROM community; -- ok<br>LOAD jsonata; -- ok<br>SELECT jsonata('Account', '{"Account": 5}');<br>-- TypeError: n is not a function<br>INSTALL and LOAD both succeed. The first real function call throws an opaque TypeError from inside the worker. A test that stops at “does it load” passes this and ships it. But nobody installs an extension just to load it; they call its functions, which is where this one fell over.

Native builds catch exactly this. make test links the extension into DuckDB’s unittest binary and runs the extension’s own test/sql/*.test sqllogictest files against it. The WASM build runs none of that, because there’s no WASM unittest binary; the test runner is native C++. So WASM has no functional test layer at all.

Why “it compiled” tells you so little in WASM

On native, a loadable DuckDB extension is a shared library the engine dlopens. In WASM it’s an emscripten side module , linked with -sSIDE_MODULE=2, that the engine’s dynamic linker loads at runtime and resolves against the main module. Anything it can’t resolve doesn’t fail the load; it becomes a stub that only blows up when something calls it.

That gap, between compiling and resolving, is where a clean compile can still hide a broken extension. It breaks four ways, and the census hit all of them: a dependency that never got linked into the .wasm, a file read that assumes a real filesystem, an HTTP call that assumes real sockets, or a hard dependency on another extension that has no WASM build. None of this shows up at compile time; you only see it when you run the extension.

The plan: run their own tests, against the real engine, in Node

I didn’t want to write new tests. Every extension already ships a test/sql/*.test suite, the same sqllogictest files the native build runs. Those assertions are a much better oracle than any smoke query I’d invent, so the harness runs them against the published WASM engine, the way a user would actually get the extension.

The harness, haybarn-extension-wasm-tester (now open source), does this per extension:

Census. Parse the community...

wasm extension duckdb test engine build

Related Articles