pglite Documentation

Embeddable Postgres with real-time, reactive bindings.

15,513 stars TypeScript TypeScript 1 file ยท ~15,498 tokens #database#postgres#wasm
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy pglite on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name pglite -p 54321:54321 --restart always electricsql/electric
Configured Services & Ports
โš™๏ธ postgres
image: postgres:16
๐Ÿ”Œ Ports: 54321:5432
โš™๏ธ electric
image: electricsql/electric
๐Ÿ”Œ Ports: 3000:3000
docker-compose.yml
yaml (docker-compose.yml)
version: "3.3"
name: "electric_quickstart"

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: electric
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - 54321:5432
    tmpfs:
      - /var/lib/postgresql/data
      - /tmp
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    command:
      - -c
      - listen_addresses=*
      - -c
      - wal_level=logical

  electric:
    image: electricsql/electric
    environment:
      DATABASE_URL: postgresql://postgres:password@postgres:5432/electric?sslmode=disable
      # Not suitable for production. Only use insecure mode in development or if you've otherwise secured the Electric API.
      # See https://electric-sql.com/docs/guides/security
      ELECTRIC_INSECURE: true
    ports:
      - "3000:3000"
    depends_on:
      - postgres

File: README.md

PGlite - Postgres in WASM

PGlite is a WASM Postgres build packaged into a TypeScript client library that enables you to run Postgres in the browser, Node.js, Bun and Deno, with no need to install any other dependencies. It is only 3mb gzipped and has support for many Postgres extensions, including pgvector and PostGIS.

javascript
import { PGlite } from "@electric-sql/pglite";

const db = new PGlite();
await db.query("select 'Hello world' as message;");
// -> { rows: [ { message: "Hello world" } ] }

It can be used as an ephemeral in-memory database, or with persistence either to the file system (Node/Bun/Deno) or indexedDB (Browser).

Unlike previous "Postgres in the browser" projects, PGlite does not use a Linux virtual machine - it is simply Postgres in WASM.

For full documentation and user guides see pglite.dev.

Browser

It can be installed and imported using your usual package manager:

js
import { PGlite } from "@electric-sql/pglite";

or using a CDN such as JSDeliver:

js
import { PGlite } from "https://cdn.jsdelivr.net/npm/@electric-sql/pglite/dist/index.js";

Then for an in-memory Postgres:

js
const db = new PGlite()
await db.query("select 'Hello world' as message;")
// -> { rows: [ { message: "Hello world" } ] }

or to persist the database to indexedDB:

js
const db = new PGlite("idb://my-pgdata");

Node/Bun/Deno

Install into your project:

NodeJS

bash
npm install @electric-sql/pglite

Bun

bash
bun install @electric-sql/pglite

Deno

bash
deno add npm:@electric-sql/pglite

To use the in-memory Postgres:

javascript
import { PGlite } from "@electric-sql/pglite";

const db = new PGlite();
await db.query("select 'Hello world' as message;");
// -> { rows: [ { message: "Hello world" } ] }

or to persist to the filesystem:

javascript
const db = new PGlite("./path/to/pgdata");

How it works

PostgreSQL typically operates using a process forking model; whenever a client initiates a connection, a new process is forked to manage that connection. However, programs compiled with Emscripten - a C to WebAssembly (WASM) compiler - cannot fork new processes, and operates strictly in a single-process mode. As a result, PostgreSQL cannot be directly compiled to WASM for conventional operation.

Fortunately, PostgreSQL includes a "single user mode" primarily intended for command-line usage during bootstrapping and recovery procedures. Building upon this capability, PGlite introduces an input/output pathway that facilitates interaction with PostgreSQL when it is compiled to WASM within a JavaScript environment.

Limitations

  • PGlite is single user/connection.

How to build PGlite and contribute

The build process of PGlite is split into two parts:

  1. Building the Postgres WASM module.
  2. Building the PGlite client library and other TypeScript packages.

Docker is required to build the WASM module, along with Node (v20 or above) and pnpm for package management and building the TypeScript packages.

To start checkout the repository and install dependencies:

bash
git clone --recurse-submodules https://github.com/electric-sql/pglite
cd pglite
pnpm install

To build everything, we have the convenient pnpm build:all command in the root of the repository. This command will:

  1. Use Docker to build the Postgres WASM module. The artifacts produced by this step are then copied to /packages/pglite/release.
  2. Build the PGlite client library and other TypeScript packages.

To only build the Postgres WASM module (i.e. point 1 above), run

bash
pnpm wasm:build

If you don't want to build the WASM module and assorted WASM binaries from scratch, they are generated automatically on Github after each successful PR merge. You can download the latest binaries by going to the last successfully merged PR and clicking the link after the comment Interim build files:. Extract the files and place them under packages/pglite/release in your local repo copy.

To build all TypeScript packages (i.e. point 2 of the above), run:

bash
pnpm ts:build

This will build all packages in the correct order based on their dependency relationships. You can now develop any individual package using the build and test scripts, as well as the stylecheck and typecheck scripts to ensure style and type validity.

Or alternatively to build a single package, move into the package directory and run:

bash
cd packages/pglite
pnpm build

When ready to open a PR, run the following command at the root of the repository:

bash
pnpm changeset

And follow the instructions to create an appropriate changeset. Please ensure any contributions that touch code are accompanied by a changeset.

Acknowledgments

PGlite builds on the work of Stas Kelvich of Neon in this Postgres fork.

Sponsors

Big shoutout to everybody supporting us!

Blacksmith

License

PGlite is dual-licensed under the terms of theย Apache License 2.0ย and theย PostgreSQL License, you can choose which you prefer.

Changes to theย Postgres sourceย are licensed under the PostgreSQL License.


File: docs/docs/framework-hooks/react.md


outline: [2, 3]

React

To aid integration of PGlite into a React project we have a PGliteProvider with a corresponding usePGlite and hooks for the live query plugin.

PGliteProvider

The PGliteProvider enables you to initiate a PGlite database and pass it to all child components for use with the usePGlite, useLiveQuery, and useLiveIncrementalQuery hooks.

To use it, pass a PGlite instance as the db property.

ts
import { PGlite } from "@electric-sql/pglite"
import { live } from "@electric-sql/pglite/live"
import { PGliteProvider } from "@electric-sql/pglite-react"

const db = await PGlite.create({
  extensions: { live }
})

const App = () => {
  // ...

  return (
    
      // ...
    
  )
}

usePGlite

You can retrieve the provided PGlite instance using usePGlite and then query it from within your components.

ts
import { usePGlite } from "@electric-sql/pglite-react"

const MyComponent = () => {
  const db = usePGlite()

  const insertItem = () => {
    db.query("INSERT INTO my_table (name, number) VALUES ('Arthur', 42);")
  }

  return (
    <>
      
  )
}

