PGlite: Run PostgreSQL in the Browser with WASM
PGlite is a groundbreaking project that brings the full power of PostgreSQL into the browser using WebAssembly (WASM). By compiling the PostgreSQL core to WASM, PGlite lets developers run a real, ACID‑compliant SQL engine completely client‑side, without any server round‑trips. This opens up a whole new class of offline‑first apps, interactive data visualizations, and educational tools where the database lives right in the user's device.
What is PGlite?
PGlite is essentially PostgreSQL compiled to WebAssembly, packaged as a lightweight JavaScript library. It ships with the core engine, a subset of extensions, and a tiny virtual file system that persists data in IndexedDB or the browser's memory. Because it runs in a sandboxed WASM environment, it inherits the security guarantees of the browser while still offering the rich SQL dialect, transactional guarantees, and extensibility that PostgreSQL is famous for.
Unlike “SQL.js” which emulates SQLite, PGlite gives you the full PostgreSQL feature set: sophisticated query planner, JSONB support, full‑text search, and even logical replication hooks (though the latter are limited in the browser). This means you can prototype complex data models locally and later switch to a server‑side PostgreSQL instance with minimal friction.
How It Works Under the Hood
The magic starts with Emscripten, a toolchain that translates C code into WebAssembly. The PostgreSQL source is compiled with a custom runtime that replaces file I/O with the browser's virtual file system (VFS). When you call await PGlite(), the WASM module is instantiated, memory is allocated, and the database engine boots just like it would on a Linux box.
Data persistence is handled by the FS API provided by Emscripten. By default, PGlite writes its data files to an in‑memory filesystem, but you can enable IndexedDB persistence with a single flag. This means your tables survive page reloads, and you can even export the raw database file for server‑side import later.
Getting Started
First, add the PGlite script to your HTML. The library is distributed via npm and a CDN, so you can choose the workflow that fits your project.
/* index.html */
<script type="module">
import { PGlite } from "https://cdn.jsdelivr.net/npm/@electric-sql/pglite@latest/dist/pglite.mjs";
// Create a new in‑memory instance
const db = await PGlite();
// Simple test query
const result = await db.query("SELECT version();");
console.log(result[0].version);
</script>
If you prefer a bundler, install via npm and import it in your JavaScript bundle:
npm install @electric-sql/pglite
// In your entry file
import { PGlite } from "@electric-sql/pglite";
(async () => {
const db = await PGlite({ persist: true }); // enables IndexedDB
// Your code here
})();
First Example: Simple Query
Let’s build a tiny todo list that lives entirely in the browser. The example demonstrates table creation, insertion, and a SELECT query—all powered by PGlite.
const db = await PGlite({ persist: true });
await db.exec(`
CREATE TABLE IF NOT EXISTS todos (
id SERIAL PRIMARY KEY,
task TEXT NOT NULL,
done BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT now()
);
`);
await db.exec(`
INSERT INTO todos (task) VALUES
('Buy groceries'),
('Write blog post about PGlite'),
('Walk the dog');
`);
const pending = await db.query(`
SELECT id, task, created_at
FROM todos
WHERE done = FALSE
ORDER BY created_at DESC;
`);
console.table(pending);
The await db.exec() method runs DDL or DML statements without returning rows, while await db.query() returns an array of JavaScript objects representing each row. Notice that timestamps are automatically converted to JavaScript Date objects, making further processing straightforward.
Advanced Example: Using Extensions
One of PostgreSQL’s strengths is its extensibility. PGlite ships with a curated set of extensions that work in the browser, including pgcrypto for cryptographic functions and jsonb operators for advanced JSON handling. Below we’ll encrypt a user’s secret note before storing it.
await db.exec(`CREATE EXTENSION IF NOT EXISTS pgcrypto;`);
await db.exec(`
CREATE TABLE IF NOT EXISTS secrets (
id SERIAL PRIMARY KEY,
owner TEXT NOT NULL,
payload BYTEA NOT NULL,
created TIMESTAMPTZ DEFAULT now()
);
`);
const secret = "My super secret API key: abc123!";
const key = "my-strong-passphrase";
// Encrypt using pgp_sym_encrypt
await db.exec(`
INSERT INTO secrets (owner, payload)
VALUES ($1, pgp_sym_encrypt($2, $3));
`, ["alice", secret, key]);
// Decrypt on read
const rows = await db.query(`
SELECT
id,
owner,
pgp_sym_decrypt(payload, $1) AS plaintext,
created
FROM secrets
WHERE owner = $2;
`, [key, "alice"]);
console.log(rows[0].plaintext); // => "My super secret API key: abc123!"
This snippet showcases parameterized queries, binary data handling (BYTEA), and the power of built‑in extensions—all without leaving the browser. The same code would run unchanged on a traditional PostgreSQL server, making migration trivial.
Real‑World Use Cases
Offline‑first web apps: Imagine a field‑service tool that collects sensor data in remote locations. With PGlite, the app can store readings locally, run complex aggregations, and sync to a backend when connectivity returns.
Interactive data visualizations: Libraries like D3 or Vega can query a PGlite database directly, enabling users to slice and dice large CSV imports without sending data to a server. This reduces latency and protects sensitive information.
Educational platforms: Codeyaan can embed a full PostgreSQL environment in its lessons, letting students experiment with joins, window functions, and indexes without setting up a local server. The instant feedback loop accelerates learning.
Performance Tips & Gotchas
PGlite runs in a single JavaScript thread, so CPU‑bound queries can block UI interactions. To mitigate this, wrap heavy operations in requestIdleCallback or offload them to a Web Worker that loads the same WASM module.
Memory usage scales with the size of your data files. While IndexedDB persistence frees RAM on reload, the in‑memory representation still occupies JavaScript heap space. Monitor memory with the browser’s performance tab and consider pruning old rows or archiving to a server when the dataset grows beyond a few hundred megabytes.
Not all PostgreSQL extensions are available. If you need a custom extension, you’ll have to compile it into the WASM binary yourself, which can be non‑trivial. Check the official PGlite repo for the current list of supported extensions before committing to a feature.
Pro Tip: Enable the debug: true flag when initializing PGlite during development. It prints the underlying WASM logs to the console, helping you spot SQL errors, VFS issues, or unexpected garbage collection pauses.
Best Practices for Production Deployments
1. Persist data wisely: Use the { persist: true } option only when you truly need data across sessions. For temporary caches, the default in‑memory mode reduces IndexedDB churn.
2. Secure secrets: Even though the database runs client‑side, never store production credentials or private keys in the WASM layer. Use the browser’s Crypto.subtle API for additional encryption before writing to PGlite.
3. Version your schema: Because the database lives in the client, schema migrations must be handled in JavaScript. Adopt a migration framework (e.g., node-pg-migrate adapted for the browser) that runs incremental ALTER TABLE scripts on first load.
4. Leverage streaming inserts: For bulk data imports (e.g., CSV uploads), wrap inserts in a single transaction. This dramatically reduces the number of round‑trips between JavaScript and the WASM runtime.
Future Roadmap & Community
The PGlite project is actively maintained, with upcoming features like logical replication support, more extensions (PostGIS, pg_partman), and improved multi‑threaded query execution via Web Workers. The community contributes via GitHub issues, and the maintainers encourage pull requests that add new extensions or performance benchmarks.
For developers who want to stay ahead, subscribe to the project’s mailing list, watch the repository releases, and experiment with the example apps that showcase real‑world scenarios ranging from offline note‑taking to collaborative spreadsheet editors.
Conclusion
PGlite transforms the browser into a fully‑featured PostgreSQL engine, blurring the line between client and server data processing. By harnessing WebAssembly, it delivers ACID guarantees, rich SQL capabilities, and extensibility—all without a single line of server‑side code. Whether you’re building offline‑first tools, interactive visualizations, or next‑generation learning platforms, PGlite empowers you to prototype and ship sophisticated data experiences with unprecedented speed.