# SQLite-JS: writing SQLite functions in JavaScript instead of C

> SQLite-JS is a loadable SQLite extension, written in C around a QuickJS interpreter, that lets you define scalar functions, aggregates, window functions and collations in JavaScript from inside SQL, with the script sandboxed. It suits an application that wants custom database logic without shipping a native extension, and it is not a way to run untrusted code quickly.

**sqliteai/sqlite-js** — Create custom SQLite functions in JavaScript. Extend your database with scalars, aggregates, window functions, and collations directly in JavaScript.

- Repository: https://github.com/sqliteai/sqlite-js
- Website: https://sqlite.ai
- Stars: 568 · Forks: 15
- Language: C
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/sqliteai-sqlite-js

## A loadable extension that puts JavaScript inside the query

SQLite-JS is a loadable SQLite extension for creating custom functions in JavaScript, and the readme's own framing is that it removes the need for C extensions for custom logic. The four function families it supports are the four SQLite offers natively: scalars that process individual rows and return a single value, aggregates that process multiple rows and return one aggregated result, window functions that behave like aggregates but can access the full dataset, and collation sequences that define custom sort orders for text.

There is a fifth capability listed in the functions overview table, JavaScript evaluation, described as directly evaluating JavaScript code within SQLite.

The mechanism is that a function is created by a SQL call rather than by a host API. The scalar form takes a name and a body:

```sql
SELECT js_create_scalar('function_name', 'function_code');
```

The body has to be in the form function(args) { ... }, which is a constraint the readme states explicitly and which matters because the extension evaluates the string and takes the resulting function. The naming of the arguments is also documented, since a function name has to satisfy SQLite's own rules for function names.

That approach is the interesting design decision. Instead of writing a C function, compiling it, and linking it into a loadable module, you call a SQL statement that creates a function at runtime. The cost is that every call from SQLite into your code crosses the extension boundary, and the benefit is that the logic lives in a language you can edit without a compiler in the loop.

The extension is implemented in C, with the primary language listed as C, and it embeds QuickJS as the JavaScript engine. The readme's headline mentions full QuickJS sandboxing, and the build manifest confirms it by compiling src/sqlitejs.c together with libs/quickjs.c into the extension. So the JavaScript does not run in the host process's interpreter; it runs in an interpreter embedded inside the extension, which is what makes the sandbox claim meaningful rather than decorative.

## Loading it, and the five platform packages

Prebuilt binaries are published on the releases page for Linux on x86 and ARM, macOS on x86 and ARM, Windows on x86, plus Android and iOS.

Loading the extension is the same operation SQLite exposes everywhere. In the SQLite CLI it is the .load meta-command:

```sql
-- In SQLite CLI
.load ./js
```

and in SQL it is the load_extension function:

```sql
-- In SQL
SELECT load_extension('./js');
```

Those two lines are the entire integration for a desktop application. Everything else in the readme is packaging for a specific language runtime on top of that same shared library.

For Swift, the repository is added as a package dependency, and because SQLite on Apple platforms has extension loading disabled by default, the readme points at steps 4 and 5 of the project's own extensions guide for enabling it. The example then uses sqlite3_enable_load_extension followed by sqlite3_load_extension with the package's path, and calls a js_version() function to confirm the extension is live.

For Android, the artifact is published to Maven Central as ai.sqlite:js, and the readme shows the Gradle line:

```gradle
implementation 'ai.sqlite:js:1.3.0'
```

with a note that additional settings and configuration are required for a complete setup, pointing at a full example in the extensions guide. The Android example constructs a SQLiteCustomExtension from the native library directory and passes it through SQLiteDatabaseConfiguration.

For Node.js, the package is @sqliteai/sqlite-js and the readme shows it working with better-sqlite3:

```typescript
import { getExtensionPath } from '@sqliteai/sqlite-js';
import Database from 'better-sqlite3';

const db = new Database(':memory:');
db.loadExtension(getExtensionPath());
console.log(db.prepare('SELECT js_version()').pluck().get());
```