makePGliteProvider

The makePGliteProvider function returns a PGliteProvider component and a usePGlite hook with the specified type, which enables you to provide a PGlite instance with all added extensions and retain then namespaces and types added to it.

ts
import { PGlite, PGliteInterfaceExtensions } from '@electric-sql/pglite'
import { LiveNamespace } from '@electric-sql/pglite/live'
import { VectorNamespace } from '@electric-sql/pglite-pgvector'
import { makePGliteProvider } from '@electric-sql/pglite-react'

const { PGliteProvider, usePGlite } = makePGliteProvider<
  PGlite &
    PGliteInterfaceExtensions<{
      live: typeof live
      vector: typeof vector
    }>
>()

export { PGliteProvider, usePGlite }

useLiveQuery

The useLiveQuery hook enables you to reactively re-render your component whenever the results of a live query change. It wraps the .live.query() API.

It has the interface:

ts
function useLiveQuery<T = { [key: string]: unknown }>(
  query: string,
  params: unknown[] | undefined | null,
): Results<T>

And its arguments are:

  1. the SQL query
  2. optional parameters for the query
ts
import { useLiveQuery } from '@electric-sql/pglite-react'

const MyComponent = () => {
  const maxNumber = 100
  const items = useLiveQuery(`
    SELECT *
    FROM my_table
    WHERE number <= $1
    ORDER BY number;
  `, [maxNumber])

  return (
    <>
      {
        items.map((item) =>
          <MyItem item={item} />
        )
      }
    </>
  )
}

useLiveQuery.sql

