This is the complete installation guide. Requirements: Node.js >= 24
(declared in engines). Any package manager works:
npm install @appthreat/sqlite3
pnpm add @appthreat/sqlite3
yarn add @appthreat/sqlite3The package installs under Bun and the binding loads — but Bun 1.4's
N-API implementation cannot run this addon's row delivery: synchronous
reads and async completions fail with Invalid argument where every
supported Node works. This is not specific to recent versions —
v9.0.2 fails identically — and it is tracked as a Bun N-API gap, not a
package bug. On the Bun runtime, use Bun's own built-in bun:sqlite;
this package is for Node (>= 24) and Electron (>= 35).
Nothing is downloaded at install time and nothing is compiled at install time on the platforms below — the prebuilt binaries ship inside the npm tarball itself.
One binary per platform, built against Node-API (napi_versions: [10]), so a
single binary covers every supported Node version. Linux builds carry both
libc flavours side by side, tagged .glibc.node / .musl.node.
| Platform | Files in prebuilds/ |
|---|---|
darwin-arm64 |
@appthreat+sqlite3.node |
darwin-x64 |
@appthreat+sqlite3.node |
linux-arm64 |
@appthreat+sqlite3.glibc.node, @appthreat+sqlite3.musl.node |
linux-x64 |
@appthreat+sqlite3.glibc.node, @appthreat+sqlite3.musl.node |
win32-arm64 |
@appthreat+sqlite3.node |
win32-x64 |
@appthreat+sqlite3.node |
The binding is resolved at runtime, not install time:
lib/sqlite3-binding.js calls node-gyp-build(rootDir) on first import,
which looks in prebuilds/<platform>/ first, then falls back to a
build/Release/ build. (For --tag-libc builds the directory stays
linux-<arch> and the libc is carried by the file suffix.)
node-gyp-build resolves the running libc as 'glibc' on every
non-Alpine platform, macOS and Windows included, and prefers a
tag-matching file over an untagged one. So a
prebuilds/darwin-arm64/@appthreat+sqlite3.glibc.node matches the
platform and the libc, outranks the @appthreat+sqlite3.node beside it,
and is loaded in preference to it — even when it comes from an entirely
different revision. That failure is silent: the addon loads, and only the
APIs added since are missing.
Three things prevent it:
pnpm run prebuildgoes throughtools/prebuild.mjs, which forwards--tag-libcon linux and drops it everywhere else. CI passes the flag for every target; the platform rule lives in the wrapper so a local build cannot recreate the trap either.pnpm run check:prebuilds(a CI step after every prebuild) fails when adarwin-*/win32-*directory contains a*.glibc.node/*.musl.node, or when a binary's object format contradicts its directory (a Mach-O file inlinux-x64/).- The loader refuses a binding whose
NATIVE_INTERFACE_VERSIONis not the onelib/expects, naming the file it loaded and therm -rf prebuilds build && pnpm run rebuildremedy. The two constants live insrc/node_sqlite3.ccandlib/sqlite3-binding.jsand are bumped together wheneverlib/starts using a new native export.
pnpm 10 and later refuse to run a dependency's lifecycle scripts unless the
dependent allowlists it. This package declares "install": "node-gyp-build",
so pnpm prints a notice like:
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @appthreat/sqlite3@9.0.2
You can ignore that notice. Verified empirically (pnpm 11.23.0, macOS,
both the published v8 and a packed v9 tarball): with the script blocked, the
module still imports and sqlite3.VERSION prints, because a matching prebuild
exists and runtime resolution never needs the install script.
No onlyBuiltDependencies entry is needed — we ship prebuilds.
The one case where you do need to allow the script is a source build:
no prebuild for your platform, or --build-from-source, --sqlite=,
SQLCipher, or a custom sqlite_magic. (Electron needs none of this: the
Node-API prebuild loads as-is on Electron >= 35 — see
electron.md.) Then the install script must actually run
node-gyp. Add to your pnpm-workspace.yaml:
onlyBuiltDependencies:
- "@appthreat/sqlite3"(or run pnpm approve-builds and select @appthreat/sqlite3), then trigger
the source build, e.g.:
npm_config_build_from_source=true pnpm rebuild @appthreat/sqlite3pnpm rebuild <pkg> (the builtin, with the package named explicitly — here it
is the right tool) re-runs that dependency's build scripts, which invokes
node-gyp-build, which sees the build_from_source config and compiles
instead of resolving a prebuild.
A source build happens when:
- your platform has no prebuild in the table above;
- you pass
--build-from-source; - you build against an external SQLite or SQLCipher (
--sqlite=,--sqlite_libname=); - you set a custom file magic (
--sqlite_magic=); or - you build against Electron headers with a custom
--target(only ever needed together with a source build; the default prebuild loads in Electron unchanged).
Toolchain requirements:
- Python 3 (for node-gyp's gyp)
- a C++17 toolchain: Xcode CLT on macOS, MSVC (msbuild) on Windows, gcc/clang elsewhere
- node-gyp 12.x — installed automatically as an
optionalDependenciesentry when your environment needs it; no global install required
With npm everything works with the classic flags:
npm install @appthreat/sqlite3 --build-from-sourceWith pnpm, allow the script as shown above and set the config via the
environment (npm_config_build_from_source=true), since pnpm does not forward
npm-style -- flags to dependency scripts.
# external sqlite instead of the bundled amalgamation
npm install @appthreat/sqlite3 --build-from-source --sqlite=/usr/local
# homebrew sqlite on macOS
npm install @appthreat/sqlite3 --build-from-source --sqlite=/usr/local/opt/sqlite/
# custom 15-char file magic
npm install @appthreat/sqlite3 --build-from-source --sqlite_magic="MyCustomMagic15"
# SQLCipher
npm install @appthreat/sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/For the full SQLCipher/Electron flag set see the README.
Error: No native build was found for platform=linux arch=arm64 runtime=node ...
node-gyp-build found neither a matching prebuilds/<platform>/ entry nor a
build/Release/ binding. In order of likelihood:
- pnpm blocked the install script on a platform that needs a source
build. You saw the
ERR_PNPM_IGNORED_BUILDSnotice and ignored it, but there is no prebuild for your platform. Add theonlyBuiltDependenciessnippet from above, thenpnpm rebuild @appthreat/sqlite3. - You are developing this repo and have no build yet: run
pnpm install(the root install script compiles the binding) orpnpm run rebuild. - Stale
prebuilds/while iterating on C++:node-gyp-buildprefersprebuilds/overbuild/, so yourpnpm run rebuildoutput is being shadowed. Deleteprebuilds/while iterating. Since 9.1 a mismatch between a resolved binary andlib/throws at import ("loaded a native binding that does not match this JavaScript"), naming the file — rather than presenting as methods missing from the namespace. - Runtime below the Node-API floor (Node < 22, Electron < 35): the
binding loader refuses with an error naming the floors rather than
crashing. On Electron the default prebuild needs no rebuild at all; only
a source build against Electron headers uses
--runtime=electron --target=<version> --dist-url=https://electronjs.org/headers(see electron.md).
This repo is developed with pnpm >= 11 (pinned exactly in
packageManager; corepack enable picks it up). Node >= 24 required.
pnpm install # strictDepBuilds is on; frozen form: pnpm install --frozen-lockfile
pnpm run rebuild # node-gyp rebuild — always `pnpm run rebuild`
pnpm run test
pnpm run prebuild # tools/prebuild.mjs → prebuildify --napi --strip
pnpm run check:prebuilds # refuses a prebuilds/ layout that loads the wrong file
pnpm pack # tarball includes prebuilds/ — smoke-test it in a scratch projectNotes:
- Never bare
pnpm rebuildin this repo — that is pnpm's builtin for rebuilding dependencies; it silently does not run this repo'srebuildscript. The same class of collision is why CI and docs usepnpm run <script>everywhere. pnpm-workspace.yaml(not apnpmfield inpackage.json— pnpm 11 no longer reads one) carries the supply-chain settings:strictDepBuilds,blockExoticSubdeps,trustPolicy: no-downgrade(with a 3-daytrustPolicyIgnoreAfterwindow),minimumReleaseAgeof 3 days excluding@appthreat/*.onlyBuiltDependenciesis intentionally empty today;strictDepBuildsfails the install naming anything blocked, so a needed entry cannot be missed silently. Expect@biomejs/biometo be added when Biome lands.