# Sqflite Ffi Flutter

> Use when a Flutter app should use the ffi (package:sqlite3) implementation of sqflite on desktop and mobile with package:sqflite_ffi: what it adds over sqflite_common_ffi (Dart-only plugin, automatic registration of sqfliteDatabaseFactoryFfi as the default databaseFactory through SqfliteFfiPlugin.registerWith, one sqflite isolate shared between Flutter isolates via IsolateNameServer and sqfliteFfiIsolatePortName), createSqfliteDatabaseFactoryFfi, using the database from compute / Isolate.run, DartPluginRegistrant.ensureInitialized, coexistence with the native sqflite plugin, and when to pick sqflite, sqflite_common_ffi or sqflite_ffi.

- Skill: `tekartik/sqflite-ffi-flutter` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tekartik/sqflite-ffi-flutter`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tekartik/sqflite-ffi-flutter/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tekartik (https://skillmd.com/u/tekartik)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tekartik/sqflite-ffi-flutter

---


# sqflite_ffi: ffi sqflite as a Flutter plugin

`package:sqflite_ffi` wraps `sqflite_common_ffi` in a Dart-only Flutter
plugin. Adding it to a Flutter app does two things that `sqflite_common_ffi`
alone does not:

* At startup Flutter calls `SqfliteFfiPlugin.registerWith()`, which runs
  `sqfliteFfiInit()` and sets `sqfliteDatabaseFactoryFfi` as the global
  `databaseFactory` if none is registered yet. The global `openDatabase()`
  works on Windows, Linux, macOS, Android and iOS with no `main()` code.
* All Flutter isolates (main, `compute`, `Isolate.run`) share one sqflite
  isolate: its `SendPort` is registered in `IsolateNameServer` under
  `sqfliteFfiIsolatePortName`, so `singleInstance` and transaction ordering
  hold across isolates.

```dart
import 'package:flutter/widgets.dart';
import 'package:sqflite_ffi/sqflite_ffi.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  var db = await openDatabase(inMemoryDatabasePath);
  debugPrint('${await db.rawQuery('SELECT sqlite_version()')}');
  await db.close();
  runApp(const SizedBox());
}
```

## Guidelines

* Add `sqflite_ffi` (0.1.x, Flutter >= 3.44, Dart 3.12) to `dependencies`.
  It brings `sqflite_common_ffi` and `sqlite3` (>= 3, build hooks: SQLite is
  bundled, nothing to install; run `flutter clean` when changing the
  `sqlite3` major version).
* Import `package:sqflite_ffi/sqflite_ffi.dart`. It re-exports
  `sqflite_common_ffi/sqflite_ffi.dart` (hence the whole `sqflite_common`
  API: `Database`, `openDatabase`, `deleteDatabase`, `inMemoryDatabasePath`,
  `databaseFactory`, `sqfliteFfiInit`, `SqfliteFfiInit`,
  `databaseFactoryFfiNoIsolate`, `SqfliteFfiIsolatePortServer`) but hides
  `databaseFactoryFfi` and `createDatabaseFactoryFfi`. Use the package's own
  `sqfliteDatabaseFactoryFfi` and `createSqfliteDatabaseFactoryFfi` instead.
* Choosing between packages:
  * `sqflite` alone: native plugin, iOS/Android/macOS only.
  * `sqflite_common_ffi`: pure Dart, desktop and tests; you set
    `databaseFactory` yourself; each Dart isolate gets its own sqflite
    isolate.
  * `sqflite_ffi`: Flutter app that wants ffi everywhere (or on desktop with
    zero setup) and/or uses the database from several isolates.
  * `sqflite_common_ffi_web` for the web: `sqflite_ffi` is a no-op there
    (`SqfliteFfiPlugin.registerWith()` does nothing and
    `sqfliteDatabaseFactoryFfi` returns the unsupported ffi factory).
* Registration order: `SqfliteFfiPlugin.registerWith()` uses
  `databaseFactoryOrNull ??=`, and so does the native `sqflite` plugin
  (`SqflitePlugin.registerWith()`). When both plugins are in the app the
  first one in the generated plugin registrant wins, which is not something
  to rely on. Assign the factory explicitly in `main()`: `databaseFactory =
  sqfliteDatabaseFactoryFfi;` to force ffi, or `databaseFactory =
  databaseFactorySqflitePlugin;` (exported by `package:sqflite/sqflite.dart`)
  to force the native plugin on mobile.
* Explicit factory use is always possible:
  `sqfliteDatabaseFactoryFfi.openDatabase(path, options: ...)`. Prefer it
  in libraries and inject it for tests.
* `createSqfliteDatabaseFactoryFfi({SqfliteFfiInit? ffiInit})` returns a new
  factory that still shares the isolate through `IsolateNameServer`.
  `ffiInit` must be top-level or static and runs in the sqflite isolate
  before the first SQLite call; the native library itself is selected by the
  `sqlite3` build hook user defines in `pubspec.yaml`
  (`hooks.user_defines.sqlite3.source`), see `sqflite-common-ffi-desktop`.
* Do not assume plugin registration in background isolates (`compute`,
  `Isolate.run`, `Isolate.spawn`): the global `databaseFactory` may be unset
  there. Either call `DartPluginRegistrant.ensureInitialized()` (from
  `dart:ui`) before the global `openDatabase()`, or use
  `sqfliteDatabaseFactoryFfi` directly: the `IsolateNameServer` lookup works
  in any isolate without registration.
* When two isolates open the same file, pass
  `OpenDatabaseOptions(rollbackActiveTransactionOnOpen: false)` (the default
  is `true` in debug mode) so the second `openDatabase` does not roll back a
  transaction running in the first isolate. Do not `close()` a shared
  single-instance database from the background isolate; the owner closes it.
* Stale registrations (hot restart leaves a dead port in
  `IsolateNameServer`) are detected with a ping (2 s timeout) and replaced
  automatically; no code needed.
* Paths: relative paths resolve under `.dart_tool/sqflite_common_ffi/databases`
  in the current directory. In an app use `path_provider`
  (`getApplicationSupportDirectory()`) and `package:path` `join`. The parent
  directory is created on open.
* Tests: `flutter test` works with `sqfliteDatabaseFactoryFfi` after
  `TestWidgetsFlutterBinding.ensureInitialized()` and `sqfliteFfiInit()`;
  plain `sqflite_common_ffi` in `dev_dependencies` is enough when the test
  does not need isolate sharing (see `sqflite-common-ffi-testing`).

## Examples

### Database work in a compute isolate sharing the instance

```dart
import 'package:flutter/foundation.dart' show compute;
import 'package:sqflite_ffi/sqflite_ffi.dart';

