WebAssembly version of DuckDB
DuckDB-Wasm
DuckDB is an in-process SQL OLAP Database Management System.
DuckDB-Wasm brings DuckDB to every browser thanks to WebAssembly.
Duckdb-Wasm speaks Arrow fluently, reads Parquet, CSV and JSON files backed by Filesystem APIs or HTTP requests and has been tested with Chrome, Firefox, Safari and Node.js. Learn more about DuckDB-Wasm from our VLDB publication or the recorded talk.
Try it out at shell.duckdb.org or at duckdb.org/visualizer. External third party embedding of DuckDB-Wasm, read the API documentation, check out the web-app examples, and chat with us on Discord.
DuckDB and DuckDB-Wasm
DuckDB-Wasm is currently based on DuckDB v1.5.6.
Relevant differences:
- Default HTTP stack is different between native and Wasm versions of DuckDB
LOAD httpfswill opt-in to using the same HTTP logic, but re-implemented in JavaScript- Notable differences in network interactions:
- Requests are always upgraded to HTTPS
- Requests needs server to allow Cross Origin access on a given resource
- Extension install is lazy, meaning that
INSTALL extension_name FROM 'https://repository.endpoint.org';defer fetching the extension to the firstLOAD extension_name;instruction.INSTALL x FROM community;shorthands are also supported. - DuckDB-Wasm builds are optimized for download speed. Core extensions like autocomplete, JSON, Parquet and ICU are usually bundled DuckDB binaries, while in duckdb-wasm they are autoloaded (including fetching them) at runtime. In particular for ICU autoloading do not work corrently in all cases, explicit
LOAD icu;might be needed to reproduce same behaviour. - DuckDB-Wasm is sandboxed and migth not have the same level of support for out-of-core operations and access to file system
- DuckDB-Wasm default mode is single threaded. Multithreading is at the moment still experimental.
Supported DuckDB features:
- DuckDB databases files are compatible to be read from DuckDB-Wasm.
- Databases files can be made available as simple as:
ATTACH 'https://blobs.duckdb.org/data/test.db'; FROM db.t;demo - Spatial support via
LOAD spatialspatial demo - A growing subset of extensions, either core, community or external, are supported for DuckDB-Wasm
- Multithreading work but it's still experimental and by default not enabled
DuckDB-Wasm and DuckDB Extension
DuckDB is extensible and this allows to delegate functionality to extensions.
Core extensions are available at https://extensions.duckdb.org, and community extensions are available at https://community-extensions.duckdb.org.
--- Excplicitly load extensions
LOAD icu;
--- Or have them autoloaded when using relevant functions or settings
DESCRIBE FROM read_parquet('https://blobs.duckdb.org/stations.parquet'); -- (this autoloads JSON)
--- Or register extensions
INSTALL h3 FROM community;
INSTALL sqlite_scanner FROM 'https://extensions.duckdb.org';
INSTALL quack FROM 'https://community-extensions.duckdb.org';
--- And then load them
LOAD h3;
LOAD sqlite_scanner;
LOAD quack;
FROM duckdb_extensions() WHERE loaded;
Will show that h3, icu, parquet, quack and sqlite_scanner have been loaded.
You can try the Shell demo with loading of extensions but this do require about 3.2 MB of compressed Wasm files to be transfered over the network (on first visit, caching might help).
Extension sizes will vary depending, among other things, on provided functionality or toolchain used.
Build from source
The build requires:
- Node.js and npm. Node.js 24 is required for the current browser test tooling.
- Yarn Classic. This repository uses a Yarn v1 lockfile.
- Emscripten 6.0.9 or a compatible 6.0.x release, CMake, and ccache.
- Rust and rustup for building the shell.
- The DuckDB CLI when generating the complete test dataset.
Install Yarn Classic and the Rust Wasm target before starting the build:
npm install --global yarn@1.22.22
yarn --version
rustup target add wasm32-unknown-unknown
git clone https://github.com/duckdb/duckdb-wasm.git
cd duckdb-wasm
git submodule init
git submodule update
make apply_patches
make build_loadable TARGET=threads
make serve
During the Wasm build, the build script checks for js-beautify and installs
it globally with npm if it is missing. The initial
js-beautify: command not found message is therefore expected when the tool
is not already installed.
If the build stops with make: yarn: No such file or directory, install Yarn
Classic using the command above and run make serve again. There is no need
to run make apply_patches again.
The JavaScript bundle requires both the base and threaded Wasm bindings.
make build_loadable TARGET=threads generates the threaded bindings before
make serve. The serve target also ensures that both variants are built.
Node.js 26 can build the shell and application, but it is not currently
supported by the Karma/Puppeteer test toolchain and can fail with
require is not defined in ES module scope or a missing yargs module. Use
Node.js 24 when running browser tests.
Some browser tests require generated data files. Generate the small university dataset used by the threads smoke test with:
./scripts/generate_uni.sh
Use make build/data to generate all test datasets. The data generator first
uses DUCKDB_SHELL when set, then looks for a native DuckDB CLI built in the
DuckDB submodule, and finally falls back to duckdb on PATH. CMake policy
warnings, Rust deprecation warnings, TypeDoc warnings, and Webpack asset-size
warnings may appear during a successful build; they are non-fatal if the
command exits successfully.
The development server uses port 8080 when available. If that port is already
in use, http-server selects another port; open the URL printed under
Available on in the terminal.
Develop HTTPFS locally
HTTPFS is loaded as a separate Wasm side module. Build it with the same DuckDB revision, Emscripten toolchain, and platform settings as the DuckDB-Wasm main module.
Keep the HTTPFS checkout outside the generated build/ directory. From the
DuckDB-Wasm repository, run:
make build_httpfs_local DUCKDB_HTTPFS_DIRECTORY=$PWD/duckdb-httpfs
DUCKDB_HTTPFS_DIRECTORY must point to the HTTPFS source root that contains
its top-level CMakeLists.txt. The target:
- validates and converts the path to an absolute path;
- reconfigures existing base and threaded builds to use that checkout;
- builds both DuckDB-Wasm main modules and HTTPFS side modules; and
- rebuilds the JavaScript package.
During configuration, confirm that CMake reports the expected source:
Load extension 'httpfs' from local path ".../duckdb-httpfs"
The generated HTTPFS modules are placed at:
build/extension_repository/<duckdb-revision>/wasm_eh/httpfs.duckdb_extension.wasm
build/extension_repository/<duckdb-revision>/wasm_threads/httpfs.duckdb_extension.wasm
Load the local extension in a browser
Local extension builds are unsigned. Configure both the local repository and the unsigned-extension policy before opening the database.
For the React application:
<DuckDBProvider
config={{
allowUnsignedExtensions: true,
extensionRepository: `${window.location.origin}/extension_repository`,
}}
>
For the direct JavaScript API:
await db.open({
allowUnsignedExtensions: true,
extensionRepository: new URL('/extension_repository', location.href).href,
});
These are startup options. SQL cannot enable unsigned extensions after the database has been opened.
Build the modules, prepare the application, copy the local extension repository, and start the development server with:
make serve_httpfs_local DUCKDB_HTTPFS_DIRECTORY=$PWD/duckdb-httpfs
The target opens /original.html. Use a fresh browser tab after rebuilding so
an older worker does not keep stale modules, then run:
INSTALL AND LOAD httpfs;
Troubleshooting
- A request to
https://extensions.duckdb.orgmeans that the localextensionRepositorysetting was not applied. - An unsigned-extension error means that
allowUnsignedExtensionswas not enabled before opening the database. - An OpenSSL or
curl/curl.herror means that the selected HTTPFS revision does not contain the required Emscripten build guards. - The warning that HTTPFS has a
vcpkg.jsonbut the build does not use vcpkg is expected for this Emscripten build.
Rebuild the web shell
After changing the Rust shell, rebuild both the shell package and the application that embeds its Wasm file:
yarn workspace @duckdb/duckdb-wasm-shell build:debug
yarn workspace @duckdb/duckdb-wasm-app build:release
cp -R build/extension_repository packages/duckdb-wasm-app/build/release/
npx http-server packages/duckdb-wasm-app/build/release -c-1 -a 127.0.0.1 -p 8081
Open /original.html at the URL printed by http-server and hard-refresh the
page. The -c-1 option disables the development server cache so an existing
browser does not reuse an older shell Wasm file or worker. Running make serve
performs the same rebuild as part of the complete build.
Local build troubleshooting log
| Failure | Cause | Fix |
|---|---|---|
duckdb-threads.js: No such file or directory |
Only the base Wasm binding was built. | Run make build_loadable TARGET=threads. The serve target now builds both base and threaded bindings. |
duckdb-threads.pthread.js: No such file or directory |
Emscripten 6 uses the main threads worker for pthreads and no longer emits the legacy file. | The bundler now creates the legacy filename as a compatibility alias of the main threads worker. |
A repeated Wasm build fails while patching markAsFinished |
The generated Emscripten glue had already been patched. | The glue patch is now idempotent and safely handles repeated builds. |
chmod: duckdb: No such file or directory during make build/data |
The data script detected the DuckDB CLI but invoked a nonexistent submodule build. | The script now prefers DUCKDB_SHELL, then a native CLI built from the DuckDB submodule, and finally duckdb from PATH. |
Arrow file does not contain correct footer, or result queries such as SELECT 42 hang |
The Rust web shell used Arrow FileReader, while DuckDB now returns an Arrow IPC stream. |
The shell now uses StreamReader and returns decoding errors instead of panicking. Rebuild the shell and application using the commands above. |
| A fixed query starts hanging again after rebuilding | An existing browser tab may still be running a cached shell Wasm file or worker. | make serve now disables HTTP caching. Restart the server, close old DuckDB-Wasm tabs, and open /original.html again. |
failed to create temp dir for cargo install wasm-bindgen |
wasm-pack cannot write to its temporary or Cargo installation directory. |
Run the build in an environment with writable temporary and Cargo directories. |
listen EPERM or an occupied port while starting http-server |
The environment prevents binding the requested port, or another process already uses it. | Allow local port binding or select another port, for example -p 8091. |
| Puppeteer/Karma fails under Node.js 26 | The current test dependencies are incompatible with Node.js 26. | Run browser tests with Node.js 24. This does not prevent building the shell and app with Node.js 26. |
Repository Structure
| Subproject | Description | Language |
|---|---|---|
| duckdb_wasm | Wasm Library | C++ |
| @duckdb/duckdb-wasm | Typescript API | Typescript |
| @duckdb/duckdb-wasm-shell | SQL Shell | Rust |
| @duckdb/duckdb-wasm-app | GitHub Page | Typescript |
| @duckdb/react-duckdb | React Hooks | Typescript |