Similarly to how you can use [ sql``](../api.md#sql) to to construct SQL queries through string templating, you can do that with useLiveQuery.sql`` for the hook equivalent. See the templating section of the API for more details.

ts
import { useLiveQuery } from '@electric-sql/pglite-react'

const MyComponent = () => {
  const maxNumber = 100
  const items = useLiveQuery.sql`
    SELECT *
    FROM my_table
    WHERE number <= ${maxNumber}
    ORDER BY number;
  `
// ...

useLiveIncrementalQuery

The useLiveIncrementalQuery hook enables you to reactively re-render your component whenever the results of a live query change. It wraps the .live.incrementalQuery() API, which provides a way to efficiently diff the query results in Postgres.

It has the interface:

ts
function useLiveIncrementalQuery<T = { [key: string]: unknown }>(
  query: string,
  params: unknown[] | undefined | null,
  key: string,
): Results<T>

And its arguments are:

  1. the SQL query
  2. optional parameters for the query
  3. the name of the column to key the diff algorithm on
ts
import { useLiveIncrementalQuery } from '@electric-sql/pglite-react'

const MyComponent = () => {
  const maxNumber = 100
  const items = useLiveIncrementalQuery(`
    SELECT *
    FROM my_table
    WHERE number <= $1
    ORDER BY number;
  `, [maxNumber], 'id')

  return (
    <>
      {
        items.map((item) =>
          <MyItem item={item} />
        )
      }
    </>
  )
}

File: docs/docs/framework-hooks/vue.md


outline: [2, 3]

Vue

To aid integration of PGlite into a Vue project we have a providePGlite with a corresponding injectPGlite and hooks for the live query plugin.

providePGlite

The providePGlite API, which follows the Vue provide / inject pattern, enables you to initiate a PGlite database and pass it to all child components for use with the corresponding injectPGlite method, as well as with the useLiveQuery and useLiveIncrementalQuery hooks.

To use it, pass a PGlite instance as the db property.

vue
<script lang="ts" setup>
import { PGlite } from '@electric-sql/pglite'
import { providePGlite } from '@electric-sql/pglite-vue'

const db = new PGlite()
providePGlite(db)
</script>
// ...

injectPGlite

You can retrieve the provided PGlite instance using injectPGlite and then query it from within your components.

vue
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue'
import { injectPGlite } from '@electric-sql/pglite-vue'

const db = injectPGlite()

const insertItem = () => {
  db.query("INSERT INTO my_table (name, number) VALUES ('Arthur', 42);")
}
</script>

<template>
  // ...
  Insert item
  // ...
</template>

makePGliteDependencyInjector

The makePGliteDependencyInjector function returns typed versions of providePGlite and injectPGlite, which enables you to provide a PGlite instance with all added extensions and retain then namespaces and types added to it.

ts
import { PGlite, PGliteInterfaceExtensions } from '@electric-sql/pglite'
import { live } from '@electric-sql/pglite/live'
import { vector } from '@electric-sql/pglite-pgvector'
import { makePGliteDependencyInjector } from '@electric-sql/pglite-vue'

const { providePGlite, injectPGlite } = makePGliteDependencyInjector<
  PGlite &
    PGliteInterfaceExtensions<{
      live: typeof live
      vector: typeof vector
    }>
>()

export { providePGlite, injectPGlite }

useLiveQuery

The useLiveQuery hook enables you to reactively receive updates to the results of a live query change. It wraps the .live.query() API.

It has the interface:

ts
function useLiveQuery<T = { [key: string]: unknown }>(
  query: string | WatchSource<string>,
  params?: QueryParams | WatchSource<QueryParams> | WatchSource[],
): LiveQueryResults<T>

And its arguments, which can also be watch sources that will trigger a re-run, are:

  1. the SQL query
  2. optional parameters for the query
vue
<script lang="ts">
import { useLiveQuery } from '@electric-sql/pglite-vue'

const maxNumber = 100
const items = useLiveQuery(
  `
    SELECT *
    FROM my_table
    WHERE number <= $1
    ORDER BY number;
  `,
  [maxNumber],
)
</script>

<template>
  <MyItem v-for="item in items" :item="item" :key="item.id" />
</template>

useLiveQuery.sql

Similarly to how you can use [ sql``](../api.md#sql) to to construct SQL queries through string templating, you can do that with useLiveQuery.sql`` for the hook equivalent. See the templating section of the API for more details.

vue
<script lang="ts">
import { useLiveQuery } from '@electric-sql/pglite-vue'

const maxNumber = 100
const items = useLiveQuery.sql`
  SELECT *
  FROM my_table
  WHERE number <= ${maxNumber}
  ORDER BY number;
`
</script>
// ...

useLiveIncrementalQuery

The useLiveIncrementalQuery hook enables you to reactively receive updates whenever the results of a live query change. It wraps the .live.incrementalQuery() API, which provides a way to efficiently diff the query results in Postgres.

It has the interface:

ts
export function useLiveIncrementalQuery<T = { [key: string]: unknown }>(
  query: string | WatchSource<string>,
  params: QueryParams | WatchSource<QueryParams> | WatchSource[],
  key: string | WatchSource<string>,
): LiveQueryResults<T>

And its arguments, which can also be watch sources that will trigger a re-run, are:

  1. the SQL query
  2. optional parameters for the query
  3. the name of the column to key the diff algorithm on
vue
<script lang="ts">
import { useLiveIncrementalQuery } from '@electric-sql/pglite-vue'

const maxNumber = 100
const items = useLiveIncrementalQuery(
  `
    SELECT *
    FROM my_table
    WHERE number <= $1
    ORDER BY number;
  `,
  [maxNumber],
  'id',
)
</script>

<template>
  <MyItem v-for="item in items" :item="item" :key="item.id" />
</template>

File: docs/docs/about.md

What is PGlite

PGlite is a WASM Postgres build packaged into a TypeScript/JavaScript client library, that enables you to run Postgres in the browser, Node.js and Bun, with no need to install any other dependencies. It's under 3mb Gzipped, and has support for many Postgres extensions, including pgvector.

Getting started with PGlite is simple: just install and import the NPM package, then create your embedded database:

js
import { PGlite } from '@electric-sql/pglite'

const db = new PGlite()
await db.query("select 'Hello world' as message;")
// -> { rows: [ { message: "Hello world" } ] }

It can be used as an ephemeral in-memory database, or with persistence either to the file system (Node/Bun), or IndexedDB (browser).

Unlike previous "Postgres in the browser" projects, PGlite does not use a Linux virtual machine - it is simply Postgres in WASM.

It's being developed by Electric for our use case of embedding into applications, either locally or at the edge, allowing users to sync a subset of their server-side Postgres database.

However, there are many more use cases for PGlite beyond its use as an embedded application database:

  • Unit and CI testing
    PGlite is very fast to start and tear down. It's perfect for unit tests - you can have a unique fresh Postgres for each test.

  • Local development
    You can use PGlite as an alternative to a full local Postgres for development; simplifying your development environments.

  • Remote development, or local web containers
    As PGlite is so lightweight it can be easily embedded into remote containerised development environments, or in-browser web containers.

  • On-device or edge AI and RAG
    PGlite has full support for pgvector, enabling a local or edge retrieval augmented generation (RAG) workflow.

We are very keen to establish PGlite both as an open source, and open contribution, project, working to build a community around it, so as to develop its capabilities for all use cases.

Read more in our getting started guide.


File: docs/docs/api.md


outline: [2, 3]

PGlite API

Main Constructor

The main constructor is imported as:

ts
import { PGlite } from '@electric-sql/pglite'

The preferred way to create a PGlite instance is with the PGlite.create() static method that returns a promise, resolving to the new PGlite instance.

await PGlite.create(dataDir: string, options: PGliteOptions)
await PGlite.create(options: PGliteOptions)

There are a couple of advantages to using the static method:

  • This awaits the .waitReady promise, ensuring that the database has been fully initialised.
  • When using TypeScript and extensions, the returned PGlite instance will have the extensions namespace on its type. This is not possible with the standard constructor due to TypesScript limitations.

A new PGlite instance can also be created using the new PGlite() constructor.

new PGlite(dataDir: string, options: PGliteOptions)
new PGlite(options: PGliteOptions)

`dataDir`

Path to the directory for storing the Postgres database. You can provide a URI scheme for various storage backends:

  • file:// or unprefixed
    File system storage, available in Node and Bun.
  • idb://
    IndexedDB storage, available in the browser.
  • memory://
    In-memory ephemeral storage, available in all platforms.

`options`

  • dataDir?: string
    The directory in which to store the Postgres database when not provided as the first argument.

  • debug?: 1-5
    the Postgres debug level. Logs are sent to the console.

  • relaxedDurability?: boolean
    Under relaxed durability mode, PGlite will not wait for flushes to storage to complete after each query before returning results. This is particularly useful when using the IndexedDB file system.

  • fs?: Filesystem
    The alternative to providing a dataDir with a filesystem prefix is to initialise a Filesystem yourself and provide it here. See Filesystems

  • loadDataDir?: Blob | File
    A tarball of a PGlite datadir to load when the database starts. This should be a tarball produced from the related .dumpDataDir() method.

  • extensions?: Extensions
    An object containing the extensions you wish to load.

  • username?: string
    The username of the user to connect to the database as. Permissions will be applied in the context of this user.

  • database?: string
    The database from the Postgres cluster within the dataDir to connect to.

  • initialMemory?: number
    The initial amount of memory in bytes to allocate for the PGlite instance. PGlite will grow the memory automatically, but if you have a particularly large database you can set this higher to prevent the pause during memory growth.

  • pgliteWasmModule?: WebAssembly.Module
    A precompiled pglite WASM module to use instead of downloading the default version, or when using a bundler that either can, or requires, loading the WASM module with a ESM import.

  • initdbWasmModule?: WebAssembly.Module
    A precompiled initdb WASM module to use instead of downloading the default version, or when using a bundler that either can, or requires, loading the WASM module with a ESM import.

  • fsBundle?: Blob | File
    A filesystem bundle to use instead of downloading the default version. This is useful if in a restricted environment such as an edge worker.

  • parsers: ParserOptions
    An object of type { [pgType: number]: (value: string) => any; } mapping Postgres data type IDs to parser functions. For convenience, the pglite package exports a constant for most common Postgres types.

    ts
    import { PGlite, types } from '@electric-sql/pglite'
    
    const pg = await PGlite.create({
      parsers: {
        [types.TEXT]: (value) => value.toUpperCase(),
      },
    })
  • serializers: SerializerOptions
    An object of type { [pgType: number]: (value: any) => string; } mapping Postgres data type IDs to serializer functions.

    ts
    import { PGlite, types } from '@electric-sql/pglite'
    
    const pg = await PGlite.create({
      serializers: {
        [types.NUMERIC]: (value) => value.toString(),
      },
    })
  • startParams?: string[]
    An array of strings that will be passed to the Postgres process as start parameters. This is the set of parameters one would pass to a native PostgreSQL instance.

    ts
    import { PGlite } from '@electric-sql/pglite'
    
    const pg = await PGlite.create({
      startParams: [
        ...PGlite.defaultStartParams,
        '-c',
        'application_name=My awesome backend',
      ],
    })
  • initDbStartParams?: string[]
    An array of strings that will be passed to the initdb process as start parameters. This is the set of parameters one would pass to a native initdb exe.

    ts
    import { PGlite } from '@electric-sql/pglite'
    
    const pg = await PGlite.create({
      initDbStartParams: ['--locale-provider=icu', '--icu-locale=de'],
    })
  • postgresqlconf?: string[] | string
    A postgresql.conf file as an array of strings or a single string. You can use it to pass parameters to the PostgreSQL process. See official documentation for details.

`options.extensions`

PGlite and Postgres extensions are loaded into a PGLite instance on start, and can include both a WASM build of a Postgres extension and/or a PGlite client plugin.

The options.extensions parameter is an object of namespace: extension parings. The namespace is used to expose the PGlite client plugin included in the extension. An example of this is the live queries extension.

ts
import { PGlite } from '@electric-sql/pglite'
import { live } from '@electric-sql/pglite/live'
import { vector } from '@electric-sql/pglite-pgvector'

const pg = await PGlite.create({
  extensions: {
    live, // Live query extension, is a PGlite client plugin
    vector, // Postgres pgvector extension
  },
})

// The `live` namespace is added by the use of the
// `live` key in the `extensions` object.
pg.live.query('...')

For information on how to develop a PGlite extension see Extension Development.

Methods

query

.query<T>(query: string, params?: any[], options?: QueryOptions): Promise<Results<T>>

Execute a single statement, optionally with parameters.

Uses the extended query Postgres wire protocol.

Returns single result object.

Example

ts
await pg.query('INSERT INTO test (name) VALUES ($1);', ['test'])
// { affectedRows: 1 },

Query Options

The query and exec methods take an optional options objects with the following parameters:

  • rowMode: "object" | "array"
    The returned row object type, either an object of fieldName: value mappings or an array of positional values. Defaults to "object".
  • parsers: ParserOptions
    An object mapping Postgres data type IDs to parser functions. This option overrides any parsers set at the instance level.
    ts
    import { types } from '@electric-sql/pglite'
    await pg.query(`SELECT * FROM test WHERE name = $1;`, ['test'], {
      parsers: {
        [types.TEXT]: (value) => value.toUpperCase(),
      },
    })
  • serializers: SerializerOptions
    An object mapping Postgres data type IDs to serializer functions. This option overrides any serializers set at the instance level.
    ts
    import { types } from '@electric-sql/pglite'
    await pg.query(`INSERT INTO test (numeric) VALUES ($1);`, [100n], {
      serializers: {
        [types.NUMERIC]: (value: number | bigint) => value.toString(),
      },
    })
  • blob: Blob | File
    Attach a Blob or File object to the query that can used with a COPY FROM command by using the virtual /dev/blob device, see importing and exporting.

sql

.sql<T>``: Promise<Results<T>>

.sql<T>(strings: TemplateStringsArray, ...params: any[]): Promise<Results<T>>

You can also use the [ sql``](#tagged-template-queries) tagged template literal to create queries:

ts
await pg.sql`SELECT * FROM test WHERE name = ${'test'}`
// equivalent of pg.query('SELECT * FROM test WHERE name = $1', ['test'])

See the templating section for more details on how to use the tagged template literal along with various helpers for more complex cases.

exec

.exec(query: string, options?: QueryOptions): Promise<Array<Results>>

Execute one or more statements. (note that parameters are not supported)

This is useful for applying database migrations, or running multi-statement SQL that doesn't use parameters.

Uses the simple query Postgres wire protocol.

Returns array of result objects; one for each statement.

Example

ts
await pg.exec(`
  CREATE TABLE IF NOT EXISTS test (
    id SERIAL PRIMARY KEY,
    name TEXT
  );
  INSERT INTO test (name) VALUES ('test');
  SELECT * FROM test;
`)
// [
//   { affectedRows: 0 },
//   { affectedRows: 1 },
//   {
//     rows: [
//       { id: 1, name: 'test' }
//     ]
//     affectedRows: 0,
//     fields: [
//       { name: 'id', dataTypeID: '23' },
//       { name: 'name', dataTypeID: '25' },
//     ]
//   }
// ]

transaction

.transaction<T>(callback: (tx: Transaction) => Promise<T>)

To start an interactive transaction, pass a callback to the transaction method. It is passed a Transaction object which can be used to perform operations within the transaction.

The transaction will be committed when the promise returned from your callback resolves, and automatically rolled back if the promise is rejected.

`Transaction` objects

  • tx.query<T>(query: string, params?: any[], options?: QueryOptions): Promise<Results<T>>
    The same as the main .query method.
  • tx.sql<T>``: Promise<Results<T>>
    The same as the main .sql template string method.
  • tx.exec(query: string, options?: QueryOptions): Promise<Array<Results>>
    The same as the main .exec method.
  • tx.rollback()
    Rollback and close the current transaction.

Example

ts
await pg.transaction(async (tx) => {
  await tx.query(
    'INSERT INTO test (name) VALUES ('$1');',
    [ 'test' ]
  );
  return await ts.query('SELECT * FROM test;');
});

close

.close(): Promise<void>

Close the database, ensuring it is shut down cleanly.

listen

.listen(channel: string, callback: (payload: string) => void): Promise<(() => Promise<void>>

Subscribe to a pg_notify channel. The callback will receive the payload from the notification.

Returns an unsubscribe function to unsubscribe from the channel.

Example

ts
const unsub = await pg.listen('test', (payload) => {
  console.log('Received:', payload)
})
await pg.query("NOTIFY test, 'Hello, world!'")

Channel names are case sensitive if double-quoted (pg.listen('"TeST"')). Otherwise channel name will be lower cased (pg.listen('TeStiNG') == pg.listen('testing')).

unlisten

.unlisten(channel: string, callback?: (payload: string) => void): Promise<void>

Unsubscribe from the channel. If a callback is provided it removes only that callback from the subscription. When no callback is provided, it unsubscribes all callbacks for the channel.

onNotification

onNotification(callback: (channel: string, payload: string) => void): () => void

Add an event handler for all notifications received from Postgres.

Note: This does not subscribe to the notification; you will need to manually subscribe with LISTEN channel_name.

offNotification

offNotification(callback: (channel: string, payload: string) => void): void

Remove an event handler for all notifications received from Postgres.

dumpDataDir

dumpDataDir(compression?: 'auto' | 'gzip' | 'none'): Promise<File | Blob>

Dump the Postgres datadir to a Gzipped tarball.

The compression option defaults to auto which uses compression where possible. You can explicit opt in or out of compression with gzip and none. When you specify that compression is required with gzip, if the environment doesn't support a suitable compression API it will throw an error.

This can then be used in combination with the loadDataDir option when starting PGlite to load a dumped database from storage.

::: tip NOTE

The datadir dump may not be compatible with other Postgres versions; it is only designed for importing back into PGlite.

:::

execProtocol

execProtocol(message: Uint8Array, options?: ExecProtocolOptions): Promise<Array<[BackendMessage, Uint8Array]>>

Execute a Postgres wire protocol message, returning an array of tuples, one for each wire protocol result message, consisting of:

  1. The passed message object - see pg-protocol
  2. The raw Uint8Array for that message.

This API is safe to use alongside the other PGlite query APIs as it handles error, transactions and notifications.

execProtocolRaw

execProtocolRaw(message: Uint8Array, options?: ExecProtocolOptions): Promise

Execute a Postgres wire protocol message, returning the unparsed result Uint8Array, this includes all wire protocol result messages emitted as a result of your message and will require external passing. This is the lowest level API exposed by PGlite and can be used to interact with a PGlite database using existing Postgres clients. It is likely that you will want to use something such as pg-gateway that uses this internally to expose the database on a TCP socket.

::: warning WARNING

execProtocolRaw bypasses PGlite's protocol wrappers that manage error/notice messages,
transactions, and notification listeners. Only use if you need to bypass these wrappers and don't intend to use the above features. execProtocol is a safer alternative.

:::

execProtocolRawStream(message: Uint8Array, options: ExecProtocolOptionsStream): void
Same as execProtocolRaw but returns raw messages in the options.onRawData callback as they arrive from the backend. This is particularly useful when expecting large sets of data.
See a usage example in the pglite-socket project, where the server uses this method to pass the results to the client socket as they arrive.

describeQuery

.describeQuery(query: string, options?: QueryOptions): Promise<DescribeQueryResult>

Get type information about a query's parameters and result fields without executing it. This is useful for understanding the expected parameter types and the structure of the results that would be returned. It can be used to build a type inference system for queries using PGlite.

Returns an object with:

  • queryParams: Array<{ dataTypeID: number, serializer: Function }>
    Information about each parameter's Postgres type ID and the serializer function used to convert JavaScript values to Postgres format.

  • resultFields: Array<{ name: string, dataTypeID: number, parser: Function }>
    Information about each result field including its name, Postgres type ID, and the parser function used to convert Postgres values to JavaScript format.

refreshArrayTypes

.refreshArrayTypes(): Promise<void>

Refresh the array types in the database. This is useful when you have added columns that contain array types (ie. array of enums) and need to update the internal array type cache. This is done automatically when the database is started, but can be called manually if needed.

Example

ts
await pg.describeQuery('SELECT * FROM test WHERE name = $1', ['test'])

// returns:
{
  queryParams: [{ dataTypeID: 25, serializer: Function }],
  resultFields: [{ name: "id", dataTypeID: 23, parser: Function }],
}

clone

.clone(): Promise

Clones the current instance. This is useful when a series of operations, like unit or integration test, need to be run on the same database without having to recreate the database each time, or for each test.

copyToFS

.copyToFS(filePath: string, data: Uint8Array, mode?: number)

Copies the data to a file at filePath in the virtual filesystem and sets the optional mode of the file.

Example

ts
await pg.copyToFS(
  '/tmp/test.txt',
  new TextEncoder().encode('PGlite says hi!'),
  0o0644,
)

Properties

ready

.ready boolean (read only)

Whether the database is ready to accept queries.

closed

.closed boolean (read only)

Whether the database is closed and no longer accepting queries.

waitReady

.waitReady Promise<void>

Promise that resolves when the database is ready to use.

::: tip NOTE

Query methods will wait for the waitReady promise to resolve if called before the database has fully initialised, and so it is not necessary to wait for it explicitly.

:::

`Results` Objects

Result objects have the following properties:

  • rows: Row<T>[]
    The rows returned by the query.

  • affectedRows?: number
    Count of the rows affected by the query. Note, this is not the count of rows returned, it is the number or rows in the database changed by the query.

  • command?: string
    The command of the query that was run, taken from the command tag reported by Postgres, e.g. SELECT, INSERT or CREATE. Matches the command field of node-postgres query results.

  • rowCount?: number
    The number of rows reported by the command tag, e.g. the rows returned by a SELECT or changed by an UPDATE. Not set when the tag carries no count (e.g. CREATE TABLE). Unlike affectedRows this is per statement and not cumulative; it matches the rowCount field of node-postgres query results.

  • fields: { name: string; dataTypeID: number }[]
    Field name and Postgres data type ID for each field returned.

  • blob?: Blob
    A Blob containing the data written to the virtual /dev/blob/ device by a COPY TO command. See /dev/blob.

`Row` Objects

Rows objects are a key / value mapping for each row returned by the query.

The .query<T>() method can take a TypeScript type describing the expected shape of the returned rows.

::: tip NOTE

These types are not validated at run time, the result is only cast to the provided type.

:::

Tagged Template Queries

PGlite has support for using tagged template literals to construct SQL queries via the .sql`` method on both the main PGlite instance and Transaction objects. Substituted template values are automatically converted to query parameters, and you receive the same results as calling the query API directly.

ts
const name = getName()
await pg.sql`SELECT * FROM test WHERE name = ${name}`

There are helpers in the template export to create more complex and parametrizeable templated queries:

  • identifier``
    Tag identifiers like column and table names to escape them with quotes and prevent them from becoming parameters.
  • raw``
    Tag any string that you want to avoid being interpreted as a parameter. Will allow you to still do regular string templating.
  • sql``
    Tag nested templated literals to preserve behaviour and parametrization. Will allow you to create reusable templating utilities
  • query``
    Use at the top level tag in order to generate the parametrized query without passing it to the query API. Returns a { query: string, params: any[] } object with the parametrized query and any parameters used in the query.

If you require additional configurations or complex binary parameters it's best to use the query method.

Example

ts
import { identifier, raw, sql, query } from '@electric-sql/pglite/template'

await pg.sql`SELECT * FROM ${identifier`test`} WHERE name = ${'test'}`
// equivalent of pg.query('SELECT * FROM "test" WHERE name = $1', ['test'])

const filterStmt = (filterVar?: string) =>
  filterVar ? sql`WHERE name = ${filterVar}` : raw`WHERE 1=1`

await pg.sql`SELECT * FROM test ${filterStmt('test')}`
// equivalent of pg.query('SELECT * FROM "test" WHERE name = $1', ['test'])
await pg.sql`SELECT * FROM test ${filterStmt(null)}`
// equivalent of pg.query('SELECT * FROM "test" WHERE 1=1, [])

query`SELECT * FROM ${identifier`test`} WHERE name = ${'test'}`
// { query: 'SELECT * FROM "test" WHERE name = $1', params: ['test'] }

/dev/blob

PGlite has support for importing and exporting via the SQL COPY TO/FROM command by using a virtual /dev/blob device.

To import a file, pass the File or Blob in the query options as blob, and copy from the /dev/blob device.

ts
await pg.query("COPY my_table FROM '/dev/blob';", [], {
  blob: MyBlob,
})

To export a table or query to a file, you just need to write to the /dev/blob device; the file will be returned as blob on the query results:

ts
const ret = await pg.query("COPY my_table TO '/dev/blob';")
// ret.blob is a `Blob` object with the data from the copy.

File: docs/docs/bundler-support.md

Bundler Support

Some bundlers require additional configuration to work with PGlite.

:::tip

If you come across any issues with PGlite and a specific bundler, please open an issue, we'd also love any contributions to this bundler documentation if you're able to help out.

:::

Vite

When using Vite, make sure to exclude pglite from dependency optimization using the optimizeDeps option inside vite.config.js:

js
import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    exclude: ['@electric-sql/pglite'],
  },
})

Additional configuration for the Multi-tab Worker

When using the Multi-tab Worker, you might encounter errors during a production build related to workers being bundle in iife format, to resolve this modify the worker.format option in vite.config.js to 'es' (the default is 'iife')

ts
import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    exclude: ['@electric-sql/pglite'],
  },
  worker: {
    format: 'es',
  },
})

