Skip to main content

SQLite Virtual File Systems

The SDK stores its local SQLite database through a virtual file system (VFS), the layer SQLite uses to read and write files. The VFS determines whether data persists, how tabs share data, and whether reads can run concurrently. For new apps that need to support a wide range of browsers, use vfs: 'auto'. It selects SimpleOpfsVFS, which supports multiple tabs across major browsers, when the Origin Private File System (OPFS) is available. If OPFS is unavailable, or you disable web workers, it uses an in-memory database that does not persist data. If your app primarily targets Safari, use OPFSCoopSyncVFS for now. If you omit vfs, the SDK uses IDBBatchAtomicVFS and stores data in IndexedDB. This remains the default for backward compatibility.

VFS Comparison

On smaller screens, scroll the table horizontally.
In this table, Multi-Tab means that tabs can access the same local database. It does not mean that they share a sync connection. The enableMultiTabs option separately controls whether the SDK uses shared workers for sync and, for some VFS options, database access. See Multiple Tab Support.
All OPFS options require useWebWorker: true (which is the default). OPFS access can fail in private browsing.
Before switching VFS implementations, upload pending mutations. Use a new database file name if the switch changes the storage format or location. The SDK does not migrate local data from the old VFS. After you connect the new database, the SDK downloads synced data again. Back up or migrate local-only data separately; it is not downloaded again.

Automatic VFS Selection

The 'auto' option is available in @powersync/web version 2.7.0 and later. Use it for new apps that need broad browser support. It selects SimpleOpfsVFS when OPFS is available and web workers are enabled. If your app primarily targets Safari, use OPFSCoopSyncVFS for now. Switching from SimpleOpfsVFS to 'auto' reuses the database if 'auto' selects that VFS and you keep the file name and storage location unchanged. The SDK does not migrate data from other VFS implementations. Example configuration:

SimpleOpfsVFS

WASQLiteVFS.SimpleOpfsVFS stores data in OPFS and supports multiple tabs across major browsers. The SDK selects a coordination strategy based on the browser’s capabilities. This VFS doesn’t support ATTACH for additional databases, and SQLite temporary data, such as materialized CTEs, always stays in memory. To select this VFS explicitly, import WASQLiteVFS and set database.vfs:

IDBBatchAtomicVFS (Default)

WASQLiteVFS.IDBBatchAtomicVFS stores data in IndexedDB. It is the default when you omit the vfs option. The SDK keeps this default for backward compatibility, so existing apps continue to open their IndexedDB databases without changing their configuration. Use this VFS if your app cannot use database workers and requires persistence. Unlike OPFS, IndexedDB is available without a database worker. See PowerSyncDatabase Flags for the related configuration. To select this VFS explicitly:

OPFSCoopSyncVFS

WASQLiteVFS.OPFSCoopSyncVFS stores data in OPFS and supports multiple tabs across major browsers, including Safari on iOS. Use this VFS if your app primarily targets Safari. On Safari, the database can fail to open after a page reloads or navigates away, because Safari doesn’t always release locks held by the previous page’s workers. Both this VFS and SimpleOpfsVFS can run into this issue, but SimpleOpfsVFS is more likely to. For broad browser support, use vfs: 'auto'. To select this VFS explicitly:

OPFSWriteAheadVFS

WASQLiteVFS.OPFSWriteAheadVFS stores data in OPFS and uses its own write-ahead log so reads can run while a write is in progress. The SDK opens additional read-only connections in separate workers, which lets multiple queries run at the same time. Use it if you need concurrent reads and only target Chromium-based browsers, such as Chrome or Edge. The additionalReaders option sets how many read-only connections the SDK opens in addition to the main connection. It defaults to 1. Increase it if your app often runs many read queries at once. Each additional reader uses more memory. This example opens two additional readers. When the main connection is idle, it can serve a third read query:

AccessHandlePoolVFS

WASQLiteVFS.AccessHandlePoolVFS stores data in OPFS but does not support sharing the database across tabs. It is retained for backward compatibility and is not recommended for new apps.

