- Foreword: Target Platforms
- The Canonical Builds
- WASM Heap Memory
- C Files
- JS Files
- Building the JS/WASM Files
- Exporting New APIs to JS
This page introduces the various builds of the WASM/JS code and how to build customized versions.
sqlite3.js and sqlite3.wasm are the "standard builds." They are
built for wide deployment on a number of browsers.
The GNUmakefile in the ext/wasm directory of the source
tree creates those files. The rest of this document describes what
goes in to building them, so that clients may create custom builds and
adapt the builds for toolchains other than Emscripten.
These instructions assume:
- A Linux-like system which supports...
- A recent version of the Emscripten SDK. This project can be built with other WASM toolchains but currently cannot do much when built without the Emscripten-provided file I/O emulation layer. Improving support for other build environments is an ongoing project goal.
Foreword: Target Platforms
This project's primary deliverable is "lowest common denominator" JavaScript which can be used in a wide variety of environments, with web browsers taking priority over server-side JS engines. We do not directly support the wide array of sub-platforms, e.g. the diverse node.js-based tools, because:
- None of this project's developers use any tools related to node.js in any capacity except "under the hood" via Emscripten.
- The JavaScript ecosystem is more fluid than most and this project lacks the developer bandwidth to obtain and maintain expertise with the ever-changing JS landscape.
Where the "vanilla" JS does not work with with a given third-party environment we are happy to help users integrate it, but we require active cooperation from such environments' users in order to do so. Such cooperation invariably requires back-and-forth interaction with the developers as the quirks of any given environment are discovered and accommodated.
By and large, we leave the development of environment-specific builds, e.g. npm, to those who have an active interest and need for them. We offer a community-maintained npm build but cannot guarantee that it works with every combination of tools from that ecosystem. We also provide separate builds of the core JS files intended to be compatible with certain tools commonly seen in node.js-based build environments, e.g. "bundlers." See the API index for details.
It is not this project's goal to become The One True SQLite WASM Distribution and we both encourage and welcome alternative distributions and implementations, especially ones which cover use cases which we lack the bandwith and topical expertise to do justice to.
Quickstart: the Canonical Builds
The canonical build files (i.e. those which live in sqlite3's own source tree) support several different build targets which might be useful to folks other than its own developers. They require a checked-out copy of the sqlite3 source tree (as opposed to an amalgamation distribution), GNU Make, the Emscripten SDK, and the wabt tools (version 1.36 or higher as of this writing).
At its simplest, do the following from the top of a checked-out source tree:
$ ./configure --enable-all
$ make sqlite3.c
$ cd ext/wasm
$ make TARGET
Where the most common TARGET values are explained below.
Tip: a parallel build, e.g. with
make -j4, will work just fine, but getting it to do so reliably requires running themake sqlite3.cstep in advance. As of version 3.51.0, the build time is cut drastically with-j#.
Useful targets include:
- The default target, if no target is provided, builds with
-O0optimization level because it compiles much more quickly than any other level. o0builds with the-O0optimization level and may add optimzation flags which the default build does not.o1,o2,o3,os, andozbuild with the-OXlevel indicated by their second character.releaseis equivalent tooz.snapshotcreates a "prelease snapshot" zip file.distcreates a zip file suitable for use with the canonical downloads page. This differs from a "snapshot" build only in the names of the resulting zip file and embedded directory.
Much experimentation has shown that -O2 provides the fastest
binaries, but -Oz provides the smallest (noting that
wasm-strip is required to make them small). The speed
difference between -O2 and -Oz builds is typically only about 10%.
Each build produces a number of deliverables, including:
jswasm/sqlite3.{js,mjs,wasm}are the core-most versions of the library and its APIs. Several other JS and WASM files are also built to this directory.sqlite3.mjsis the same assqlite3.jsexcept for very minor differences required for loading it as an ES6 module.jswasm/*-bundler-friendly.*are variant builds intended to be used with JS "bundler" tools. They are are completely untested (see above about our not using any tools which require this format).fiddle/fiddle-module.{js,wasm}are the core of the fiddle application. Thefiddle/directory can be copied as-is and served via a web server to host the sqlite3 fiddle application.
The remaining deliverables in the top-level directory are various demos and tests, neither necessary nor (in all likelihood) interesting for most folks.
64-bit Integers
JavaScript's core Number type does not support 64-bit integers,
which causes some friction between WASM and JS regarding 64-bit
integers (whereas both support 64-bit floats).
Many sqlite3 APIs take or return int64 values, but all such APIs are
disabled unless sqlite3.js is built with support for JavaScript's
BigInt type. This is a build-time option.
The canonical builds enable BigInt support by default. Custom builds may disable it but must be aware that all sqlite3 C APIs which take int64 arguments or return them will not be available in the JS bindings.
64-bit WASM
As of 2025-09-22 the build process
supports 64-bit WASM. In short, that changes all JS-side pointer
values from Number to BigInt, which changes how the JS side has to
deal with them.
A 64-bit build can be created by doing the following from a checked-out copy of the source tree or one of the full source downloads (not the amalgamation builds):
$ ./configure ... $ make sqlite3.c $ cd ext/wasm $ make -j4 64bit
The resulting files are in the jswasm subdirectory, named
sqlite3-64bit.js (vanilla JS), sqlite3-64bit.mjs (ES6 module),
sqlite3-64bit.wasm (the wasm file used by both of those JS files).
The only client-visible change in that mode is the aforementioned pointer type change. What that means, in practical terms, is that code like the following no longer works:
let ptr = wasm.alloc(16); let ptr2 = ptr + 8; // Cannot mix BigInt/Number math
To account for that, new APIs have been introduced: see ./api-wasm.md#wasm-ptr for details.
WASM Heap Memory
One of WASM's design features is how WASM modules are given RAM to work with. They must have one or the other of the following:
- The starting memory limit is compiled in to the WASM file.
- The memory is allocated by the client from JavaScript and provided to the WASM module loader. Unfortunately, because of how the WASM module loading process works, it is not possible, with our build, to provide this from arbitrary client-level code when loading the sqlite3 module.
Depending on how the build is configured, it may or may not be possible for the WASM module to grow its amount of memory at runtime. If it cannot, and it runs out of memory, allocation attempts will fail. Whether or not that is outright fatal to the WASM module is determined by a build option. If it is not fatal, application code must check for out-of-memory conditions on its own, as it would in C. If it is configured to be fatal, the module will fail loudly (in the browser's dev console, not necessarily someplace client-visible) and stop working if any allocation fails.
The heap memory size is one of a staggering number of build options which clients may want, or need, to customize for a given deployment. The default heap memory assigned to the module was chosen based on testing and development of the sqlite3 module. As experience is garnered via 3rd-party client applications, future releases of the module may change the initial amount of memory the WASM module receives.
Preprocessing JS Files
In order to be able to support both conventional JavaScript and ES6
modules (a.k.a. ESM) in the same source files, we must
"preprocess" certain source files to filter specific code sections in
or out depending on whether they're being built for conventional JS or
ESM. Because C preprocessors make a mess of things when used for
preprocessing JS code, a custom preprocessor was created specifically
for use in this project, colloquially known as the C-Minus
Preprocessor, or c-pp. It is maintained as a standalone side
project but the sqlite3 source tree contains its own
copy.
Certain JS files cannot be used as-is by a JS engine, and instead must
be run through c-pp. Though this complicates the creation of custom
builds somewhat, it seems to be the "lesser evil" in terms of
approaches for maintaining the JS code in such a way as to be usable
by both flavors of JS.
Tip: files which require preprocessing have a file extension of
.c-pp.X, whereXis the typical extension for that file type, e.g.htmlorjs.
The preprocessor's directives do not directly interfere with the source code but do lead to constructs which, if run in JS without preprocessing them, will lead to (at best) syntax errors or (at worst) potentially silent errors. Here's an example snippet of a preprocessed block:
const W =
//#if target=es6-module
new Worker(new URL(options.proxyUri, import.meta.url));
//#else
new Worker(options.proxyUri);
//#endif
The JS keywords import and export trigger syntax errors in non-ESM
code, so may not be filtered out at runtime via JS's introspection
features. The capabilities they provide are essential for client-side
use of ESM modules, so cannot simply be hand-waved away as a
"nice-to-have." i.e. we have to support ESM, one way or another, and
preprocessing gives us a relatively painless way of doing so.
The preprocessor supports a configurable prefix for its keywords and
the JS code in this tree uses the prefix //# so that such constructs
do not directly interefere with syntax highlighting and code
indentation in JS-aware text editors. The preprocessor's keywords may
not be indented: they must start on column 0.
After preprocessing, the above block is reduced to one of the following:
const W =
new Worker(new URL(options.proxyUri, import.meta.url));
or:
const W =
new Worker(options.proxyUri);
Use of the preprocessor is kept to a minimum. As of this writing, it
is used only to account for differences between vanilla JS, ESM, and
"bundler-friendly". All code using the preprocessor can be found by
grepping the JS files for '^//#'.
The complete technical details of such preprocessing are maintained in
GNUMakefile.
SQLite Encryption Extension (SEE)
The build process supports building with the SQLite Encryption Extension (SEE) variant of the SQLite amalgamation source file. See see.md for details.
Adding Client-custom Init Code
The WASM build does not support dynamically loading extensions because
there is no compatible dlopen() equivalent for web-based WASM, but
clients may add extensions at compile time using the approach
described in this section.
If the build sees a file named sqlite_wasm_extra_init.c in the build
directory (ext/wasm) then the following applies:
- The build defines
SQLITE_EXTRA_INIT=sqlite3_wasm_extra_init - That file is added to the compilation process.
- That file must define a function with this signature:
int sqlite3_wasm_extra_init(const char *)
It will be called during the library-initialization phase and passedNULL. If the function returns non-0, initialization of the library will fail.
That function may install any extensions the client cares to, provided
they can be compiled by the WASM build process. The build does not
detect and compile additional C files, so the extensions should either
be copied, in full, into sqlite3_wasm_extra_init.c or that file
should use:
#include "/path/to/your/extension.c"
(Note the .c extension) for each included extension.
The file name sqlite3_wasm_extra_init.c may be overridden by the
client by passing sqlite3_wasm_extra_init.c=/path/to/its/replacement.c to
make.
The source tree contains an example/template file for bootstrapping
purposes, named example_extra_init.c. Simply rename it, edit it to
suit, and rebuild.
The C Files
Building sqlite3.wasm requires two local C files and one local H
file, plus whatever system-level headers it requires:
sqlite3.candsqlite3.hcomprise the canonical amalgamation build.sqlite3-wasm.cextends the amalgamation a small bit to add the handful of WASM-specific APIs which require C-side support. The overwhelming majority of the JS/WASM API is implemented entirely in JavaScript, with only a small amount of infrastructure-level support code implemented in C. The WASM-specific C routines are intended solely for use by the JS layer and are not considered to be public APIs unless specifically documented as such. That is, they are "unsupported" and may change at any time.
When compiling, only sqlite3-wasm.c gets compiled. It #include's
sqlite3.c because it requires access to some of the latter file's
internal-use-only state1. It also #define's a
number of WASM-relevant configuration flags for sqlite3.c. Because
it uses state internal to sqlite3.c, sqlite3-wasm.c is only
intended to be built with a sqlite3.c generated from the same
version of the project's source tree.
The JS Files (sqlite3-api.js)
The oft-mentioned sqlite3.js is generated out of numerous other
files by a build process. This section provides an overview of those
files as a guide for those who would like to create custom builds.
The following files found in the ext/wasm directory base
directory are used to create the JS amalgamation, listed in the order in
which they need to be assembled:
api/sqlite3-api-prologue.js
Contains the initial bootstrap setup of the sqlite3 API objects. This is exposed as a function, rather than objects, so that the next step can pass in a config object which abstracts away parts of the WASM environment, to facilitate plugging it in to arbitrary WASM toolchains.common/whwasmutil.js
A semi-third-party collection of JS/WASM utility code intended to replace much of the Emscripten glue and provide us with more control over the available JS/WASM-bridging features. The sqlite3 APIs internally use these APIs instead of their Emscripten counterparts. This API is configurable, in principle, for use with arbitrary WASM toolchains. It is "semi-third-party" in that it was created in order to support this tree but is standalone and maintained together with...jaccwabyt/jaccwabyt.js
Another semi-third-party API, jaccwabyt creates bindings between JS and C structs, such that changes to the struct state from either JS or C are visible to the other end of the connection. This is also an independent spinoff project, conceived for the sqlite3 project but maintained separately. It is used, for example, to create the OPFS sqlite3_vfs in JS.api/sqlite3-api-glue.js
Invokes functionality exposed by the previous two files to flesh out low-level parts ofsqlite3-api-prologue.js. Most of these pieces are related to populating thesqlite3.wasmobject and using it to create more JS-friendly wrappers of the WASM-exported functions. This file also deletes most global-scope symbols the above files create, effectively moving them into the scope being used for initializing the API.<build>/sqlite3-api-build-version.js
Gets created by the build process and populates thesqlite3.versionobject.api/sqlite3-api-oo1.js
Provides a high-level object-oriented wrapper to the lower-level C API, colloquially known as OO API #1. Its API is similar to other high-level sqlite3 JS wrappers and should feel relatively familiar to anyone familiar with such APIs. That said, it is not a "required component" and can be elided from builds which do not want it.api/sqlite3-api-worker1.js
A Worker-thread-based API which uses OO API #1 to provide an interface to a database which can be driven from the main Window thread via the Worker message-passing interface. Like OO API #1, this is an optional component, offering one of any number of potential implementations for such an API.api/sqlite3-worker1.js
Is not part of the amalgamated sources and is intended to be loaded by a client Worker thread. It loads the sqlite3 module and runs the Worker #1 API which is implemented insqlite3-api-worker1.js.api/sqlite3-worker1-promiser.js
Is likewise not part of the amalgamated sources and provides a Promise-based interface into the Worker #1 API. This is a far user-friendlier way to interface with databases which run in a separate Worker thread.
api/sqlite3-v-helper.js
This internal-use-only file installs utilities for use by othersqlite3-*.jsfiles which create customsqlite3_vfsor virtual table implementations.api/sqlite3-vfs-opfs.c-pp.js
An sqlite3 VFS implementation which supports the Origin-Private FileSystem (OPFS) as a storage layer to provide persistent storage for database files in a browser. If activated, it requires...api/sqlite3-opfs-async-proxy.js
is the asynchronous backend part of the OPFS proxy. It speaks directly to the (async) OPFS API and channels those results back to its synchronous counterpart. This file, because it must be started in its own Worker, is not part of the amalgamation.
api/sqlite3-api-cleanup.js
The previous files do not immediately extend the library as they are initially evaluated. Instead they add callback functions to be called during the library's bootstrapping phase. Some also temporarily create global objects in order to communicate their state to the files which follow them. This file cleans up any dangling globals and runs the API bootstrapping process, which is what finally executes the initialization code installed by the previous files. This code ensures that the previous files leave no more than a single global symbol installed. When adapting the API for non-Emscripten toolchains, this "should" be the only file where changes are needed.
Files with the extension .c-pp.js are intended to be processed
with c-pp, noting that such preprocessing may be
applied after all of the relevant files are concatenated. That
extension is used primarily to keep the code maintainers cognisant of
the fact that those files contain constructs which will not run as-is
in JavaScript.
The result of concatenating those files (except for the ones noted as
being external) is typically named sqlite3-api.js. That file
contains the entire JS API but it requires sqlite3.wasm to be loaded
before it is bootstrapped, a process which exists primarily to
configure the JS parts to work with the current WASM environment. The
bootstrapping requires pieces specific to each WASM build environment
and happens in sqlite3-api-cleanup.js. With a custom build it would
be possible to delay the bootstrapping further by replacing parts of
sqlite3-api-cleanup.js, perhaps with the goal of enabling the end
client to extend the library further before bootstrapping or to
provide a more customized configuration to the bootstrap process.
sqlite3-api.js may, depending on the build environment, be
compounded further into other JS files.
For example, in Emscripten-driven build, that file gets sandwiched
between additional files which wrap it up in such a way that the
Emscripten module-initialization process will activate it. That bundle
then gets included into the resulting Emscripten-generated
sqlite3.js, which includes not only the sqlite3 API but also all of
the infrastructure needed for loading sqlite3.wasm and various
utility code provided by Emscripten.
The following files are part of the build process but are injected
into the build-generated sqlite3.js along with sqlite3-api.js.
extern-pre-js.js
Emscripten-specific header for Emscripten's--extern-pre-jsflag. As of this writing, that file is only used for experimentation purposes and holds no code relevant to the production deliverables.pre-js.c-pp.js
Emscripten-specific header for Emscripten's--pre-jsflag. This file provides a place to override certain Emscripten behavior before it starts up.post-js-header.js
Emscripten-specific header for the--post-jsinput. It opens up a lexical scope by starting a post-run handler for Emscripten.post-js-footer.js
Emscripten-specific footer for the--post-jsinput. This closes off the lexical scope opened bypost-js-header.js.extern-post-js.c-pp.js
Emscripten-specific header for Emscripten's--extern-post-jsflag. This file overwrites the Emscripten-installedsqlite3InitModule()function with one which, after the module is loaded, also initializes the asynchronous parts of the sqlite3 module. For example, the OPFS VFS support.
Building the JS/WASM Files
Aside from the tools mentioned below, the wabt tools are also
strongly recommended, primarily for wasm-strip.
Emscripten
The Emscripten-centric build process is covered on the Emscripten page.
wasi-sdk
sqlite3 can be built with wasi-sdk, for "server-side" use, but such builds cannot work with the provided JavaScript code because the WASI builds require JS imports which we cannot provide (but Emscripten does).
Here is a mini-HOWTO for setting up the wasi-sdk on an Ubuntu-style Linux system:
$ git clone --recursive https://github.com/WebAssembly/wasi-sdk.git
$ cd wasi-sdk
$ sudo apt install ninja-build cmake clang
$ NINJA_FLAGS=-v make package
# ^^^^ go order a pizza, wait for it to arrive, eat it, and
# check back. Maybe it'll be done by then. Maybe. As of this writing,
# it has to compile more than 3000 C++ files.
$ sudo ln -s $PWD/build/wasi-sdk-* /opt/wasi-sdk
# ^^^^^^^ /opt/wasi-sdk is a magic path name for these tools
Or, much faster, grab a prebuilt SDK binary from that git page,
unpack it somewhere, then symlink /opt/wasi-sdk to point to it.
With that installed, the canonical source tree can be told to use it with:
$ ./configure --with-wasi-sdk=/opt/wasi-sdk
$ make
Plain clang
Like the wasi-sdk build, it is possible to compile sqlite3.wasm
using vanilla clang, but the resulting binary is not usable as-is with
this project's JavaScript code due to missing JS/WASM
imports/exports.
Building for SQLite Encryption Extension (SEE)
Since 2023-03-08, the canonical WASM build supports building against the SQLite Encryption Extension (SEE) by either:
- Adding
sqlite3-see.cto the top-most directory of the tree before building the WASM components or - Passing
sqlite3.c=/path/to/sqlite3-see.cwhen runningmakeorgmakefrom theext/wasmdirectory. (The path must not contain any spaces.)
The resulting jswasm/sqlite3.wasm will include the SEE-related APIs
and jswasm/sqlite3.[m]js will add those APIs to the sqlite3.capi
namespace, including automatic string-type argument conversion.
Caveats:
- SEE is a commercial product and a WASM build has the same distribution restrictions as binary files created using the SEE source code. This project's stance is that using SEE WASM builds within a licensee's private intranet is compatible with the SEE licensing terms, but publication on a wider network is not.
- Only encryption methods which require no third-party libraries work here. Specifically: RC4, AES128-OFB, and AES258-OFB.
- There is no known way to completely hide encryption keys from JavaScript clients. Even if such keys are compiled in to a WASM binary, those are easily disassembled to a plain-text form.
Exporting New APIs to JS
Exporting new APIs to WASM requires more than simply including them in
the sqlite3.c amalgamation and enabling any required feature
flags. In short, it requires:
- Add them to the list of exported functions for the current build
environment. For the Emscripten-based builds, that means adding
them to
ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-*(see below), prefixing each function with an underscore character (because Emscripten requires it). - If desirable (it normally is), adding a description of the binding
to the JS code, which ensures that it will get installed into the
sqlite3.capinamespace and perform any required argument and result type conversion.
These steps are described below, with the caveat that ⚠ these are internal details subject to change at any time ⚠! The specifics of how C functions get exported into the WASM and JS APIs are not part of the public interface of the library and are at least partially dependent on the build platform used for creating the WASM file (i.e. part of it is Emscripten-dependent).
That said...
Extending the Exported Functions List
ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-core and
ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-extras are plain text files
listing all C functions which must be exported into the resulting
sqlite3.wasm. Every function name is preceded by an underscore
because Emscripten requires it. For maintenance's sake, the list
should be kept in alphabetical order. It is acceptable for exports to
be listed here but not exposed to JS.
If/when other build platforms are available, they may have their own function exports list.
Adding sqlite3.capi Bindings
Once a function is exported from WASM, we can access it via the
sqlite3.wasm.exports namespace, but such bindings are in their "raw"
form, meaning that no type conversion is performed on their arguments
or result values, e.g. for string arguments. In order to expose the
API to the public interface and add any necessary type conversion, we
must add an entry to the internal list of bindings.
⚠ ACHTUNG: again, what follows are internal implementation details which are subject to change at any time. Do not rely on these instructions from client-level code.
The mapping between WASM-exported functions and their JS-exposed
counterparts are stored in ext/wasm/api/sqlite3-api-glue.js in one
of three places, depending on the role of the function and whether or
not the function uses 64-bit integers in its arguments or result
value. The bindings belong in one of the following file-local arrays:
bindingSignatures.corehouses most functions. These bindings get mapped into thesqlite3.capinamespace.bindingSignatures.int64houses all functions which use 64-bit integers. These bindings are replaced with exception-throwing placeholders when built without BigInt support.bindingSignatures.wasmInternalhouses C-side functions which are intended only for internal use within the project's own JS APIs and are not part of the public interface. Such routines which are exposed to the public API are manually re-exported to thesqlite3.capinamespace or an equivalent wrapper is provided for them.
Each of those arrays contains sub-arrays which describe the function
arguments and return type, as documented for
sqlite3.wasm.xWrap(). During library
initialization, a type-converting proxy for each listed function is
injected into one of the namespaces described above. If any function
listed is missing from the WASM exports, an exception will be
triggered and library initialization will fail.
When adding new entries to one of the function-list arrays, it is
important that all result and argument type names refer to
xWrap()-compatible mapping names (ignoring the special case of
function-type JS-to-WASM conversions). A number of them are extensions
defined in that same file and are not documented in the xWrap() docs
(because they are extensions, not core-defined conversions). Any typos
or unknown type names will lead to an exception during library
initialization.
- ^
This might not be the case any more since
src:e447a50f3a. Changing the build to compile
sqlite3.oseparately is something to explore.