When importing the worker in your script, you can use the recommended ?worker import method from Vite:

ts
import PGWorker from './worker.js?worker'

export const pglite = new PGliteWorker(
  new PGWorker({
    type: 'module',
      name: 'pglite-worker',
    }),
    {
      // ...your options here
    }
  },
)

esbuild

esbuild does not support new URL('./file', import.meta.url) pattern that PGlite uses to locate its WebAssembly and data files. This means the automatic file resolution won't work out of the box.

Workaround: manually provide `pgliteWasmModule`, `initdbWasmModule` and `fsBundle`

  1. Copy pglite.wasm, initdb.wasm and pglite.data from node_modules/@electric-sql/pglite/dist/ to your public/build directory so your web server can serve them.

  2. Pass them manually when creating a PGlite instance:

ts
import { PGlite } from '@electric-sql/pglite'

const [pgliteWasmModule, initdbWasmModule, fsBundle] = await Promise.all([
  WebAssembly.compileStreaming(fetch('/pglite.wasm')),
  WebAssembly.compileStreaming(fetch('/initdb.wasm')),
  fetch('/pglite.data').then((response) => response.blob()),
])

const db = await PGlite.create({
  pgliteWasmModule,
  initdbWasmModule,
  fsBundle,
})

Alternatively, you can use an esbuild plugin like @chialab/esbuild-plugin-meta-url to handle new URL() imports automatically.