InMemoryVfs

WASQLiteVFS.InMemoryVfs stores database files in memory, avoiding the overhead of reading and writing them through OPFS or IndexedDB. Use it for development or online-only apps with small datasets when you don’t need persistence. It is available in @powersync/web version 1.39.0 and later. Starting with an empty database can help you reproduce issues from a clean state during development. If tabs share an in-memory database through a shared worker, closing the last tab using it clears the database. Otherwise, each tab has its own database, which is cleared when that tab closes or reloads. Any mutations that haven’t uploaded are lost when the database is cleared. A new in-memory database must download its synced data again after you connect, so consider how much data your app needs at startup.
In production, consider watching SELECT * FROM ps_crud LIMIT 1 to detect pending mutations and show users when the upload queue is not empty. A beforeunload listener can request confirmation before closing a tab with pending mutations, but it is not reliable on all platforms.
If you disable enableMultiTabs, only one database with a given name syncs at a time. Give each tab a unique database file name to let every tab sync independently:

Multiple Tab Support

Think of PowerSync’s multi-tab behavior as two separate layers:
  1. Local database access: The VFS determines whether tabs can access the same local database and how they coordinate that access.
  2. Sync connection: The enableMultiTabs option controls whether tabs share one sync connection through a shared worker. Without a shared worker, only one tab syncs at a time for a given database name.
PowerSync supports multi-tab database access on most desktop browsers. When shared-worker mode is enabled, the SDK creates a shared worker named shared-powersync-[dbFileName] to manage one sync connection for the tabs. The shared sync worker connects to the PowerSync Service and applies downloaded changes on behalf of all tabs. It calls the fetchCredentials and uploadData methods from the connector provided by the most recently registered tab that is still open. When that tab closes, the worker uses the connector from another registered tab.

Behavior Without Shared Workers

PowerSync does not use shared-worker mode when the browser does not support it, the SDK disables it by default for the platform, or you set enableMultiTabs: false. To disable it explicitly:
Without shared-worker mode, each tab manages its own sync implementation, but only one tab syncs at a time for a given database name. Whether tabs access the same local data still depends on the VFS. With InMemoryVfs, each tab has its own database. The SDK uses broadcast channels to share watched-query updates between tabs. Since version 2.1.0, it also shares sync status and Sync Stream subscriptions this way. This coordination is less reliable than using a shared worker, so updates may not reach every tab.

Multi-Threaded In-Memory SQLite Connection Pool

Since version 2.2.0, the @powersync/web package provides an experimental, per-tab in-memory SQLite connection pool for highly concurrent query workloads. It uses a design inspired from OPFSWriteAheadVFS, but relies on SharedArrayBuffer to coordinate an in-memory database instead of persisting to OPFS. It is available under a separate import and is thus configured via the opened option on PowerSyncDatabase instead of database.vfs.
This setup is experimental and might change in the future.
The pool creates numWorkers SQLite connections in dedicated web workers. One worker is the designated writer, while the remaining workers are used as additional readers. The writer can also serve reads while it is idle. Read-only transactions can run in parallel, including while the writer is writing. This helps when queries overlap, but it does not make an individual query faster. Additional workers increase initialization time and memory use. Each pool instance is independent and belongs to one tab. Its data is not persisted or shared across tabs, and the application cannot assign it a database filename. Opening or refreshing a tab creates a fresh database that must be resynced. Any local writes that have not been uploaded are lost when the tab closes. This option requires cross-origin isolation and browser support for growable SharedArrayBuffer. The connection pool cannot be created without them. This connection pool is primarily relevant when all of the following apply:
  1. You need to run read queries concurrently.
  2. Your dataset is small enough to download again whenever a tab opens.
  3. You don’t need persistence.
  4. You can enable cross-origin isolation by using the appropriate headers.
  5. You don’t need multiple tabs to share offline state.
Import the pool from @powersync/web/extra/shared-memory-pool and pass it through the opened option:

Managing OPFS Storage

The following utility functions can help you manage OPFS storage programmatically: