sql.js: SQLite compiled to WebAssembly, with no persistence unless you add it
A javascript library to run SQLite on the web.
At a glance
- What is it?
- sql.js runs a full SQLite engine inside the browser or Node.js from a WebAssembly build, keeping the whole database in memory. It is a good fit for client-side querying and a poor fit for anything that must survive a page reload.
- Who is it for?
- Adopt sql.js when the database must live inside the page or process and the data set fits comfortably in memory, for example a client-side query tool or a Node.js script that reads a SQLite file and writes a report. Do not adopt it when you need concurrent writers, durable storage without extra work, or tables larger than the browser can hold, because the README states it does not persist changes and loads the entire file into memory.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 47 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem sql.js solves, and who it is actually for
Browsers have no SQL engine. If an application needs to run joins, aggregates or a WHERE clause over data that already sits on the client, the usual options are a JavaScript library that reimplements a query language or a round trip to a server. sql.js takes a third route: it compiles the real SQLite source to WebAssembly with emscripten, so the browser executes the same C code that a native SQLite build would. The README describes it as a "javascript SQL database" that lets you "create a relational database and query it entirely in the browser."
The audience is narrow but real. Someone building an offline-capable analysis page, a teaching tool that lets students run SQL without a backend, or an Electron application that already ships JavaScript everywhere. The README is explicit that native JavaScript applications and Node.js users will "likely prefer to use a native binding of SQLite to JavaScript," because a native binding runs native code and works on database files directly instead of loading the entire database into memory. That sentence is the project telling you where it does not belong.
How the emscripten build and the in-memory database file fit together
The mechanism has two layers. The first is the build: the Makefile downloads a SQLite amalgamation zip, verifies it against a SHA3 hash, and compiles it with emcc using flags such as -Oz, -DSQLITE_OMIT_LOAD_EXTENSION, -DSQLITE_THREADSAFE=0 and -DSQLITE_ENABLE_FTS3. The second is the runtime: the JavaScript wrapper loads a .wasm binary and exposes a SQL.Database constructor over an emscripten virtual file system.
That virtual file system is the part that shapes everything else. The README says sql.js "uses a virtual database file stored in memory, and thus doesn't persist the changes made to the database." Nothing is written to disk. What you get instead is a round trip through typed arrays: new SQL.Database(data) accepts a Uint8Array containing an existing SQLite file, and db.export() returns a Uint8Array containing the current database file. Persistence is your job, whether that means IndexedDB, a file write in Node, or a POST back to a server.
SQLITE_THREADSAFE=0 in the compilation flags is worth noticing. It is a deliberate choice for a single-threaded WebAssembly target, and it means the engine makes no attempt to coordinate concurrent access. In a browser that is mostly academic, because the main thread is the only writer. In a Node.js worker setup it is a constraint you have to design around.
Installing sql.js from npm and running a first query
The package is published on npm as sql.js. Installing it puts the WebAssembly binary at ./node_modules/sql.js/dist/sql-wasm.wasm, and the README instructs you to have your bundler copy that file into your static assets or to load it from a CDN. In Node.js the locateFile option can be omitted entirely.
npm install sql.jsThe README gives this initialization pattern, with locateFile pointing at wherever the .wasm file is served from.
const initSqlJs = require('sql.js');
const SQL = await initSqlJs({
locateFile: file => `https://sql.js.org/dist/${file}`
});
const db = new SQL.Database();From there db.run executes a string with multiple statements and returns nothing, while db.exec returns an array of objects with columns and values arrays. Prepared statements are the path for parameterized work: db.prepare returns a statement, getAsObject binds named parameters, and step advances through rows. The README warns that you cannot use a statement after calling free, and that "not freeing your statements causes memory leaks."
db.run("CREATE TABLE hello (a int, b char); INSERT INTO hello VALUES (0, 'hello');");
const stmt = db.prepare("SELECT * FROM hello WHERE a=:aval AND b=:bval");
const row = stmt.getAsObject({ ':aval': 0, ':bval': 'hello' });
stmt.free();When you are done, db.export() gives you the Uint8Array to store. The examples directory contains a persistent.html example, and examples/start_local_server.py is provided for serving the demo files locally.
The persistence gap is the limitation that decides most adoptions
Every other limitation follows from the absence of a file. There is no WAL, no locking, no crash recovery, and no incremental write. A user who closes the tab loses everything that was not exported first. If you export on every change, you pay a full serialization of the database each time, which means the cost of saving grows with the size of the data rather than the size of the change.
Memory is the second wall. The README notes that a native binding avoids "out of memory errors" precisely because it does not load the whole database. A virtual file system in a browser tab has a ceiling that varies by engine and device, and the failure mode is a crash rather than a slow query. A 50 MB SQLite file is a reasonable thing to open with a native binding and a risky thing to open in a mobile browser tab.
Feature coverage is the third. The Makefile enables FTS3 and FTS3_PARENTHESIS but does not list FTS5, so a schema that depends on FTS5 tables will not work with the default distributed build. Extension loading is compiled out with SQLITE_OMIT_LOAD_EXTENSION, so you cannot load a runtime extension to fill the gap. If your application needs FTS5, that is a reason to look elsewhere before you write any code.
sql.js versus the sqlite3 npm package and better-sqlite3
The npm package sqlite3 is a native binding: it links against a C SQLite library and reads and writes database files on disk. The difference in approach is not performance tuning, it is where the file lives. With sqlite3, a query touches only the pages it needs and the operating system handles durability. With sql.js, the whole file is a buffer in your process, and durability is a function you write.
better-sqlite3 takes the same native approach and is synchronous by design, which makes it a natural fit for scripts and server-side code where a blocking call is acceptable. Neither of these runs in a browser tab. That is the trade: sql.js is the only one of the three that works where there is no filesystem, and it gives up the properties that come from having one.
IndexedDB is the alternative people reach for when the requirement is storage rather than SQL. It persists without a server, but it stores object records and indexes, not relations, and there is no query planner to join two stores. If your queries are key lookups, IndexedDB is simpler. If they are joins and aggregates, sql.js is the one that speaks SQL, and you will be writing the persistence layer yourself either way.
Maintenance, licence and the cost of upgrading
The repository is not archived, and the last push was on 2026-08-14, which is the same day as the v1.14.2 release. The two releases before it were v1.14.1 on 2026-03-04 and v1.14.0 on 2026-02-12. package.json lists the version as 1.14.2 and declares MIT as the licence, while the README notes that SQLite itself is public domain. The GitHub metadata reports the licence as NOASSERTION, which is a detection artifact rather than a conflict: the package manifest and the LICENSE file agree on MIT.
The upgrade cost is low for consumers, because the API surface is small and stable. The work sits on the build side. The Makefile pins a specific SQLite amalgamation version and checks it against a SHA3 hash, and it carries a note that the output was last built with version 2.0.15 of Emscripten. If you vendor your own build to change compile flags or add FTS5, you inherit that toolchain and the job of tracking SQLite releases yourself. If you consume the published package, you get whatever flags the maintainers chose, and the FTS5 gap is part of the deal.
The MIT licence covers the JavaScript wrapper. It does not change the fact that you are embedding a database engine in a client, so the data your users load is visible in their browser. That is a design consequence, not a legal one.
Editorial conclusion
Adopt sql.js when the database must live inside the page or process and the data set fits comfortably in memory, for example a client-side query tool or a Node.js script that reads a SQLite file and writes a report. Do not adopt it when you need concurrent writers, durable storage without extra work, or tables larger than the browser can hold, because the README states it does not persist changes and loads the entire file into memory. Before committing, confirm that your bundler serves sql-wasm.wasm from the path you pass to locateFile, and check whether you need FTS5, which the Makefile's SQLITE_COMPILATION_FLAGS do not list.
Frequently asked questions
What is sql.js?
It is SQLite compiled to WebAssembly with emscripten, wrapped in a JavaScript library that runs a relational database entirely in the browser or in Node.js. The README describes it as a javascript SQL database that uses a virtual database file stored in memory.
How do I install sql.js?
Install the sql.js package from npm, then load the sql-wasm.wasm file that ships at ./node_modules/sql.js/dist/sql-wasm.wasm. The README says to point the locateFile option of initSqlJs at wherever you serve that file, or omit locateFile entirely in Node.js.
How do I use sql.js to run a query?
Call initSqlJs to get the SQL constructor, create a database with new SQL.Database(), then use db.run for statements that return nothing or db.exec for queries that return columns and values. For parameterized queries, db.prepare returns a statement that you bind values to and must free afterward.
What is the difference between sql.js and the sqlite3 npm package?
sqlite3 is a native binding that reads and writes database files on disk, while sql.js loads the entire database into memory as a typed array. The README states that a native binding will be faster and will avoid out of memory errors, and recommends one for Node.js and native JavaScript applications.
How does sql.js compare to sqlite wasm?
The README, the Makefile and package.json do not describe sqlite wasm, so that comparison cannot be made from what the project publishes. What those files do establish is that sql.js is SQLite compiled with emscripten and shipped as a JavaScript wrapper around a WebAssembly binary.
Is sql.js a good alternative to IndexedDB?
They store different things. IndexedDB persists object records and indexes without a server, while sql.js runs SQL over a database held in memory and exports it as a Uint8Array that you still have to save somewhere. The README states that sql.js does not persist changes to the database.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sql-js-sql-js)