Next.js

When using Next.js, make sure to add @electric-sql/pglite to the transpilePackages array in next.config.js:

js
const nextConfig = {
  swcMinify: false,
  transpilePackages: [
    '@electric-sql/pglite-react', // Optional
    '@electric-sql/pglite',
  ],
}

export default nextConfig

File: docs/docs/filesystems.md

Filesystems

PGlite has a virtual file system layer that allows it to run in environments that don't traditionally have filesystem access.

PGlite VFSs are under active development, and we plan to extend the range of options in future, as well as make it easy for users to create their own filesystems.

We would recommend using the IndexedDB VFS in the browser at the current time as the OPFS VFS is not supported by Safari.

In-memory FS

The in-memory FS is the default when starting PGlite, and it is available on all platforms. All files are kept in memory and there is no persistence, other than calling pg.dumpDataDir() and then using the loadDataDir option at start.

To use the in-memory FS you can use one of these methods:

  • Don't provide a dataDir option
    ts
    const pg = new PGlite()
  • Set the dataDir to memory://
    ts
    const pg = new PGlite('memory://')
  • Import and pass the FS explicitly
    ts
    import { MemoryFS } from '@electric-sql/pglite'
    const pg = new PGlite({
      fs: new MemoryFS(),
    })

Platform Support