For Flutter and Dart, the package is sqlite_js on pub.dev, with loadSqliteJsExtension() doing the loading and the readme noting that the dart pub variant of the command exists for pure Dart projects.

So the surface is one shared library plus five wrappers, and the wrappers exist mainly because the tricky part on each platform is enabling extension loading in the first place.

## The build system and what it does per platform

The Makefile is the clearest document of what the extension has to cope with across five operating systems, and it is worth reading even if you only use the prebuilt binaries.

The platform detection is done in the Makefile itself. On Windows it checks for the Windows_NT environment variable and takes the processor count from PowerShell. Everywhere else it takes the host name from uname, lowercases it, and branches: darwin becomes macos, and anything else keeps the uname name as the platform. The core count comes from sysctl on macOS and nproc elsewhere, and it feeds into MAKEFLAGS with a -j flag so the build uses every core.

The source list is two files, src/sqlitejs.c and libs/quickjs.c, with include paths into both directories. The compiler is gcc by default with flags -Wall -Wextra -fPIC -g -O2 and the define QJS_BUILD_LIBC, which tells QuickJS to include its standard library pieces rather than building a bare interpreter.

Each platform then gets its own target name and its own link flags. Windows produces dist/js.dll with -shared and generates a .def file, and strips with --strip-unneeded. macOS produces dist/js.dylib, and this is where the universal binary logic lives: with no ARCH variable set it compiles for both x86_64 and arm64, and setting ARCH narrows it to a single architecture. The macOS link line adds -dynamiclib, -undefined dynamic_lookup and -headerpad_max_install_names, which are the flags that let an extension resolve SQLite symbols at load time rather than at link time. macOS also strips with -x -S.

Android is handled separately and requires two variables to be set: ARCH, which must be x86_64 or arm64-v8a, and ANDROID_NDK, the path to the NDK, since the compiler is the NDK's Clang. The Makefile errors out with an explanatory message if either is missing.

The JavaScript engine is compiled from source rather than linked as a system library, which is why libs/quickjs.c is in the source list. That is what keeps the extension self-contained and portable, at the cost of a larger binary.

## Where the project sits in the wider SQLite AI family

The readme places SQLite-JS inside a family of projects, and that context says something about the direction of the work.

The data side of the family is SQLite Vector, SQLite Sync, SQLite Columnar and SQLite JS itself. The AI side is SQLite AI, SQLite Agent, SQLite Memory and SQLite MCP. The naming makes the intent clear: these are components intended to be composed, with SQLite JS as the layer that lets application-specific logic live inside the database rather than in the application.

There is also a commercial relationship to note. The readme advertises a free managed instance, links to the SQLite Cloud dashboard for sign-in, to the SQLite Cloud docs, and to a blog, and it ends the installation section with a note for anyone wanting to run the same JavaScript server-side in the cloud: SQLite JS runs locally as an extension, and it can be paired with SQLite Cloud Edge Functions to deploy the same JavaScript on the server. A free tier is mentioned.

That is the natural commercial shape for this project. The local extension is Apache-2.0 licensed and downloadable, and the managed service is where the hosted version of the same idea is sold. The homepage is sqlite.ai.

For an evaluator, the relevant question is whether the local extension alone is sufficient. For a desktop or mobile application that wants custom SQL functions, it is a complete artefact on its own, and the cloud pairing is an option rather than a requirement.

## Where SQLite-JS is the wrong tool

The first limitation is performance, and it is structural rather than a tuning problem. Every call into your JavaScript crosses from SQLite's C core into an embedded interpreter, and QuickJS is an interpreter rather than a JIT-compiled engine in the sense that V8 is. For a function applied to a million rows, that boundary cost is paid a million times. The extension is a good fit for logic invoked on a handful of rows per query, and a poor fit for a per-row transformation over a large scan, where doing the work in the application or in SQL is usually faster anyway.

