---
title: Quick Start
description: Get started with Flutter Workmanager in minutes
---

## Installation

Add `workmanager` to your `pubspec.yaml`:

```yaml
dependencies:
  workmanager: ^0.10.0
```

Then run:
```bash
flutter pub get
```

## Platform Setup

### Android
Android works automatically - no additional setup required! ✅

<Info>
If tasks stop running after the app is closed, that is usually the device's
battery optimizer, not the plugin. See the
[Troubleshooting guide](troubleshooting) for per-vendor whitelist instructions,
constraint gotchas, and `adb shell dumpsys jobscheduler` verification.
</Info>

### iOS

<Warning>
**iOS Minimum Deployment Target:** iOS 14.0 or later is required. Update your project's deployment target in Xcode:
1. Open `ios/Runner.xcodeproj` in Xcode
2. Select the Runner target
3. Set "Minimum Deployments" to iOS 14.0 or later
4. Or edit `ios/Runner.xcodeproj/project.pbxproj` and set `IPHONEOS_DEPLOYMENT_TARGET = 14.0;`
</Warning>

iOS requires a 5-minute setup in Xcode. Choose your approach based on your needs:

#### Use other Flutter plugins inside background tasks (iOS)

Background tasks run in a **separate Flutter engine/isolate**. Flutter plugins
(Firebase, `shared_preferences`, networking, etc.) are **not** registered in
that engine by default — calling them from inside your `callbackDispatcher`
fails with `PlatformException(channel-error, Unable to establish connection on
channel...)`.

To make plugins available in the background engine, wire the plugin
registrant callback in your `AppDelegate.swift`. The snippet below also follows
Flutter's [UIScene lifecycle migration](https://docs.flutter.dev/release/breaking-changes/uiscenedelegate):
plugins are registered in `didInitializeImplicitFlutterEngine` (instead of
`application(_:didFinishLaunchingWithOptions:)`), and
`WorkmanagerPlugin.registerLaunchHandlers()` re-registers the BGTaskScheduler
launch handlers persisted from previous sessions — iOS requires those to be
registered before app launch finishes, and under UIScene the plugin's own
`application` callback runs too late for that:

```swift
import Flutter
import UIKit
import workmanager_apple

@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    WorkmanagerPlugin.registerLaunchHandlers()

    WorkmanagerPlugin.setPluginRegistrantCallback { registry in
      GeneratedPluginRegistrant.register(with: registry)
    }

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
    GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
  }
}
```

<Info>
**UIScene lifecycle:** Apple requires UIKit apps to adopt the UIScene lifecycle
from the release following iOS 26. After migration, call
`WorkmanagerPlugin.registerLaunchHandlers()` in your AppDelegate's
`application(_:didFinishLaunchingWithOptions:)` so BGTaskScheduler launch
handlers are registered before app launch finishes (the plugin re-registers
every identifier it persisted in previous sessions, so tasks scheduled from
Dart keep being delivered after a relaunch). Apps that haven't migrated to
UIScene keep working without this call.
</Info>

<Warning>
**Background-isolate plugins:** only plugins that are safe to use from a
background isolate (no UI, no views) work there. Plugins that are not
isolate-safe can still crash the background task.
</Warning>

#### Option A: Periodic Tasks (Recommended for most use cases)
For regular data sync, notifications, cleanup - uses iOS Background Fetch:

