Adding local storage to a Flutter project means choosing how the app will save and retrieve its data. sqflite is the Flutter plugin interface for SQLite, an embedded database that stores data in a file.
I’ll explain what sqflite does and show how to build a notes database, including a versioned change for a later release.
TL;DR: sqflite in Flutter
Use sqflite for SQLite storage in Flutter when targeting Android, iOS, or macOS. Choose a separate implementation for desktop or web.
- Install sqflite and path, then open a database under getDatabasesPath().
- Set a positive schema version and add onCreate or onUpgrade callbacks.
- Use whereArgs for values supplied to query, update, and delete filters.
- Desktop uses sqflite_common_ffi. Web uses a separate experimental package.
What is sqflite?
sqflite is the Flutter plugin interface to SQLite, an embedded relational database stored in a file. It offers helpers such as insert, query, update, and delete, plus raw SQL, transactions, batches, and version callbacks.
A database table has named columns and rows. In a notes app, a table can hold an integer id, a title, and a body. SQLite enforces the schema while the app maps rows to Dart objects.
The package lists iOS, Android, and macOS as supported platforms. Desktop uses sqflite_common_ffi, while web uses a separate experimental package. See SQLite with Python for engine basics.
How to build a Flutter database with sqflite
This example creates a notes table and keeps database access in one repository. I use package helpers for ordinary row operations because they keep values separate from SQL syntax.
Step 1: Add the packages and imports
From the Flutter project root, add sqflite for database access and path for platform-safe path joining.
flutter pub add sqflite path
Import the APIs in the Dart file that owns the database. Initialize the binding before startup code uses platform services.
import 'package:flutter/widgets.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
Future<Database> openNotesDatabase() async {
WidgetsFlutterBinding.ensureInitialized();
final path = join(await getDatabasesPath(), 'notes.db');
return openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('CREATE TABLE notes (id INTEGER PRIMARY KEY, title TEXT NOT NULL, body TEXT NOT NULL DEFAULT \'\')');
},
);
}
The Flutter code below follows the package and cookbook APIs, but has not been executed because this host has no Flutter or Dart executable. The SQL output below was run using Python’s SQLite library as a database-level check, not as a Flutter plugin test.
Step 2: Insert and query rows
Insert takes a map with keys matching table columns. Query returns a list of maps, which your repository can convert into Dart model objects.
final id = await db.insert('notes', {'title': title, 'body': body});
final rows = await db.query(
'notes',
columns: ['id', 'title', 'body'],
where: 'id = ?',
whereArgs: [id],
);
The question mark is a parameter placeholder. whereArgs supplies its value separately, so data stays out of the SQL text.
Step 3: Update and delete a row
Update and delete use the same where plus whereArgs form. Their integer result reports how many rows matched.
final changed = await db.update(
'notes', {'title': title}, where: 'id = ?', whereArgs: [id]);
final removed = await db.delete(
'notes', where: 'id = ?', whereArgs: [id]);
Keep the filter narrow when changing or removing rows. A missing where clause can apply an operation to the whole table.
Step 4: Add a versioned migration
When a later release adds a column, raise the database version and handle each old version in onUpgrade. This version 2 migration adds a pinned flag with a default for existing notes.
openDatabase(
path,
version: 2,
onUpgrade: (db, oldVersion, newVersion) async {
if (oldVersion < 2) {
await db.execute(
'ALTER TABLE notes ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0',
);
}
},
);
sqflite invokes onConfigure before onCreate, onUpgrade, or onDowngrade, then invokes onOpen. Keep migrations in ascending version order and add a new step rather than changing a migration already shipped. The callbacks run as part of database opening’s transaction.
A run of the corresponding SQL operations against an in-memory SQLite database produced one inserted row, one update, one deletion, and an empty table at the end.
inserted id: 1 query: [(1, 'Migration notes', 'Keep each schema change versioned.')] updated: 1 deleted: 1 remaining: 0
Edge cases: platform support and migrations
Choose the implementation that matches the app’s target. Native sqflite lists iOS, Android, and macOS, while desktop and web use separate packages.
| Target | Implementation | Check |
|---|---|---|
| iOS, Android, macOS | sqflite | SQLite version comes from the platform |
| Linux, Windows, tests | sqflite_common_ffi | Initialize the FFI factory and check system library requirements |
| Web | sqflite_common_ffi_web | Experimental status, worker files, browser storage and port behavior |
The native implementation uses the SQLite available on the operating system, so SQL features can differ by OS version. Query the engine when an app depends on a newer feature.
SELECT sqlite_version();
The web package documents IndexedDB persistence and worker setup, but labels the implementation experimental and notes a single-connection constraint. If the app needs typed queries, generated code, or reactive streams, compare Drift before choosing a repository design. The React Native SQLite guide covers a different framework and is outside this Flutter tutorial.
For grouped writes, use the transaction object inside the transaction callback. The API warns that calling methods on the database object from inside the callback can deadlock.
Conclusion: keep the database version explicit
sqflite provides a direct Flutter API for local SQLite data on its supported native platforms. Keep the path and schema version in one place, bind filter values with whereArgs, and verify desktop or web requirements against the implementation package.
For each release that changes the schema, add a versioned migration and test an upgrade from a database created by the prior version.
FAQ
These answers cover platform and data-safety questions about Flutter SQLite.
Does sqflite support Flutter web?
Web uses the separate sqflite_common_ffi_web package, which is documented as experimental and requires its own setup.
How do I upgrade a sqflite database schema?
Increase the version passed to openDatabase and add an onUpgrade callback that applies ordered changes from the stored version to the new version.
How should values be passed to a sqflite query?
Use a question-mark placeholder in where and pass the value in whereArgs instead of concatenating input into SQL.
