Skip to main content

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_queue

9

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

Why it exists

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.

ONE TASK, FROM TAP TO DONEenqueue(task)your appStoragestatus: pendingGatenetwork open?Handlerattempt 1completedenqueue returns herebefore any network callclosed: the task waitsno attempt is usedthrowsretryScheduled,then backoffEvery change of status is written to storage before the queue reports it.
One task from tap to done: stored before enqueue returns, past the gate, run by its handler, completed.
What it does

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

The core engine in pure Dart: retries with backoff, priorities, dependencies, groups, rate limits, deduplication and cancellation.

durable_queue_sqlite

SQLite storage. Each task is a committed transaction, so it survives restarts and crashes.

durable_queue_hive

Hive storage for apps that already use Hive, on mobile, desktop and the web.

durable_queue_encryption

Wraps any storage and encrypts task payloads and error messages with AES-256-GCM.

durable_queue_dio

Dio integration: HTTP retry rules, Retry-After, idempotency keys, cancellation and queued requests.

durable_queue_flutter

Starts the queue with the app, pauses it in the background and rebuilds widgets from stored tasks.

durable_queue_connectivity

A network gate: tasks wait while the device is offline and start when connectivity returns.

durable_queue_transfer

Hands uploads and downloads to the operating system, so they keep going after the app is minimised or closed.

durable_queue_workmanager

Runs the queue from Workmanager jobs on Android and iOS while the app is away.
Your Flutter or Dart app: enqueue tasks, show status, react to resultsdurable_queue coreretries, priorities, dependencies, rate limits, events. Pure Dart, no runtime dependencies.Gatesmay a task start now?durable_queue_connectivitybuilt in: ManualQueueGateStoragewhere tasks livedurable_queue_sqlitedurable_queue_hivedurable_queue_encryptionbuilt in: MemoryQueueStorageHandlerswhat a task doesdurable_queue_diodurable_queue_transferbuilt in: any async functiondurable_queue_flutter starts the queue with the app and rebuilds widgets from stored tasks.durable_queue_workmanager runs it in background jobs while the app is away.
How the packages fit: gates decide whether a task may start, storage decides where tasks live, handlers do the work. The core engine sits between them.
Quick start

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_queue
upload_photo.dart
1import '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 enqueue returns
  • 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
How it is checked

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.

Watch it work

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.

Read more

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.

More open source

The other packages we maintain

Node.js and TypeScript, on npm

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.

JavaScript and TypeScript, on npm

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.

FAQ

durable_queue: common questions

An offline-first app saves what the user does on the phone first and sends it when the network allows, instead of failing when the signal drops. durable_queue is the part that holds that work, retries it and recovers it after a crash.

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.