1. **Enable Background Modes** in Xcode target capabilities ([Configuration Guide](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)) and add to Info.plist ([UIBackgroundModes reference](https://developer.apple.com/documentation/bundleresources/information-property-list/uibackgroundmodes)):
```xml
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
</array>
```

2. **No AppDelegate configuration needed** - works automatically from Dart code

<Warning>
**iOS Background Fetch scheduling:** iOS completely controls when Background Fetch runs (typically once per day based on user app usage patterns). You cannot force immediate execution - it's designed for non-critical periodic updates like refreshing content.
</Warning>

#### Option B: Processing Tasks (For complex operations)
For file uploads, data processing, longer tasks - uses BGTaskScheduler:

1. **Enable Background Modes** in Xcode target capabilities ([Configuration Guide](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)) and add to Info.plist ([UIBackgroundModes reference](https://developer.apple.com/documentation/bundleresources/information-property-list/uibackgroundmodes)):
```xml
<key>UIBackgroundModes</key>
<array>
    <string>processing</string>
</array>

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.yourapp.processing_task</string>
</array>
```

2. **Configure AppDelegate.swift** (required for BGTaskScheduler):
```swift
import workmanager_apple

// In application didFinishLaunching
WorkmanagerPlugin.registerBGProcessingTask(
  withIdentifier: "com.yourapp.processing_task"
)
```

Apps that adopted the UIScene lifecycle must also call
`WorkmanagerPlugin.registerLaunchHandlers()` in
`application(_:didFinishLaunchingWithOptions:)` — see the
[registrant wiring section above](#use-other-flutter-plugins-inside-background-tasks-ios).

<Warning>
**iOS Task Identifier Matching:** The task name in your Dart code must **exactly match** the identifier in Info.plist and AppDelegate. Using short names like `"data_sync"` in Dart while having `com.yourapp.processing_task` in native code will cause `BGTaskSchedulerErrorDomain Code 3` errors.
</Warning>

<Info>
**Why BGTaskScheduler registration is needed:** iOS requires every background task identifier to be listed in Info.plist for security and system resource management. The plugin registers the task handler automatically (at schedule time and again on the next app launch), so no manual AppDelegate code is required. Background Fetch (Option A) doesn't require this since it uses the simpler, system-managed approach.
</Info>

#### Option C: Periodic Tasks with Custom Frequency
For periodic tasks with more control than Background Fetch - uses BGTaskScheduler with frequency:

1. **Enable Background Modes** in Xcode target capabilities ([Configuration Guide](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)) and add to Info.plist ([UIBackgroundModes reference](https://developer.apple.com/documentation/bundleresources/information-property-list/uibackgroundmodes)):
```xml
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
</array>

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.yourapp.periodic_task</string>
</array>
```

2. **(Optional) Configure AppDelegate.swift** for custom frequency control:
```swift
import workmanager_apple

// In application didFinishLaunching
WorkmanagerPlugin.registerPeriodicTask(
  withIdentifier: "com.yourapp.periodic_task",
  frequency: NSNumber(value: 20 * 60) // 20 minutes (15 min minimum)
)
```
The plugin re-registers launch handlers for scheduled task identifiers on app
launch, so this step is only needed if you want to pre-register the task or
control the scheduling hint from native code. Apps that adopted the UIScene
lifecycle must also call `WorkmanagerPlugin.registerLaunchHandlers()` in
`application(_:didFinishLaunchingWithOptions:)` — see the
[registrant wiring section above](#use-other-flutter-plugins-inside-background-tasks-ios).

<Warning>
**iOS Task Identifier Matching:** The task name in your Dart code must **exactly match** the identifier in Info.plist and AppDelegate. Using short names like `"cleanup"` in Dart while having `com.yourapp.periodic_task` in native code will cause `BGTaskSchedulerErrorDomain Code 3` errors.
</Warning>

#### Option D: Health Research Tasks (iOS 17+)
For apps participating in a Health Research Study — `BGHealthResearchTaskRequest`
gets additional priority/reliability for study-essential processing:

1. **App requirements (Apple-enforced, outside the plugin):**
   - The app must be part of a HealthKit **Health Research Study container**
     (typically provisioned with ResearchKit / `HKResearchStudy`).
   - The `com.apple.developer.backgroundtasks.healthresearch` entitlement must
     be present in your `.entitlements` file.
   - The user must have **opted in** to the study.

2. **Enable Background Modes** and add the identifier to Info.plist:
```xml
<key>UIBackgroundModes</key>
<array>
    <string>processing</string>
</array>

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.yourapp.health_research_task</string>
</array>
```

3. **(Optional) Pre-register the launch handler** in `AppDelegate.swift`:
```swift
import workmanager_apple

// In application didFinishLaunching
WorkmanagerPlugin.registerBGHealthResearchTask(
  withIdentifier: "com.yourapp.health_research_task"
)
```
The plugin also registers the handler automatically at schedule time and on
the next app launch.

4. **Schedule from Dart** (iOS 17+; older iOS versions receive an error):
```dart
await Workmanager().registerHealthResearchTask(
  'com.yourapp.health_research_task',
  'healthResearchTask',
  initialDelay: const Duration(hours: 1),
  constraints: Constraints(
    networkType: NetworkType.connected,
    requiresCharging: true,
  ),
);
```

<Warning>
**Health research tasks require the entitlement.** Without the Health Research
Study container and `com.apple.developer.backgroundtasks.healthresearch`
entitlement, `BGTaskScheduler.submit` fails and the task is never delivered —
the plugin cannot validate this for you. See the capability matrix in the docs
index for the full iOS strategy comparison.
</Warning>

#### Option E: Continued Processing Tasks (iOS 26+)
For workloads that must **begin immediately or shortly after submission** and
are allowed to **continue running while the app is backgrounded** (e.g. ML
inference on a captured camera session) — `BGContinuedProcessingTaskRequest`.
The system shows a Live Activity to the user while the task runs.

1. **Enable Background Modes** and add the identifier to Info.plist:
   Continued-processing identifiers must use **wildcard notation** ending in
   `.*`, with a prefix containing your app's bundle identifier:
```xml
<key>UIBackgroundModes</key>
<array>
    <string>processing</string>
</array>

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.yourapp.continuedProcessing.*</string>
</array>
```

2. **(Optional) Pre-register the launch handler** in `AppDelegate.swift`:
```swift
import workmanager_apple

// In application didFinishLaunching
WorkmanagerPlugin.registerBGContinuedProcessingTask(
  withIdentifier: "com.yourapp.continuedProcessing.*"
)
```
The plugin also registers the handler automatically at schedule time and on
the next app launch.

3. **Schedule from Dart** (iOS 26+; older iOS versions receive an error):
```dart
await Workmanager().registerContinuedProcessingTask(
  'com.yourapp.continuedProcessing.*',
  'continuedProcessingTask',
  title: 'Example continued processing',
  subtitle: 'Processing in progress',
  inputData: {'frames': 120},
);
```

<Warning>
**Differences vs processing tasks:** continued processing tasks start near
submission time (the scheduler ignores `initialDelay`) and are not limited to
idle devices, but the system still enforces expiration based on system
conditions and user input. Apple expects tasks to report progress
(`NSProgress`); the plugin does not currently plumb progress from Dart, so
callbacks that appear stalled may be expired by the scheduler.
</Warning>

<Success>
**Which option to choose?** 
- **Option A (Background Fetch)** for non-critical updates that can happen once daily (data sync, content refresh)
- **Option B (BGTaskScheduler)** for one-time tasks, file uploads, or immediate task scheduling  
- **Option C (Periodic Tasks)** for regular tasks with custom frequency control (15+ minutes)
- **Option D (Health Research Tasks)** for iOS 17+ health research study apps that need reliable background processing
- **Option E (Continued Processing Tasks)** for iOS 26+ workloads that must start now and continue while backgrounded
</Success>

### macOS

macOS uses `NSBackgroundActivityScheduler` (the macOS equivalent of
BGTaskScheduler). No Info.plist keys or AppDelegate registration are needed to
schedule tasks — scheduling happens directly from Dart.

1. **Wire the plugin registrant callback** in your macOS `AppDelegate.swift` so
   other plugins are available in the background engine:

```swift
import Cocoa
import FlutterMacOS
import workmanager_apple

@main
class AppDelegate: FlutterAppDelegate {
  override func applicationDidFinishLaunching(_ notification: Notification) {
    WorkmanagerPlugin.setPluginRegistrantCallback { registry in
      RegisterGeneratedPlugins(registry: registry)
    }
    super.applicationDidFinishLaunching(notification)
  }
}
```

2. **Name your dispatcher `callbackDispatcher`** — see the Basic Usage section
   below. On macOS the function must be a top-level function in your app's main
   library (e.g. `main.dart`) with that exact name.

<Warning>
**macOS limitations:** Tasks only run while the app is running or backgrounded
and the Mac is awake. They do **not** run after the app is quit. Timing is
best-effort — the system may defer activities while the Mac is busy, and
network/charging constraints are not supported.
</Warning>

## Basic Usage

### 1. Create Background Task Handler

```dart
@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    switch (task) {
      case "com.yourapp.processing_task":  // Must match Info.plist and AppDelegate
        await syncDataWithServer();
        break;
      case "com.yourapp.periodic_task":  // Must match Info.plist and AppDelegate
        await cleanupOldFiles();
        break;
      case Workmanager.iOSBackgroundTask:
        // iOS Background Fetch task
        await handleBackgroundFetch();
        break;
      default:
        // Handle unknown task types
        break;
    }
    
    return Future.value(true);
  });
}
```

<Info>
**Important:** The `callbackDispatcher` must be a top-level function (not inside a class) since it runs in a separate isolate.
</Info>

### What is `callbackDispatcher`?

`callbackDispatcher` is the *entry point* of your background isolate. It is not
the task itself: it is the function that iOS and Android start whenever a
scheduled task becomes due, and inside it you tell the plugin which handler to
run via `Workmanager().executeTask(...)`.

```dart
@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    // This closure is where your background work actually runs.
    switch (task) {
      case 'data_sync':
        await syncData(inputData);
        break;
    }
    return Future.value(true);
  });
}
```

Requirements and behavior:

- It must be a **top-level function** or a **static method**. Flutter needs to
  look it up by handle when the app is started from the background, which is
  only possible for top-level/static functions (`PluginUtilities.getCallbackHandle`
  returns `null` for anything else).
- It must be annotated with `@pragma('vm:entry-point')` so the Dart compiler
  keeps it reachable for background starts.
- It runs in a **separate isolate** from your UI. Initialize plugins and
  dependencies (e.g. `DartPluginRegistrant.ensureInitialized()`,
  `SharedPreferences`, `Firebase.initializeApp`) *inside* the handler, not in
  `main()`.
- You register exactly one dispatcher per app (in `main()`) and dispatch on
  `task` inside it. You do not need a separate `callbackDispatcher` per task.

The name can be confusing: the function is really your *task runner*, but the
name is kept for historical reasons. Feel free to name it `taskRunner` or
`backgroundTaskHandler` in your own code.

### 2. Initialize in main()

```dart
import 'package:flutter/foundation.dart';

void main() {
  Workmanager().initialize(callbackDispatcher);
  
  runApp(MyApp());
}
```

### 3. Schedule Tasks

```dart
// Schedule a one-time task
Workmanager().registerOneOffTask(
  "sync-task",
  "data_sync",  // taskName: the value your callback receives (no AppDelegate setup needed)
  initialDelay: Duration(seconds: 10),
);

// Schedule a periodic task
Workmanager().registerPeriodicTask(
  "cleanup-task",
  "com.yourapp.periodic_task",  // Must match Info.plist and AppDelegate
  frequency: Duration(hours: 24),
);

// Schedule a periodic task with input data
Workmanager().registerPeriodicTask(
  "sync-task",
  "data_sync",
  frequency: Duration(hours: 6),
  inputData: <String, dynamic>{
    'server_url': 'https://api.example.com',
    'sync_type': 'full',
    'max_retries': 3,
  },
);
```

<Warning>
**iOS: `registerPeriodicTask` requires BGTaskScheduler setup.** On iOS the
`uniqueName` you pass here is submitted to BGTaskScheduler, so it must appear in
`BGTaskSchedulerPermittedIdentifiers` in Info.plist. The launch handler is
registered automatically by the plugin (at schedule time and on the next app
launch), so no AppDelegate code is required. Without the Info.plist entry
you'll get `BGTaskSchedulerErrorDomain Code 3` ("not advertised in the
application's Info.plist"). On Android no native setup is needed.
</Warning>

## Expedited tasks (Android only)

For one-off work that must start promptly (a user-initiated upload, a
notification the user is waiting on), pass `expedited: true`:

```dart
await Workmanager().registerOneOffTask(
  "sync-user-data",
  "syncTask",
  expedited: true,
);
```

On Android 12+ (API 31+) the system runs expedited work as a WorkManager-
managed foreground service and shows a notification while it runs. Expedited
work is **one-off only** (periodic tasks cannot be expedited) and is meant for
short, user-visible work — not long-running processing. Other platforms ignore
the flag. See
[Customization → Expedited work](customization#expedited-work-android-only) for
details and the `outOfQuotaPolicy` interplay.

## Long-running tasks (foreground service)

Android's WorkManager runs background workers for a limited time (typically a
few minutes). For work that legitimately takes longer — bulk uploads or
downloads, ML processing, large file operations — Android's first-class answer
is a **foreground service**: the worker is promoted to the foreground, the
process is kept alive, and a notification stays visible for the whole duration
of the task.

To run a task as a foreground service, pass a `foregroundServiceConfig` when
registering the task:

```dart
await Workmanager().registerOneOffTask(
  "upload-task",
  "upload_files",
  inputData: <String, dynamic>{
    'destination': 'https://api.example.com/uploads',
  },
  foregroundServiceConfig: ForegroundServiceConfig(
    notificationTitle: "Uploading files",
    notificationText: "Your files are being uploaded",
  ),
);
```

The same option is available on `registerPeriodicTask`. The notification is
shown as soon as the task starts and is removed automatically when the task
finishes (or is cancelled). The worker keeps running even if the app is in the
background or closed, for as long as the task takes.

`ForegroundServiceConfig` has sane defaults for every field; you only need to
provide the notification text you want to show:

| Field | Default |
| --- | --- |
| `notificationTitle` | `Task in progress` |
| `notificationText` | `Your task is still running` |
| `notificationChannelId` | `workmanager_foreground_tasks` |
| `notificationChannelName` | `Long-running tasks` |
| `notificationId` | `0` |
| `foregroundServiceType` | `ForegroundServiceType.dataSync` |

The supported foreground service types are `dataSync` (default; for
synchronization, uploads and downloads) and `shortService` (short, critical
work that must complete quickly):

```dart
foregroundServiceConfig: ForegroundServiceConfig(
  notificationTitle: "Cleanup",
  notificationText: "Removing temporary files",
  foregroundServiceType: ForegroundServiceType.shortService,
),
```

<Warning>
**Android 13+ notification permission:** on Android 13 (API 33) and newer the
foreground service still runs, but the notification is only visible when the
app holds the `POST_NOTIFICATIONS` runtime permission. Request it from your UI
before registering the task if you want the notification to be shown.
</Warning>

<Warning>
**Android 14+ foreground service types:** apps targeting SDK 34+ must declare
the foreground service type in the manifest and hold the matching permission.
The plugin already declares `dataSync` and `shortService` (and their
permissions) for you — no native setup is required.
</Warning>

<Info>
**Android 15+ limitations:** on Android 15 (API 35), `dataSync` foreground
services may run for at most 6 hours in any 24-hour period, and work launched
directly from `BOOT_COMPLETED` may be blocked from starting a `dataSync` or
`shortService` foreground service by the system. In that case the task simply
falls back to running as a regular background worker within the usual limits.
</Info>

## Task Results

Your background tasks can return:

- `Future.value(true)` - ✅ Task successful
- `Future.value(false)` - 🔄 Task should be retried
- `Future.error(...)` - ❌ Task failed


## Key Points

- **Callback Dispatcher**: Must be a top-level function (not inside a class)
- **Separate Isolate**: Background tasks run in isolation - initialize dependencies inside the task
- **iOS Task Identifiers**: When using BGTaskScheduler (Options B & C), task names in Dart must exactly match the identifiers in `BGTaskSchedulerPermittedIdentifiers` in Info.plist
- **Platform Differences**: 
  - Android: Reliable background execution, 15-minute minimum frequency
  - iOS: 30-second limit, execution depends on user patterns and device state


## Next Steps

- **[Task Customization](customization)** - Advanced configuration with constraints, input data, and management
- **[Debugging Guide](debugging)** - Learn how to debug and troubleshoot background tasks  
- **[Example App](https://github.com/fluttercommunity/flutter_workmanager/tree/main/example)** - Complete working demo
