durable_queue: the task queue behind an offline-first app
An upload that starts in a lift, a form sent on a train, a sync the user closes the app on: durable_queue saves each task on the phone before it runs, waits for the network without wasting retries, retries with backoff and recovers interrupted tasks after a crash. Pure Dart core, MIT licence, built and maintained by Boffin Coders.
- durable_queue 0.5.10 on pub.dev, released 30 Oct 2026
- MIT licence, tested on every change
- Dart
- Flutter
- MIT
flutter pub add durable_queue9
Packages
A pure-Dart core plus storage, network, background and encryption adapters
400+
Tests
Unit tests on every push, plus a kill test on an Android emulator and an iOS simulator
0
Core dependencies
The core engine has no runtime dependencies
MIT
Licence
Free for commercial apps
A retry loop dies with the app
An app that retries a failed upload in memory works until the phone loses signal for ten minutes, the operating system closes the app in the background, or the user swipes it away. The retry loop is gone, and so is the upload. Nobody sees an error; the photo or the form simply never arrives.
durable_queue moves the work out of memory. With the SQLite or Hive adapter, a task is written to storage before enqueue returns, so it survives a crash or a kill. Gates hold tasks while the device is offline without spending retry attempts, retries back off, with optional jitter, and when the app starts again the queue finds every interrupted task and retries it if it has attempts left. Uploads can be handed to the operating system so they finish even after the app is closed.
It is built as a small core with everything else in adapters, and one page says exactly what it promises and what it does not.
Nine packages, one small core
The core decides when each task runs. Everything else plugs into three slots: storage decides where tasks live, gates decide whether a task may start now, and handlers do the work. Add only the packages your app needs.
durable_queue
durable_queue_sqlite
durable_queue_hive
durable_queue_encryption
durable_queue_dio
durable_queue_flutter
durable_queue_connectivity
durable_queue_transfer
durable_queue_workmanager
A photo upload with retries and backoff
Describe the work as a task, register a handler, enqueue it with a retry policy. In an app, swap the memory storage for the SQLite or Hive adapter.
flutter pub add durable_queue1import 'package:durable_queue/durable_queue.dart';
2
3final class UploadPhotoTask extends DurableTask {
4 UploadPhotoTask({required this.path});
5
6 final String path;
7
8
9 String get type => 'upload_photo';
10
11
12 Map<String, dynamic> toJson() => {'path': path};
13
14 static UploadPhotoTask fromJson(Map<String, dynamic> json) =>
15 UploadPhotoTask(path: json['path'] as String);
16}
17
18Future<void> main() async {
19 final queue = DurableQueue(
20 storage: MemoryQueueStorage(), // use a persistent adapter in an app
21 maxConcurrentTasks: 3,
22 );
23
24 queue.register<UploadPhotoTask>(
25 type: 'upload_photo',
26 decoder: UploadPhotoTask.fromJson,
27 handler: (task, context) async {
28 await photoApi.upload(task.path); // your code
29 },
30 );
31
32 await queue.start();
33
34 await queue.enqueue(
35 UploadPhotoTask(path: '/storage/avatar.jpg'),
36 retryPolicy: RetryPolicy.exponential(
37 maxAttempts: 5,
38 initialDelay: Duration(seconds: 1),
39 ),
40 );
41}The full guide, every method and more examples are on the documentation site.
What it promises, and what it does not
Promised and tested
- A task is stored before
enqueuereturns - Every task runs at least once, until it succeeds or its attempts run out
- Waiting for the network or a rate limit does not use attempts
- Interrupted tasks are found when the queue starts again and retried if attempts remain
- A task never starts before the tasks it depends on have completed
Not promised
- Exactly once: make handlers safe to repeat, for example with the task's idempotency key
- Running while the app is closed, beyond what the transfer and Workmanager adapters get from the operating system
- Rate limits across restarts: they are kept in memory
- Encryption unless you wrap the storage with durable_queue_encryption
Tested by killing the app, not only in unit tests
The repository runs its unit tests on every push and pull request, runs a kill test on an Android emulator and an iOS simulator whenever the code the demo app uses changes, and publishes its benchmarks and the guarantees it is tested against.
Kill test
CI kills the demo app mid-upload on an Android emulator and an iOS simulator, then checks every photo arrived and was announced exactly once. The runs are public.
Benchmarks
Storing, running and recovering tasks are measured on Linux and macOS runners, with the method published.
Written guarantees
One page states every promise and every non-promise. A case that breaks it is a bug.
Documentation site
A guide, a cookbook, a page per package, the storage contract and a 10-minute video.
Closed the app. The uploads still arrived.
The demo app from the repository, recorded on a real Android phone: three photos are queued, the app goes to the home screen, and the server on the left receives every photo and is told about each one.
The demo, recorded
Recorded on a real Android phone, sped up three times. Left: what the demo server received. Right: the app, then the home screen while the uploads finish.
The 10-minute explainer
Why uploads get lost, and how durable_queue stores, gates, retries and recovers them, chapter by chapter.
How we use it
The design behind the queue, the guide that builds an upload with it, and the team that builds apps like this.
The architecture write-up: storage before enqueue, gates, backoff with jitter, the two keys and crash recovery.
A step-by-step guide with the setup code, the benchmarks and the video.
The team that wrote durable_queue builds Flutter apps for businesses and agencies.
The other packages we maintain
ts-netstorageapi-akamai
A typed, promise-based client and command line tool for the Akamai NetStorage HTTP API: upload and download files or buffers, list, inspect and manage files, with Akamai's method names.
fundamental-analysis
Financial ratios and fundamental analysis for JavaScript and TypeScript: valuation multiples, Piotroski F-Score, Altman Z, DCF and intrinsic value, with plain-English ratings and zero dependencies.
Every package, with its documentation and source: open source by Boffin Coders.
durable_queue: common questions
Not answered here?
Building an app that has to work without a signal?
We maintain durable_queue for apps like yours. Tell us what has to survive a dropped connection, and we will show you how we would build it.