sqflite_common_ffi_web: sqflite in the browser
package:sqflite_common_ffi_web implements the sqflite DatabaseFactory on
the web with package:sqlite3 compiled to WebAssembly. Databases persist in
IndexedDB and SQLite runs in a shared worker (one instance for all tabs). It
needs two binary files served next to the app: sqlite3.wasm and the worker
script sqflite_sw.js, both produced by the package setup command.
dart pub add sqflite_common_ffi_web
dart run sqflite_common_ffi_web:setup # creates web/sqlite3.wasm and web/sqflite_sw.js
import 'package:sqflite_common/sqlite_api.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
Future<void> main() async {
Database db = await databaseFactoryFfiWeb.openDatabase('my_db.db');
await db.execute('CREATE TABLE IF NOT EXISTS Test (id INTEGER PRIMARY KEY, value TEXT)');
await db.insert('Test', {'value': 'hello'});
print(await db.query('Test'));
await db.close();
}
Guidelines
Dependency, binaries and import
- Add
sqflite_common_ffi_web (currently 1.1.x, Dart 3.12, platforms: web
only) to dependencies. It depends on sqflite_common_ffi and
sqflite_common.
- Run
dart run sqflite_common_ffi_web:setup in the project directory after
adding the dependency and after every upgrade of the package or of
sqlite3. It builds the shared worker with webdev (into
.dart_tool/sqflite_common_ffi_web/setup/<version>/) and writes
web/sqflite_sw.js plus web/sqlite3.wasm (downloaded from the
sqlite3.dart GitHub releases, version pinned by the package).
Options: --force (-f) rebuild, --dir <dir> output directory
(default web), --verbose, --no-sqlite3-wasm (skip the wasm
download), --sqlite3-wasm-url <url>, and an optional project path
argument. Commit the two files or add them to .gitignore and run setup in
CI; either way they must be present in the deployed site root.
- The same setup is callable from Dart:
setupSqfliteWebBinaries(options: SqfliteWebSetupOptions(...)) from package:sqflite_common_ffi_web/setup.dart
(path, dir, force, verbose, sqlite3WasmUri, noSqlite3Wasm,
sqlite3WasmFilename, sqfliteWebWorkerFilename). io only.
- Import
package:sqflite_common_ffi_web/sqflite_ffi_web.dart. It exports
only the web factories and options (databaseFactoryFfiWeb,
databaseFactoryFfiWebNoWebWorker, databaseFactoryFfiWebBasicWebWorker,
createDatabaseFactoryFfiWeb, SqfliteFfiWebOptions,
SqfliteFfiWebContext, sqfliteFfiWebLoadSqlite3Wasm,
sqfliteFfiWebStartSharedWorker, sqliteFfiWebDebugWebWorker). The
Database / DatabaseFactory types come from
package:sqflite_common/sqlite_api.dart (Dart web),
package:sqflite_common_ffi/sqflite_ffi.dart or package:sqflite/sqflite.dart
(Flutter), so import one of them too.
- The file compiles on io too (the getters throw
UnsupportedError there),
so importing it from shared code is fine as long as the factory is only
touched when kIsWeb / identical(0, 0.0) is true.
Choosing the factory
databaseFactoryFfiWeb (tag ffi_web): SQLite runs in a SharedWorker
loaded from sqflite_sw.js; all tabs of the origin share one SQLite
instance, so it is cross-tab safe. When SharedWorker is unavailable
(Android Chrome) it silently falls back to a dedicated Worker, which is
not cross-tab safe. This is the default choice.
databaseFactoryFfiWebNoWebWorker: loads sqlite3.wasm in the main
thread; no worker file needed, but long queries block the UI and several
tabs writing the same database can corrupt it. Use for tools, demos or
when workers cannot be served.
databaseFactoryFfiWebBasicWebWorker: testing only (forces a dedicated
Worker).
- The worker and wasm are loaded lazily on the first factory call from
sqflite_sw.js / sqlite3.wasm relative to the page URL. With a
non-root <base href> or another location, pass sharedWorkerUri /
sqlite3WasmUri through createDatabaseFactoryFfiWeb(options: SqfliteFfiWebOptions(...)) (see sqflite-common-ffi-web-options).
- Flutter app that already uses
sqflite on mobile: keep the code, set
databaseFactory = databaseFactoryFfiWeb; once at startup when kIsWeb,
before the first openDatabase. The global openDatabase() then works on
all platforms. Use sqflite_common_ffi (databaseFactoryFfi) for the
desktop counterpart.
Paths and persistence
getDatabasesPath() returns / and relative paths are used as is: a
database name such as my_db.db is a key in the virtual file system, not
a file. Do not build paths with path_provider on the web.
- Storage is an IndexedDB database named
sqflite_databases (override with
SqfliteFfiWebOptions.indexedDbName) holding every sqflite database of the
origin. IndexedDB is per origin including the port: localhost:8080 and
localhost:8081 are different stores, so always debug on the same port.
There is no OPFS backend.
inMemoryDatabasePath opens a private in-memory SQLite database inside
the worker (nothing stored).
- To import a bundled database (asset or download), write its bytes with
factory.writeDatabaseBytes(path, bytes) then openDatabase(path);
readDatabaseBytes(path) exports it. databaseExists, deleteDatabase
work on the virtual file system.
sqfliteFfiInit() from sqflite_common_ffi is a no-op on the web; calling
it in shared code is harmless.
Limitations
- Experimental: slower than native, larger message overhead (each call goes
through
postMessage), not fully tested.
deleteDatabase is reported as not working when the app itself is
compiled with dart2wasm (--wasm); the JS build is the tested path.
- Basic
Worker fallback (Android Chrome) is not cross-tab safe.
- Only one worker script name per site: the shared worker is keyed by its
URL, so after upgrading the package all tabs must reload. To force a
reload change the worker file name (see options skill,
sw_js_file).
Examples
Flutter app: web + mobile + desktop
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/widgets.dart';
// sqflite_ffi.dart exports the whole sqflite_common API (openDatabase,
// databaseFactory...); package:sqflite/sqflite.dart would be redundant.
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
if (kIsWeb) {
databaseFactory = databaseFactoryFfiWeb;
} else if (Platform.isWindows || Platform.isLinux) {
sqfliteFfiInit();
databaseFactory = databaseFactoryFfi;
}
// Global openDatabase() now works everywhere; on the web the path is a name.
var db = await openDatabase(
'app.db',
version: 1,
onCreate: (db, _) => db.execute(
'CREATE TABLE Note (id INTEGER PRIMARY KEY, text TEXT)',
),
);
await db.insert('Note', {'text': 'hello'});
runApp(const SizedBox());
}
Dart web app (no Flutter)
import 'package:sqflite_common/sqlite_api.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
Future<void> main() async {
var factory = databaseFactoryFfiWeb;
var db = await factory.openDatabase(
'counter.db',
options: OpenDatabaseOptions(
version: 1,
onCreate: (db, _) =>
db.execute('CREATE TABLE Counter (id INTEGER PRIMARY KEY, value INTEGER)'),
),
);
await db.rawInsert(
'INSERT INTO Counter(id, value) VALUES (1, 0) ON CONFLICT(id) DO UPDATE SET value = value + 1',
);
print(await db.query('Counter'));
await db.close();
}
Importing an asset database on Flutter web
import 'package:flutter/services.dart' show rootBundle;
import 'package:sqflite/sqflite.dart';
Future<Database> openBundledDatabase() async {
const path = 'bundled.db';
if (!await databaseFactory.databaseExists(path)) {
final data = await rootBundle.load('assets/bundled.db');
final bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await databaseFactory.writeDatabaseBytes(path, bytes);
}
return openDatabase(path, readOnly: true);
}
Main-thread factory for a small tool page
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
Future<String> sqliteVersion() async {
// Only sqlite3.wasm is required, no sqflite_sw.js.
var db = await databaseFactoryFfiWebNoWebWorker.openDatabase('tool.db');
try {
return (await db.rawQuery('SELECT sqlite_version()')).first.values.first
as String;
} finally {
await db.close();
}
}
Common mistakes
- Forgetting
dart run sqflite_common_ffi_web:setup: the first call fails
and the console prints "An error occurred while initializing the web
worker ... failure to find the worker javascript file at sqflite_sw.js".
- Serving the app from a sub path without adjusting
sharedWorkerUri /
sqlite3WasmUri, or a server that does not serve .wasm as
application/wasm.
- Upgrading
sqflite_common_ffi_web or sqlite3 without re-running setup
(--force if the files look up to date but the wasm version changed).
- Using
getDatabasesPath() + join or path_provider on the web; use a
plain name.
- Debugging on changing ports and "losing" the database.
- Setting
databaseFactory = databaseFactoryFfiWeb on io (throws
UnsupportedError): guard with kIsWeb.
- Importing only
sqflite_ffi_web.dart and expecting Database or
OpenDatabaseOptions to be defined; add package:sqflite_common/sqlite_api.dart
(or sqflite).
1---2name: sqflite-common-ffi-web-setup3description: Use when adding sqflite support to a web app (Flutter web or Dart web) with package:sqflite_common_ffi_web: running dart run sqflite_common_ffi_web:setup to install sqlite3.wasm and sqflite_sw.js under web/, databaseFactoryFfiWeb, databaseFactoryFfiWebNoWebWorker, setting the global databaseFactory behind kIsWeb, database names and IndexedDB persistence, shared worker vs basic worker, importing a database with writeDatabaseBytes, limitations and troubleshooting (missing worker file, port-bound storage, deleteDatabase).4---56# sqflite_common_ffi_web: sqflite in the browser78`package:sqflite_common_ffi_web` implements the sqflite `DatabaseFactory` on9the web with `package:sqlite3` compiled to WebAssembly. Databases persist in10IndexedDB and SQLite runs in a shared worker (one instance for all tabs). It11needs two binary files served next to the app: `sqlite3.wasm` and the worker12script `sqflite_sw.js`, both produced by the package `setup` command.1314```bash15dart pub add sqflite_common_ffi_web16dart run sqflite_common_ffi_web:setup # creates web/sqlite3.wasm and web/sqflite_sw.js17```1819```dart20import 'package:sqflite_common/sqlite_api.dart';21import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';2223Future<void> main() async {24 Database db = await databaseFactoryFfiWeb.openDatabase('my_db.db');25 await db.execute('CREATE TABLE IF NOT EXISTS Test (id INTEGER PRIMARY KEY, value TEXT)');26 await db.insert('Test', {'value': 'hello'});27 print(await db.query('Test'));28 await db.close();29}30```3132## Guidelines3334### Dependency, binaries and import3536* Add `sqflite_common_ffi_web` (currently 1.1.x, Dart 3.12, `platforms: web`37 only) to `dependencies`. It depends on `sqflite_common_ffi` and38 `sqflite_common`.39* Run `dart run sqflite_common_ffi_web:setup` in the project directory after40 adding the dependency and after every upgrade of the package or of41 `sqlite3`. It builds the shared worker with `webdev` (into42 `.dart_tool/sqflite_common_ffi_web/setup/<version>/`) and writes43 `web/sqflite_sw.js` plus `web/sqlite3.wasm` (downloaded from the44 `sqlite3.dart` GitHub releases, version pinned by the package).45 Options: `--force` (`-f`) rebuild, `--dir <dir>` output directory46 (default `web`), `--verbose`, `--no-sqlite3-wasm` (skip the wasm47 download), `--sqlite3-wasm-url <url>`, and an optional project path48 argument. Commit the two files or add them to `.gitignore` and run setup in49 CI; either way they must be present in the deployed site root.50* The same setup is callable from Dart: `setupSqfliteWebBinaries(options:51 SqfliteWebSetupOptions(...))` from `package:sqflite_common_ffi_web/setup.dart`52 (`path`, `dir`, `force`, `verbose`, `sqlite3WasmUri`, `noSqlite3Wasm`,53 `sqlite3WasmFilename`, `sqfliteWebWorkerFilename`). io only.54* Import `package:sqflite_common_ffi_web/sqflite_ffi_web.dart`. It exports55 only the web factories and options (`databaseFactoryFfiWeb`,56 `databaseFactoryFfiWebNoWebWorker`, `databaseFactoryFfiWebBasicWebWorker`,57 `createDatabaseFactoryFfiWeb`, `SqfliteFfiWebOptions`,58 `SqfliteFfiWebContext`, `sqfliteFfiWebLoadSqlite3Wasm`,59 `sqfliteFfiWebStartSharedWorker`, `sqliteFfiWebDebugWebWorker`). The60 `Database` / `DatabaseFactory` types come from61 `package:sqflite_common/sqlite_api.dart` (Dart web),62 `package:sqflite_common_ffi/sqflite_ffi.dart` or `package:sqflite/sqflite.dart`63 (Flutter), so import one of them too.64* The file compiles on io too (the getters throw `UnsupportedError` there),65 so importing it from shared code is fine as long as the factory is only66 touched when `kIsWeb` / `identical(0, 0.0)` is true.6768### Choosing the factory6970* `databaseFactoryFfiWeb` (tag `ffi_web`): SQLite runs in a `SharedWorker`71 loaded from `sqflite_sw.js`; all tabs of the origin share one SQLite72 instance, so it is cross-tab safe. When `SharedWorker` is unavailable73 (Android Chrome) it silently falls back to a dedicated `Worker`, which is74 not cross-tab safe. This is the default choice.75* `databaseFactoryFfiWebNoWebWorker`: loads `sqlite3.wasm` in the main76 thread; no worker file needed, but long queries block the UI and several77 tabs writing the same database can corrupt it. Use for tools, demos or78 when workers cannot be served.79* `databaseFactoryFfiWebBasicWebWorker`: testing only (forces a dedicated80 `Worker`).81* The worker and wasm are loaded lazily on the first factory call from82 `sqflite_sw.js` / `sqlite3.wasm` relative to the page URL. With a83 non-root `<base href>` or another location, pass `sharedWorkerUri` /84 `sqlite3WasmUri` through `createDatabaseFactoryFfiWeb(options:85 SqfliteFfiWebOptions(...))` (see `sqflite-common-ffi-web-options`).86* Flutter app that already uses `sqflite` on mobile: keep the code, set87 `databaseFactory = databaseFactoryFfiWeb;` once at startup when `kIsWeb`,88 before the first `openDatabase`. The global `openDatabase()` then works on89 all platforms. Use `sqflite_common_ffi` (`databaseFactoryFfi`) for the90 desktop counterpart.9192### Paths and persistence9394* `getDatabasesPath()` returns `/` and relative paths are used as is: a95 database name such as `my_db.db` is a key in the virtual file system, not96 a file. Do not build paths with `path_provider` on the web.97* Storage is an IndexedDB database named `sqflite_databases` (override with98 `SqfliteFfiWebOptions.indexedDbName`) holding every sqflite database of the99 origin. IndexedDB is per origin including the port: `localhost:8080` and100 `localhost:8081` are different stores, so always debug on the same port.101 There is no OPFS backend.102* `inMemoryDatabasePath` opens a private in-memory SQLite database inside103 the worker (nothing stored).104* To import a bundled database (asset or download), write its bytes with105 `factory.writeDatabaseBytes(path, bytes)` then `openDatabase(path)`;106 `readDatabaseBytes(path)` exports it. `databaseExists`, `deleteDatabase`107 work on the virtual file system.108* `sqfliteFfiInit()` from `sqflite_common_ffi` is a no-op on the web; calling109 it in shared code is harmless.110111### Limitations112113* Experimental: slower than native, larger message overhead (each call goes114 through `postMessage`), not fully tested.115* `deleteDatabase` is reported as not working when the app itself is116 compiled with dart2wasm (`--wasm`); the JS build is the tested path.117* Basic `Worker` fallback (Android Chrome) is not cross-tab safe.118* Only one worker script name per site: the shared worker is keyed by its119 URL, so after upgrading the package all tabs must reload. To force a120 reload change the worker file name (see options skill, `sw_js_file`).121122## Examples123124### Flutter app: web + mobile + desktop125126```dart127import 'dart:io' show Platform;128129import 'package:flutter/foundation.dart' show kIsWeb;130import 'package:flutter/widgets.dart';131// sqflite_ffi.dart exports the whole sqflite_common API (openDatabase,132// databaseFactory...); package:sqflite/sqflite.dart would be redundant.133import 'package:sqflite_common_ffi/sqflite_ffi.dart';134import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';135136Future<void> main() async {137 WidgetsFlutterBinding.ensureInitialized();138 if (kIsWeb) {139 databaseFactory = databaseFactoryFfiWeb;140 } else if (Platform.isWindows || Platform.isLinux) {141 sqfliteFfiInit();142 databaseFactory = databaseFactoryFfi;143 }144 // Global openDatabase() now works everywhere; on the web the path is a name.145 var db = await openDatabase(146 'app.db',147 version: 1,148 onCreate: (db, _) => db.execute(149 'CREATE TABLE Note (id INTEGER PRIMARY KEY, text TEXT)',150 ),151 );152 await db.insert('Note', {'text': 'hello'});153 runApp(const SizedBox());154}155```156157### Dart web app (no Flutter)158159```dart160import 'package:sqflite_common/sqlite_api.dart';161import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';162163Future<void> main() async {164 var factory = databaseFactoryFfiWeb;165 var db = await factory.openDatabase(166 'counter.db',167 options: OpenDatabaseOptions(168 version: 1,169 onCreate: (db, _) =>170 db.execute('CREATE TABLE Counter (id INTEGER PRIMARY KEY, value INTEGER)'),171 ),172 );173 await db.rawInsert(174 'INSERT INTO Counter(id, value) VALUES (1, 0) ON CONFLICT(id) DO UPDATE SET value = value + 1',175 );176 print(await db.query('Counter'));177 await db.close();178}179```180181### Importing an asset database on Flutter web182183```dart184import 'package:flutter/services.dart' show rootBundle;185import 'package:sqflite/sqflite.dart';186187Future<Database> openBundledDatabase() async {188 const path = 'bundled.db';189 if (!await databaseFactory.databaseExists(path)) {190 final data = await rootBundle.load('assets/bundled.db');191 final bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);192 await databaseFactory.writeDatabaseBytes(path, bytes);193 }194 return openDatabase(path, readOnly: true);195}196```197198### Main-thread factory for a small tool page199200```dart201import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';202203Future<String> sqliteVersion() async {204 // Only sqlite3.wasm is required, no sqflite_sw.js.205 var db = await databaseFactoryFfiWebNoWebWorker.openDatabase('tool.db');206 try {207 return (await db.rawQuery('SELECT sqlite_version()')).first.values.first208 as String;209 } finally {210 await db.close();211 }212}213```214215## Common mistakes216217* Forgetting `dart run sqflite_common_ffi_web:setup`: the first call fails218 and the console prints "An error occurred while initializing the web219 worker ... failure to find the worker javascript file at sqflite_sw.js".220* Serving the app from a sub path without adjusting `sharedWorkerUri` /221 `sqlite3WasmUri`, or a server that does not serve `.wasm` as222 `application/wasm`.223* Upgrading `sqflite_common_ffi_web` or `sqlite3` without re-running setup224 (`--force` if the files look up to date but the wasm version changed).225* Using `getDatabasesPath()` + `join` or `path_provider` on the web; use a226 plain name.227* Debugging on changing ports and "losing" the database.228* Setting `databaseFactory = databaseFactoryFfiWeb` on io (throws229 `UnsupportedError`): guard with `kIsWeb`.230* Importing only `sqflite_ffi_web.dart` and expecting `Database` or231 `OpenDatabaseOptions` to be defined; add `package:sqflite_common/sqlite_api.dart`232 (or `sqflite`).