PocketBase Drift
A powerful, offline-first Flutter client for PocketBase, backed by the reactive persistence of Drift (the Flutter & Dart flavor of moor
).
This library extends the official PocketBase Dart SDK to provide a seamless offline-first experience. It automatically caches data from your PocketBase instance into a local SQLite database, allowing your app to remain fully functional even without a network connection. Changes made while offline are queued and automatically retried when connectivity is restored.
Features
- Offline-First Architecture: Read, create, update, and delete records even without a network connection. The client seamlessly uses the local database as the source of truth.
- Automatic Synchronization: Changes made while offline are automatically queued and retried when network connectivity is restored.
- Reactive Data & UI: Build reactive user interfaces with streams that automatically update when underlying data changes, whether from a server push or a local mutation.
- Local Caching with Drift: All collections and records are cached in a local SQLite database, providing fast, offline access to your data.
- Powerful Local Querying: Full support for local querying, mirroring the PocketBase API. This includes:
- Filtering: Complex
filter
strings are parsed into SQLiteWHERE
clauses. - Sorting: Sort results by any field with
sort
(e.g.,-created,name
). - Field Selection: Limit the returned fields with
fields
for improved performance. - Pagination:
limit
andoffset
are fully supported for local data.
- Filtering: Complex
- Relation Expansion: Support for expanding single and multi-level relations (e.g.,
post.author
) directly from the local cache. - Full-Text Search: Integrated Full-Text Search (FTS5) for performing fast, local searches across all your cached record data.
- Authentication Persistence: User authentication state is persisted locally using
shared_preferences
, keeping users logged in across app sessions. - Cross-Platform Support: Works across all Flutter-supported platforms, including mobile (iOS, Android), web, and desktop (macOS, Windows, Linux).
- File & Image Caching: Includes a
PocketBaseImageProvider
that caches images in the local database for offline display.
- Robust & Performant: Includes optimizations for batching queries and file streaming on all platforms to handle large files efficiently.
Getting Started
1. Add Dependencies
Add the following packages to your pubspec.yaml
:
dependencies:
pocketbase_drift: ^0.1.2 # Use the latest version
2. Initialize the Client
Replace a standard PocketBase
client with a $PocketBase.database
client. It's that simple.
- import 'package:pocketbase/pocketbase.dart';
+ import 'package:pocketbase_drift/pocketbase_drift.dart';
- final client = PocketBase('http://127.0.0.1:8090');
+ final client = $PocketBase.database('http://127.0.0.1:8090');
3. Cache the Database Schema (Enable Offline Records)
To enable the offline caching functionality for records, you must provide the database schema to the client. This allows the local database to understand your collection structures for validation and relation expansion without needing to contact the server.
First, download your pb_schema.json
file from the PocketBase Admin UI (Settings > Export collections
). Then, add it to your project as an asset.
// 1. Load the schema from your assets
final schema = await rootBundle.loadString('assets/pb_schema.json');
// 2. Initialize the client and cache the schema
final client = $PocketBase.database('http://127.0.0.1:8090')
..cacheSchema(schema);
4. Web Setup
For web, you need to follow the instructions for Drift on the Web to copy the sqlite3.wasm
binary and drift_worker.js
file into your web/
directory.
- Download the latest
sqlite3.wasm
from the sqlite3.dart releases and the latestdrift_worker.js
from the drift releases. - Place each file inside your project's
web/
folder. - Rename
drift_worker.js
todrift_worker.dart.js
.
Core Concepts
RequestPolicy
The RequestPolicy
enum is central to this library and controls how requests are handled.
-
RequestPolicy.cacheAndNetwork
(Default): Provides a seamless online/offline experience.- For one-time fetches (e.g., getFullList):
- Tries to fetch fresh data from the remote PocketBase server.
- If successful, it updates the local cache with the new data and returns it.
- If the network fails (or the device is offline), it seamlessly falls back to returning data from the local cache without throwing an error.
- For reactive streams (e.g., watchRecords):
- The stream immediately emits the data currently in the local cache, making the UI feel instant.
- A network request is then triggered in the background.
- If the network data is different, the local cache is updated, and the stream automatically emits the new, fresh data.
- For one-time fetches (e.g., getFullList):
-
RequestPolicy.cacheOnly
:- Only ever reads data from the local cache.
- Never makes a network request.
- Important: Records created or updated with this policy are marked with a
noSync
flag and will not be automatically synced to the server when connectivity is restored. This is useful for data that should only ever exist on the local device.
-
RequestPolicy.networkOnly
:- Only ever reads data from the remote server.
- Never uses the local cache for reading.
- Will throw an exception if the network is unavailable.
Offline Support
When you use create
, update
, or delete
methods, the library automatically handles the network state:
- Online: The operation is sent to the server. If it succeeds, the local cache is updated.
- Offline: The operation is immediately applied to the local cache and marked as "pending sync." The UI will react instantly to the local change. When connectivity is restored, the library will automatically attempt to send the pending change to the server.
Usage Examples
Fetching Records
// Get a reactive stream of all "posts"
final stream = client.collection('posts').watchRecords();
// Get a one-time list of posts, sorted by creation date
final posts = await client.collection('posts').getFullList(
sort: '-created',
requestPolicy: RequestPolicy.cacheAndNetwork, // Explicitly set policy
);
// Get a single record
final post = await client.collection('posts').getOne('RECORD_ID');
Creating and Updating Records
// Create a new record (works online and offline)
final newRecord = await client.collection('posts').create(
body: {
'title': 'My Offline Post',
'content': 'This was created without a connection.',
},
requestPolicy: RequestPolicy.cacheAndNetwork,
);
// Update a record
await client.collection('posts').update(newRecord.id, body: {
'content': 'The content has been updated.',
});
Local Full-Text Search
// Search all fields in the 'posts' collection for the word "flutter"
final results = await client.collection('posts').search('flutter').get();
// Search across all collections
final globalResults = await client.search('flutter').get();
File Handling
The library automatically caches files for offline use.
// Use the included PocketBaseImageProvider for easy display
Image(
image: PocketBaseImageProvider(
client: client,
recordId: postRecord.id,
recordCollectionName: postRecord.collectionName,
filename: postRecord.get('my_image_field'), // The filename
),
);
// Or get the file bytes directly
final bytes = await client.files.getFileData(
recordId: postRecord.id,
recordCollectionName: postRecord.collectionName,
fileName: postRecord.get('my_image_field'),
requestPolicy: RequestPolicy.cacheAndNetwork,
);
Custom API Route Caching
The library supports offline caching for custom API routes accessed via the send
method. This is particularly useful for GET
requests to custom endpoints that return data you want available offline.
To use it, simply call the send
method on your $PocketBase
client and provide a RequestPolicy
.
Note: Caching is only applied to GET
requests by default to prevent unintended side effects from caching state-changing operations (POST
, DELETE
, etc.).
// This request will be cached and available offline.
try {
final customData = await client.send(
'/api/my-custom-route',
requestPolicy: RequestPolicy.cacheAndNetwork, // Use the desired policy
);
} catch (e) {
// Handle errors, e.g., if networkOnly fails or cache is empty
}
// This POST request will bypass the cache and go directly to the network.
await client.send(
'/api/submit-form',
method: 'POST',
body: {'name': 'test'},
// No requestPolicy needed, but even if provided, it would be ignored.
);
TODO
X
Offline mutations and retryX
Offline collections & recordsX
Full-text searchX
Support for fields (select), sort, expand, and paginationX
Robust file caching and streaming for large filesX
Proactive connectivity handlingX
Structured loggingX
Add support for indirect expand (e.g.,post.author.avatar
)X
Add support for more complex query operators (e.g., ~ for LIKE/Contains)X
More comprehensive test suite for edge cases
Credits
- Rody Davis (Original Creator)
Libraries
- image
- pocketbase_drift
- A powerful, offline-first Flutter client for PocketBase, backed by the
reactive persistence of Drift (the Flutter & Dart flavor of
moor
).