The second is that extension loading is often disabled in production builds, and this is where most of the integration pain lives. SQLite compiles extension loading out unless it is enabled, and Apple platforms in particular ship with it off. The readme has to send you to a separate guide for the steps to enable it on iOS, and the Android example carries a note that additional settings and configuration are required for a complete setup. A team whose deployment target uses a stock SQLite build will not be able to load this extension at all.

The third is the JavaScript string body. Functions are defined by evaluating a string that must match a particular shape, function(args) { ... }. That means your logic lives in SQL string literals or in values read from the database, and there is no editor tooling, no type checking and no module system. It also means the code is data, which raises questions about where it comes from and who can change it.

The fourth is the sandbox claim, which needs care in how you read it. The extension embeds QuickJS, and the readme advertises full QuickJS sandboxing. QuickJS is a genuine sandbox with its own memory management and no access to the host by default, which is much better than embedding Node, but it is not a security boundary you should assume against a determined attacker if your function bodies come from untrusted input. Nothing in the readme documents a resource limit policy, and the readme does not document what happens if your JavaScript throws, allocates without bound or loops forever. The behaviour on a timeout or an exception inside a function is not specified in the documentation, and that is the first thing to test.

The fifth is the release cadence and the version spread. The newest release is 1.3.3 from 2026-04-07 while the last push was on 2026-10-20, and the Gradle example in the readme pins ai.sqlite:js:1.3.0, so the published artefacts and the documented versions can be a release or two apart.

## Conclusion

SQLite-JS fits a team that wants to ship custom database logic without maintaining a C extension per platform, since the extension is distributed as prebuilt binaries for Linux, macOS, Windows, Android and iOS plus wrappers for Swift, Android, Node.js and Flutter, and the JavaScript runs inside a QuickJS sandbox rather than in the host process. It is the wrong choice if you need per-row performance beyond what an interpreter can give, since every call crosses the C to JavaScript boundary, and it is the wrong choice if you need the extension to survive in a build where extension loading is disabled. Before adopting it, check that your SQLite build permits extension loading at all, read the function naming rules in the readme, and note that the last push was on 2026-10-20 with the newest release 1.3.3 from 2026-04-07.

## FAQ

### What is SQLite-JS?

SQLite-JS is a loadable SQLite extension that lets you create custom SQLite functions, aggregates, window functions and collation sequences using JavaScript code instead of a C extension. It embeds a QuickJS interpreter, so the script runs sandboxed inside the extension.

### How do I load the SQLite-JS extension?

In the SQLite CLI use the .load meta-command with the path to the shared library, and in SQL use the load_extension function, for example .load ./js or SELECT load_extension('./js');. Prebuilt binaries are published for Linux, macOS, Windows, Android and iOS.

### How do I create a custom scalar function with SQLite-JS?

Call js_create_scalar with the function name and the JavaScript body, for example SELECT js_create_scalar('function_name', 'function_code');. The code must be in the form function(args) { your code here }, and the function name has to satisfy SQLite's function naming rules.

### How do I use SQLite-JS from Node.js?

Install the @sqliteai/sqlite-js package with npm install @sqliteai/sqlite-js, then use the exported getExtensionPath with better-sqlite3: create the database, call db.loadExtension(getExtensionPath()), and query the extension with something like SELECT js_version().

### What licence is SQLite-JS released under?

Apache-2.0, with the licence file in the repository root. The last push was on 2026-10-20 and the newest release is 1.3.3 from 2026-04-07.

## Sources

- [License: Apache-2.0](https://github.com/sqliteai/sqlite-js/blob/main/LICENSE)
- [Project website](https://sqlite.ai)
- [README](https://github.com/sqliteai/sqlite-js/blob/main/README.md)
- [Releases](https://github.com/sqliteai/sqlite-js/releases)
- [sqliteai/sqlite-js on GitHub](https://github.com/sqliteai/sqlite-js)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sqliteai-sqlite-js