Node Bun Deno Chrome Safari Firefox
โœ“ โœ“ โœ“ โœ“ โœ“ โœ“

Node FS

The Node FS uses the Node.js file system API to implement a VFS for PGLite. It is available in both Node and Bun.

To use the Node FS you can use one of these methods:

  • Set the dataDir to a directory on your filesystem
    ts
    const pg = new PGlite('./path/to/datadir/')
  • Import and pass the FS explicitly
    ts
    import { NodeFS } from '@electric-sql/pglite'
    const pg = new PGlite({
      fs: new NodeFS('./path/to/datadir/'),
    })

Platform Support

Node Bun Deno Chrome Safari Firefox
โœ“ โœ“ โœ“

IndexedDB FS

The IndexedDB FS persists the database to IndexedDB in the browser. It's a layer over the in-memory filesystem, loading all files for the database into memory on start, and flushing them to IndexedDB after each query if they have changed.

To use the IndexedDB FS you can use one of these methods:

  • Set the dataDir with a idb:// prefix, the database will be stored in an IndexedDB named with the path provided
    ts
    const pg = new PGlite('idb://my-database')
  • Import and pass the FS explicitly
    ts
    import { IdbFs } from '@electric-sql/pglite'
    const pg = new PGlite({
      fs: new IdbFs('my-database'),
    })

The IndexedDB filesystem works at the file level, storing whole files (Postgres has a single file per table or index) as blobs in IndexedDB. Flushing whole files can take a few milliseconds after each query. To aid in building responsive apps we provide a relaxedDurability mode that can be configured when starting PGlite. Under this mode, the results of a query are returned immediately, and the flush to IndexedDB is scheduled to occur asynchronously afterwards. Typically, this is immediately after the query returns with no delay.

Platform Support

Node Bun Deno Chrome Safari Firefox
โœ“ โœ“ โœ“

OPFS AHP FS

The OPFS AHP filesystem is built on top of the Origin Private Filesystem in the browser and uses an "access handle pool". It is only available when PGlite is run in a Web Worker, this could be any worker you configure. We provide a Multi Tab Worker to aid in using PGlite from multiple tabs in the browser.

To use the OPFS AHP FS you can use one of these methods:

  • Set the dataDir to a directory within the origins OPFS
    ts
    const pg = new PGlite('opfs-ahp://path/to/datadir/')
  • Import and pass the FS explicitly
    ts
    import { OpfsAhpFS } from '@electric-sql/pglite/opfs-ahp'
    const pg = new PGlite({
      fs: new OpfsAhpFS('./path/to/datadir/'),
    })

Platform Support

Node Bun Deno Chrome Safari Firefox
โœ“ โœ“

Unfortunately, Safari appears to have a limit of 252 open sync access handles, this prevents this VFS from working due to a standard Postgres install consisting of over 300 files.

What is an "access handle pool"?

The Origin Private Filesystem API provides both asynchronous and synchronous methods, but the synchronous methods are limited to read, write and flush. You are unable to traverse the filesystem or open files synchronously. PGlite is a fully synchronous WASM build of Postgres and unable to call async APIs while handling a query. While it is possible to build an async WASM Postgres using Asyncify, it adds significant overhead in both file size and performance.

