PGlite
Overview
PGlite is a lightweight WASM build of PostgreSQL 17.4 that runs directly in the browser, Node.js, and Bun with no external dependencies. It provides a full Postgres query engine with extensions, transactions, COPY support, and listen/notify in under 3MB gzipped.
When to use: Local-first apps needing a real SQL engine, browser-based analytics, offline-capable PWAs, embedded Postgres for testing, prototyping without a server.
When NOT to use: High-concurrency server workloads (use native Postgres), apps requiring full Postgres replication, Safari OPFS storage (not supported), write-heavy multi-tab scenarios without leader election.
Quick Reference
| Pattern |
API |
Key Points |
| Create instance |
PGlite.create(dataDir?, options?) |
Awaits ready internally |
| In-memory DB |
PGlite.create() |
Default, ephemeral storage |
| IndexedDB storage |
PGlite.create('idb://dbname') |
Persists in browser |
| OPFS storage |
PGlite.create('opfs-ahp://dbname') |
Worker only, no Safari |
| Parameterized query |
db.query<T>(sql, params) |
Returns QueryResult<T> |
| Tagged template |
db.sql\SELECT ...`` |
Auto-parameterized |
| Multi-statement exec |
db.exec(sqlString) |
No params, returns void |
| Transaction |
db.transaction(async (tx) => {}) |
Full ACID, use tx.query |
| Load extension |
extensions: { vector } in create |
Loaded at construction |
| Live query |
db.live.query(sql, params, cb) |
Re-runs on data change |
| Incremental query |
db.live.incrementalQuery(sql, params, key, cb) |
Diffs large result sets |
| Live changes |
db.live.changes(sql, params, key, cb) |
Raw insert/update/delete ops |
| React hook |
useLiveQuery(sql, params) |
Returns { rows } reactively |
| Multi-tab worker |
PGliteWorker + leader election |
Single writer, multi reader |
| Electric sync |
syncShapeToTable(db, shape, table) |
One-way read-only sync |
| Dump data |
db.dumpDataDir('gzip') |
Returns File blob |
| Load data |
PGlite.create({ loadDataDir: blob }) |
Restore from dump |
| Listen/notify |
db.listen(channel, cb) |
Postgres LISTEN/NOTIFY |
| Relaxed durability |
relaxedDurability: true |
Better browser write perf |
Common Mistakes
| Mistake |
Correct Pattern |
Using new PGlite() without awaiting ready |
Use PGlite.create() which awaits internally |
| Using OPFS in main thread |
OPFS-AHP requires a Web Worker context |
| Using OPFS on Safari |
Safari lacks required OPFS APIs; use idb:// |
| Querying outside transaction callback |
Pass tx from transaction callback, use tx.query() |
| Forgetting extension in create options |
Extensions must be declared at PGlite.create({ extensions }) |
| Multiple tabs writing without leader election |
Use PGliteWorker with leader election for multi-tab |
| Expecting Electric sync to be bidirectional |
Electric sync is read-only, one-way from server |
Using live.query() for large datasets |
Use live.incrementalQuery() for large result sets |
Not providing key to incremental query |
Incremental and changes queries require a unique key column |
| Importing extensions without bundler support |
Extensions are WASM; ensure bundler handles .wasm assets |
Delegation
If the electricsql skill is available, delegate Electric sync patterns, Shape configuration, and real-time replication to it.
Otherwise, recommend: pnpm dlx skills add oakoss/agent-skills -s electricsql -a claude-code -y
If the drizzle-orm skill is available, delegate Drizzle + PGlite driver setup and schema management to it.
Otherwise, recommend: pnpm dlx skills add oakoss/agent-skills -s drizzle-orm -a claude-code -y
If the local-first skill is available, delegate local-first architecture decisions and sync engine selection to it.
Otherwise, recommend: pnpm dlx skills add oakoss/agent-skills -s local-first -a claude-code -y
References
- Installation, storage backends, and configuration
- Queries, transactions, COPY, listen/notify, and data import/export
- Extensions: pgvector, pg_trgm, pgcrypto, full-text search, and more
- Live queries: reactive, incremental, and change-tracking
- React integration: providers, hooks, and typed patterns
- Multi-tab worker architecture and leader election
- Electric sync: shapes, transactional sync, and persistence
1---
2name: pglite
3description: |
4 PGlite embeds PostgreSQL 17.4 in the browser and Node.js via WASM, under 3MB gzipped. Covers storage backends, queries, transactions, extensions (pgvector, pg_trgm, full-text search), live queries, React integration, multi-tab worker architecture, and Electric sync.
5
6 Use when setting up PGlite, choosing storage backends, writing queries or transactions, configuring extensions, implementing live queries, integrating with React, managing multi-tab workers, or syncing with Electric.
7license: MIT
8metadata:
9 author: oakoss
10 version: '1.0'
11 source: 'https://pglite.dev/docs'
12user-invocable: false
13---
14
15# PGlite
16
17## Overview
18
19PGlite is a lightweight WASM build of PostgreSQL 17.4 that runs directly in the browser, Node.js, and Bun with no external dependencies. It provides a full Postgres query engine with extensions, transactions, COPY support, and listen/notify in under 3MB gzipped.
20
21**When to use:** Local-first apps needing a real SQL engine, browser-based analytics, offline-capable PWAs, embedded Postgres for testing, prototyping without a server.
22
23**When NOT to use:** High-concurrency server workloads (use native Postgres), apps requiring full Postgres replication, Safari OPFS storage (not supported), write-heavy multi-tab scenarios without leader election.
24
25## Quick Reference
26
27| Pattern | API | Key Points |
28| -------------------- | ------------------------------------------------ | ----------------------------- |
29| Create instance | `PGlite.create(dataDir?, options?)` | Awaits ready internally |
30| In-memory DB | `PGlite.create()` | Default, ephemeral storage |
31| IndexedDB storage | `PGlite.create('idb://dbname')` | Persists in browser |
32| OPFS storage | `PGlite.create('opfs-ahp://dbname')` | Worker only, no Safari |
33| Parameterized query | `db.query<T>(sql, params)` | Returns `QueryResult<T>` |
34| Tagged template | `db.sql\`SELECT ...\`` | Auto-parameterized |
35| Multi-statement exec | `db.exec(sqlString)` | No params, returns void |
36| Transaction | `db.transaction(async (tx) => {})` | Full ACID, use tx.query |
37| Load extension | `extensions: { vector }` in create | Loaded at construction |
38| Live query | `db.live.query(sql, params, cb)` | Re-runs on data change |
39| Incremental query | `db.live.incrementalQuery(sql, params, key, cb)` | Diffs large result sets |
40| Live changes | `db.live.changes(sql, params, key, cb)` | Raw insert/update/delete ops |
41| React hook | `useLiveQuery(sql, params)` | Returns `{ rows }` reactively |
42| Multi-tab worker | `PGliteWorker` + leader election | Single writer, multi reader |
43| Electric sync | `syncShapeToTable(db, shape, table)` | One-way read-only sync |
44| Dump data | `db.dumpDataDir('gzip')` | Returns File blob |
45| Load data | `PGlite.create({ loadDataDir: blob })` | Restore from dump |
46| Listen/notify | `db.listen(channel, cb)` | Postgres LISTEN/NOTIFY |
47| Relaxed durability | `relaxedDurability: true` | Better browser write perf |
48
49## Common Mistakes
50
51| Mistake | Correct Pattern |
52| --------------------------------------------- | -------------------------------------------------------------- |
53| Using `new PGlite()` without awaiting ready | Use `PGlite.create()` which awaits internally |
54| Using OPFS in main thread | OPFS-AHP requires a Web Worker context |
55| Using OPFS on Safari | Safari lacks required OPFS APIs; use idb:// |
56| Querying outside transaction callback | Pass `tx` from transaction callback, use `tx.query()` |
57| Forgetting extension in create options | Extensions must be declared at `PGlite.create({ extensions })` |
58| Multiple tabs writing without leader election | Use `PGliteWorker` with leader election for multi-tab |
59| Expecting Electric sync to be bidirectional | Electric sync is read-only, one-way from server |
60| Using `live.query()` for large datasets | Use `live.incrementalQuery()` for large result sets |
61| Not providing `key` to incremental query | Incremental and changes queries require a unique key column |
62| Importing extensions without bundler support | Extensions are WASM; ensure bundler handles `.wasm` assets |
63
64## Delegation
65
66> If the `electricsql` skill is available, delegate Electric sync patterns, Shape configuration, and real-time replication to it.
67> Otherwise, recommend: `pnpm dlx skills add oakoss/agent-skills -s electricsql -a claude-code -y`
68>
69> If the `drizzle-orm` skill is available, delegate Drizzle + PGlite driver setup and schema management to it.
70> Otherwise, recommend: `pnpm dlx skills add oakoss/agent-skills -s drizzle-orm -a claude-code -y`
71>
72> If the `local-first` skill is available, delegate local-first architecture decisions and sync engine selection to it.
73> Otherwise, recommend: `pnpm dlx skills add oakoss/agent-skills -s local-first -a claude-code -y`
74
75## References
76
77- [Installation, storage backends, and configuration](references/setup.md)
78- [Queries, transactions, COPY, listen/notify, and data import/export](references/queries-and-transactions.md)
79- [Extensions: pgvector, pg_trgm, pgcrypto, full-text search, and more](references/extensions.md)
80- [Live queries: reactive, incremental, and change-tracking](references/live-queries.md)
81- [React integration: providers, hooks, and typed patterns](references/react-integration.md)
82- [Multi-tab worker architecture and leader election](references/multi-tab-worker.md)
83- [Electric sync: shapes, transactional sync, and persistence](references/electric-sync.md)