Future<void> _insertInIsolate(String path) async {
  // Same sqflite isolate as the main isolate: same Database instance.
  final db = await sqfliteDatabaseFactoryFfi.openDatabase(
    path,
    options: OpenDatabaseOptions(rollbackActiveTransactionOnOpen: false),
  );
  await db.transaction((txn) async {
    await txn.insert('Test', {'name': 'isolate 1'});
    await txn.insert('Test', {'name': 'isolate 2'});
  });
  // Do not close: the main isolate owns the shared instance.
}

Future<List<Object?>> run(String path) async {
  final db = await sqfliteDatabaseFactoryFfi.openDatabase(
    path,
    options: OpenDatabaseOptions(
      version: 1,
      onCreate: (db, _) => db.execute(
        'CREATE TABLE Test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)',
      ),
      rollbackActiveTransactionOnOpen: false,
    ),
  );
  try {
    await Future.wait([
      db.transaction((txn) async {
        await txn.insert('Test', {'name': 'main 1'});
        await Future<void>.delayed(const Duration(milliseconds: 100));
        await txn.insert('Test', {'name': 'main 2'});
      }),
      compute(_insertInIsolate, path),
    ]);
    // The background transaction waited for the main one:
    // main 1, main 2, isolate 1, isolate 2
    return (await db.query('Test', orderBy: 'id')).map((r) => r['name']).toList();
  } finally {
    await db.close();
  }
}
```

### Forcing ffi even when the native sqflite plugin is present

```dart
import 'package:flutter/widgets.dart';
import 'package:sqflite_ffi/sqflite_ffi.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Both sqflite and sqflite_ffi are in pubspec.yaml; pick ffi explicitly.
  databaseFactory = sqfliteDatabaseFactoryFfi;
  runApp(const SizedBox());
}
```

### Database path with path_provider

```dart
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:sqflite_ffi/sqflite_ffi.dart';

Future<Database> openAppDatabase() async {
  final dir = await getApplicationSupportDirectory();
  return openDatabase(
    p.join(dir.path, 'app.db'),
    version: 1,
    onCreate: (db, _) => db.execute(
      'CREATE TABLE Note (id INTEGER PRIMARY KEY, text TEXT)',
    ),
  );
}
```

### Isolate spawned outside Flutter

```dart
import 'dart:isolate';
import 'dart:ui' show DartPluginRegistrant;

import 'package:sqflite_ffi/sqflite_ffi.dart';

Future<int> countInIsolate(String path) => Isolate.run(() async {
      // Needed for the global openDatabase(); not for sqfliteDatabaseFactoryFfi.
      DartPluginRegistrant.ensureInitialized();
      final db = await openDatabase(
        path,
        options: OpenDatabaseOptions(rollbackActiveTransactionOnOpen: false),
      );
      final rows = await db.rawQuery('SELECT COUNT(*) AS c FROM Note');
      return rows.first['c'] as int;
    });
```

### Custom ffiInit

```dart
import 'package:sqflite_ffi/sqflite_ffi.dart';

void _ffiInit() {
  // Runs inside the shared sqflite isolate before the first SQLite call.
}

final myFactory = createSqfliteDatabaseFactoryFfi(ffiInit: _ffiInit);
```

### Flutter test

```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:sqflite_ffi/sqflite_ffi.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  sqfliteFfiInit();

  test('open in memory', () async {
    final db = await sqfliteDatabaseFactoryFfi.openDatabase(inMemoryDatabasePath);
    expect(await db.getVersion(), 0);
    await db.close();
  });
}
```

## Common mistakes

* Depending on `sqflite_ffi` and still calling `sqfliteFfiInit()` +
  `databaseFactory = databaseFactoryFfi` from `sqflite_common_ffi`: this
  creates a second, non-shared sqflite isolate and prints the "changing
  sqflite default factory" warning. Use `sqfliteDatabaseFactoryFfi` or
  nothing.
* Expecting ffi on the web: use `sqflite_common_ffi_web`.
* Closing a shared single-instance database from a background isolate.
* Opening the same file from two isolates in debug mode without
  `rollbackActiveTransactionOnOpen: false`.
* Calling `openDatabase()` in a hand-spawned isolate without
  `DartPluginRegistrant.ensureInitialized()`: `databaseFactory not
  initialized`.
* Having both `sqflite` and `sqflite_ffi` in `pubspec.yaml` and not
  assigning `databaseFactory`: which implementation runs depends on the
  registrant order.