To overcome these limitations, and to provide a fully synchronous file system to PGlite on top of OPFS, we use something called an "access handle pool". When you first start PGlite we open a pool of OPFS access handles with randomised file names; these are then allocated to files as needed. After each query, a pool maintenance job is scheduled that maintains its size. When you inspect the OPFS directory where the database is stored, you will not see the normal Postgres directory layout, but rather a pool of files and a state file containing the directory tree mapping along with file metadata.

The PGlite OPFS AHP FS is inspired by the wa-sqlite access handle pool file system by Roy Hashimoto.


File: docs/docs/index.md

Getting started with PGlite

PGlite can be used in both Node/Bun/Deno or the browser, and with any JavaScript framework.

Install and start in Node/Bun/Deno

Install into your project:

::: code-group

bash [npm]
npm install @electric-sql/pglite
bash [pnpm]
pnpm install @electric-sql/pglite
bash [yarn]
yarn add @electric-sql/pglite
bash [bun]
bun install @electric-sql/pglite
bash [deno]
deno add npm:@electric-sql/pglite

:::

To use the in-memory Postgres:

js
import { PGlite } from '@electric-sql/pglite'

const db = new PGlite()

or to persist to the native filesystem:

js
const db = new PGlite('./path/to/pgdata')

Install and start in the browser

It can be installed and imported using your usual package manager:

js
import { PGlite } from '@electric-sql/pglite'

or using a CDN such as JSDeliver:

js
import { PGlite } from 'https://cdn.jsdelivr.net/npm/@electric-sql/pglite/dist/index.js'

Then for an in-memory Postgres:

js
const db = new PGlite()

or to persist the database to IndexedDB:

js
const db = new PGlite('idb://my-pgdata')

Making a query

There are two methods for querying the database, .query and .exec. The former supports parameters, while the latter supports multiple statements.

First, let's create a table and insert some test data using the .exec method:

js
await db.exec(`
  CREATE TABLE IF NOT EXISTS todo (
    id SERIAL PRIMARY KEY,
    task TEXT,
    done BOOLEAN DEFAULT false
  );
  INSERT INTO todo (task, done) VALUES ('Install PGlite from NPM', true);
  INSERT INTO todo (task, done) VALUES ('Load PGlite', true);
  INSERT INTO todo (task, done) VALUES ('Create a table', true);
  INSERT INTO todo (task, done) VALUES ('Insert some data', true);
  INSERT INTO todo (task) VALUES ('Update a task');
`)

The .exec method is perfect for migrations and batch inserts with raw SQL.

Now, let's retrieve an item using .query method:

js
const ret = await db.query(`
  SELECT * from todo WHERE id = 1;
`)
console.log(ret.rows)

Output:

js
;[
  {
    id: 1,
    task: 'Install PGlite from NPM',
    done: false,
  },
]

Using parameterised queries

When working with user supplied values, it's always best to use parameterized queries; these are supported on the .query method.

We can use this to update a task:

js
const ret = await db.query(
  'UPDATE todo SET task = $2, done = $3 WHERE id = $1',
  [5, 'Update a task using parameterized queries', true],
)

What next?

  • To learn more about querying and transactions along with the other methods and options available, you can read the main PGlite API documentation.

  • There is also a live-query extension that enables reactive queries to update a UI when the underlying database changes.

  • PGlite has a number of built-in virtual file systems to provide persistence for your database.

  • There are framework hooks to make working with PGlite within React and Vue much easier with less boilerplate.

  • For help configuring PGlite with your bundler, see the bundler support page.

  • As PGlite only has a single exclusive connection to the database, we provide a multi-tab worker to enable sharing a PGlite instance between multiple browser tabs.

  • There is a REPL component that can be easily embedded into a web-app to aid in debugging and development, or as part of a database application itself.

  • We maintain a list of ORMs and query builders that support PGlite.

  • PGlite supports both Postgres extensions and PGlite Plugins via its extensions API, and there is a list of supported extensions.

  • We have a page of examples that you can open to test out PGlite in the browser.


File: docs/docs/live-queries.md

Live Queries

The "live" extension enables you to subscribe to a query and receive updated results when the underlying tables change.

To use the extension, it needs to be added to the PGlite instance when creating it:

ts
import { PGlite } from '@electric-sql/pglite'
import { live } from '@electric-sql/pglite/live'

const pg = await PGlite.create({
  extensions: {
    live,
  },
})

There are three methods on the live namespace:

  • live.query() for basic live queries. With less machinery in PGlite, it's quicker for small results sets and narrow rows.
  • live.incrementalQuery() for incremental queries. It materialises the full result set on each update from only the changes emitted by the live.changes API. Perfect for feeding into React, and with good performance for large result sets and wide rows.
  • live.changes() a lower level API that emits the changes (insert/update/delete) that can then be mapped to mutations in a UI or other datastore.

live.query

live.query<T>()

This is very similar to a standard query, but takes an additional callback that receives the results whenever they change:

js
const ret = pg.live.query('SELECT * FROM test ORDER BY rand;', [], (res) => {
  // res is the same as a standard query result object
})

You can also pass an options object with offset and limit to implement efficient windowed queries:

js
const ret = pg.live.query({
  query: 'SELECT * FROM test ORDER BY rand;',
  offset: 0,
  limit: 10,
  callback: (res) => {
    // res includes:
    // - rows: the rows for the current window
    // - offset: the current offset
    // - limit: the current limit
    // - totalCount: total number of rows in the full result set
  },
})

The windowed query is optimized to:

  • Only transfer the rows within the requested window
  • Efficiently update when rows enter/leave the window
  • Track the total count separately to avoid slow COUNT(*) queries blocking updates

You can update the window position by calling refresh with new offset/limit values:

js
// Move to page 2
await ret.refresh(10, 10)

The returned value from the call is a Promise of an object with this interface:

ts
interface LiveQueryReturn<T> {
  initialResults: Results<T> & {
    offset?: number // Current offset (if windowed)
    limit?: number // Current limit (if windowed)
    totalCount?: number // Total row count (if windowed)
  }
  unsubscribe: () => Promise<void>
  refresh: (options?: { offset?: number; limit?: number }) => Promise<void>
}
  • initialResults is the initial results set (also sent to the callback)
  • unsubscribe allows you to unsubscribe from the live query
  • refresh allows you to force a refresh of the query with the updated results sent to the callback. For windowed queries you can optionally provide new offset/limit values.

Internally it watches the tables that the query depends on, and reruns the query whenever they are changed.

live.incrementalQuery

