Flutter Local Storage: SharedPreferences vs Hive vs Isar vs SQLite
Almost every Flutter app needs to persist something locally — a login token, a theme preference, a list of favorited items, an offline cache of API responses. The question I get asked most often isn’t “how do I save data,” it’s “which package should I even use,” because there are four reasonable answers and the pubspec.dev descriptions all sound the same (“fast,” “lightweight,” “easy to use”).
They’re not interchangeable. Here’s what actually differs, based on what I’ve shipped with each.
The quick answer
- Just a few key-value settings (theme, onboarding-seen flag, auth token) →
SharedPreferences. - Structured objects, offline-first app, no complex queries →
Hive(or its actively maintained successor,Isar). - Relational data — things that reference each other, need joins, filters, or you already think in SQL →
SQLite, viadriftorsqflite.
Now the reasoning, because “it depends” isn’t useful without knowing what it depends on.
SharedPreferences: for settings, not data
SharedPreferences wraps native key-value storage (UserDefaults on iOS, SharedPreferences on Android). It only stores primitives — String, int, bool, double, and List<String>. That’s the whole API surface, and that’s the point: it’s for small, flat settings, not for your app’s actual data model.
Where people get into trouble: storing JSON-encoded objects or lists as strings in SharedPreferences because it’s the first thing they reached for. It works until you need to update one field of one object, and you’re now decoding an entire list, mutating it in memory, and re-encoding the whole thing back to a string on every write. If you catch yourself calling jsonEncode/jsonDecode around SharedPreferences, that’s the signal to move to Hive or Isar instead.
Hive: fast, simple, no native dependencies
Hive stores actual Dart objects as binary data, with no SQL and no native platform code — which is why it’s popular for Flutter specifically (it even works cleanly on Flutter Web, unlike sqflite). You define a typed model, generate an adapter, and read/write objects directly.
Hive is genuinely fast for what it does, but “what it does” is deliberately narrow: it’s a key-value store where the values happen to be typed objects. There’s no query language — filtering means iterating box.values in Dart yourself. For a list of 50 tasks that’s irrelevant. For 50,000 rows where you need “all orders from this customer placed in the last 30 days,” you’ll be writing manual loops instead of a WHERE clause, and that’s the point where SQL-backed storage starts winning.
One caveat worth knowing before you commit: Hive’s original maintainer moved on to a rewrite called Isar, and Hive itself has slowed on updates. It still works fine and is safe to use, but if you’re starting a new project today, it’s worth looking at Isar first for the same use case.
Isar: Hive’s successor, with actual queries
Isar keeps Hive’s “no native SQL, define a Dart class, store objects directly” model, but adds real indexes and a query builder — so you get some of SQLite’s query power without leaving Dart.
That .filter().isDoneEqualTo(false).sortByTitle() chain is the thing Hive can’t do for you — it runs against an index, not a Dart-side loop. If your app has moderately structured data (favorites, cached feed items, notes) and you want queries without pulling in a full relational model, Isar is usually my default now over Hive for new projects.
SQLite (via drift or sqflite): when data actually has relationships
Once your data has real relationships — orders that belong to customers, line items that belong to orders, tags that apply to many posts — you want a relational database, not a document store. sqflite gives you raw SQL access; drift (built on top of sqflite) gives you a type-safe query builder plus compile-time-checked SQL, which I’d recommend over hand-writing SQL strings once a project has more than one or two tables.
The references(Customers, #id) line is doing real foreign-key work — drift will catch it at compile time if you try to insert an order for a customer ID that doesn’t exist in a way that violates the schema, and it generates migrations you version alongside your schema changes. That structure is exactly what you lose with Hive or Isar, and exactly what you don’t need if your data is a flat list of independent objects.
The trade-off is setup cost: drift needs build_runner code generation, schema migrations to manage as your tables evolve, and a genuinely different mental model (tables and joins) than “just store this object.” For a note-taking app, that’s overkill. For anything resembling an e-commerce order history or a multi-user chat log, it’s the right amount of structure.
What I actually reach for
- Auth token, theme, feature flags, “has seen onboarding” →
SharedPreferences, every time. - A single collection of similar objects with light filtering (favorites, downloaded articles, a cache of API responses) →
Isar. - Data with real relationships, or anything I’d normally model as SQL tables →
drift. - Legacy codebase already using Hive, works fine, no query pain yet → leave it, don’t migrate for the sake of migrating.
Frequently asked questions
Is Hive still safe to use in 2026? Yes — it’s stable and widely used, it just isn’t receiving significant new feature development. For an existing app that already uses it and isn’t hitting query limitations, there’s no urgency to migrate.
Can I use more than one of these in the same app?
Commonly, yes — SharedPreferences for settings alongside Isar or drift for the app’s actual data model is a completely normal combination, not a code smell.
Does SharedPreferences persist data securely?
No — it’s stored in plaintext on the platform’s standard preferences storage. For anything sensitive (tokens, credentials), use flutter_secure_storage, which wraps Keychain on iOS and the Android Keystore, instead.
Which of these works on Flutter Web?
SharedPreferences and Hive/Isar all work on web. sqflite/drift need sqflite_common_ffi_web or a similar web-specific driver — it’s supported, but it’s an extra setup step, not a drop-in.
Not sure which storage approach fits your app’s data model? Get in touch — happy to talk through it.