live.incrementalQuery<T>()

Similar to above, but maintains a temporary table of the previous state inside of Postgres. When the tables it depends on change, the query is re-run and diffed with the last state. Only the changes from the last version of the query are copied from WASM into JS.

It requires an additional key argument - the name of a column (often a primary key) on which to key the diff.

ts
const ret = pg.live.incrementalQuery(
  'SELECT * FROM test ORDER BY rand;',
  [],
  'id',
  (res) => {
    // res is the same as a standard query result object
  },
)

The returned value is of the same type as the query method above.

live.changes

live.changes()

A lower-level API which is the backend for the incrementalQuery, it emits the changes that have occurred. It requires a key on which to compare row differences:

ts
const ret = pg.live.changes(
  'SELECT * FROM test ORDER BY rand;',
  [],
  'id',
  (res) => {
    // res is a change result object
  },
)

The call returns a Promise of an object defined by this interface:

ts
interface LiveChangesReturn<T = { [key: string]: any }> {
  fields: { name: string; dataTypeID: number }[]
  initialChanges: Array<Change<T>>
  unsubscribe: () => Promise<void>
  refresh: () => Promise<void>
}

The results passed to the callback are an array of Change objects:

ts
type ChangeInsert<T> = {
  __changed_columns__: string[]
  __op__: 'INSERT'
  __after__: number
} & T

type ChangeDelete<T> = {
  __changed_columns__: string[]
  __op__: 'DELETE'
  __after__: undefined
} & T

type ChangeUpdate<T> = {
  __changed_columns__: string[]
  __op__: 'UPDATE'
  __after__: number
} & T

type Change<T> = ChangeInsert<T> | ChangeDelete<T> | ChangeUpdate<T>

Each Change includes the new values along with:

  • __changed_columns__ the column names that were changed.
  • __op__ the operation that is required to update the state (INSERT, UPDATE, DELETE).
  • __after__ the key of the row after which this row should be positioned; it will be included in __changed_columns__ if it has been changed. This allows for very efficient moves within an ordered set of results.

This API can be used to implement very efficient in-place DOM updates.


File: docs/docs/multi-tab-worker.md

Multi-tab Worker

It's likely that you will want to run PGlite in a Web Worker so that it doesn't block the main thread. Additionally, as PGlite is single connection only, you may want to proxy multiple browser tabs to a single PGlite instance.

To aid in this, we provide a PGliteWorker with the same API as the standard PGlite, and a worker wrapper that exposes a PGlite instance to other tabs.

Using PGliteWorker

First, you need to create a js file for your worker instance. You use the worker wrapper with an init option that returns a PGlite instance to start that database and expose it to all tabs:

js
// my-pglite-worker.js
import { PGlite } from '@electric-sql/pglite'
import { worker } from '@electric-sql/pglite/worker'

worker({
  async init() {
    // Create and return a PGlite instance
    return new PGlite()
  },
})

Then connect the PGliteWorker to your new worker process in your main script:

js
import { PGliteWorker } from '@electric-sql/pglite/worker'

const pg = new PGliteWorker(
  new Worker(new URL('./my-pglite-worker.js', import.meta.url), {
    type: 'module',
  }),
)

// `pg` has the same interface as a standard PGlite interface

Internally, this starts a worker for each tab, but then runs an a election to nominate one as the leader. Only the leader then starts PGlite by calling the init function, and handles all queries. When the leader tab is closed, a new election is run, and a new PGlite instance is started.

In addition to having all the standard methods of the PGlite interface, PGliteWorker also has the following methods and properties:

  • onLeaderChange(callback: () => void)
    This allows you to subscribe to a notification when the leader worker is changed. It returns an unsubscribe function.
  • offLeaderChange(callback: () => void)
    This allows you to unsubscribe from the leader change notification.
  • isLeader: bool
    A boolean property indicating if this instance is the leader.

Passing options to a worker

PGliteWorker takes an optional second parameter options; this can include any standard PGlite options along with these additional options:

  • id: string
    This is an optional id to group your PGlite workers. The leader election is run between all PGliteWorkers with the same id.
    If not provided, the url to the worker is concatenated with the dataDir option to create an id.
  • meta: any
    Any additional metadata you would like to pass to the worker process init function.

The worker() wrapper takes a single options argument, with a single init property. init is a function that takes any options passed to PGliteWorker, excluding extensions, and returns a PGlite instance. You can use the options passed to decide how to configure your instance:

js
// my-pglite-worker.js
import { PGlite } from '@electric-sql/pglite'
import { worker } from '@electric-sql/pglite/worker'

worker({
  async init(options) {
    const meta = options.meta
    // Do something with additional metadata.
    // or even run your own code in the leader along side the PGlite
    return new PGlite({
      dataDir: options.dataDir,
    })
  },
})

// my-app.js
import { PGliteWorker } from '@electric-sql/pglite/worker'

const pg = new PGliteWorker(
  new Worker(new URL('./my-pglite-worker.js', import.meta.url), {
    type: 'module',
  }),
  {
    dataDir: 'idb://my-db',
    meta: {
      // additional metadata passed to `init`
    },
  },
)

Extension support

PGliteWorker has support for both Postgres extensions and PGlite plugins using the normal extension api.

Any extension can be used by the PGlite instance inside the worker, however the extensions namespace is not exposed on a connecting PGliteWorker on the main thread.

js
// my-pglite-worker.js
import { PGlite } from '@electric-sql/pglite'
import { worker } from '@electric-sql/pglite/worker'
import { vector } from '@electric-sql/pglite-pgvector'

worker({
  async init() {
    return new PGlite({
      extensions: {
        vector,
      },
    })
  },
})

Extensions that only use the PGlite plugin interface, such as live queries, can be used on the main thread with PGliteWorker to expose their functionality; this is done by providing a standard options object as a second argument to the PGliteWorker constructor:

js
import { PGliteWorker } from '@electric-sql/pglite/worker'
import { live } from '@electric-sql/pglite/live'

const pg = new PGliteWorker(
  new Worker(new URL('./my-pglite-worker.js', import.meta.url), {
    type: 'module',
  }),
  {
    extensions: {
      live,
    },
  },
)

PGliteWorker also has a create static method that resolves to a new instance when it is fully initiated. This also adds the correct types for any extensions to the PGliteWorker instance:

ts
import { PGliteWorker } from '@electric-sql/pglite/worker'
import { live } from '@electric-sql/pglite/live'

const pg = await PGliteWorker.create(
  new Worker(new URL('./my-pglite-worker.js', import.meta.url), {
    type: 'module',
  }),
  {
    extensions: {
      live,
    },
  },
)

// TypeScript is aware of the `pg.live` namespace:
pg.live.query(/* ... */)