A Tauri v2 plugin that manages long-lived background service lifecycle across all platforms (Android, iOS, Windows, macOS, Linux).
You implement a single BackgroundService trait on your own struct. The plugin spawns it in a Tokio task, keeps the OS from killing it on mobile, and provides helpers for notifications and event emission. No business logic lives in the plugin — only lifecycle management.
| Capability | Android | iOS | Desktop (Win/macOS/Linux) |
|---|---|---|---|
| Service runs in background | Foreground Service | BGAppRefreshTask + BGProcessingTask | Standard Tokio task |
| OS service mode | — | — | systemd / launchd (desktop-service feature) |
| Service survives app close | START_STICKY |
No | In-process: No; OS service: Yes |
| Local notifications | Yes | Yes | Yes |
Key features: Structured stop reasons (StopReason), native lifecycle events from OS, full lifecycle status API (getLifecycleStatus()), runtime recovery configuration (configureRecovery()), enhanced validation with severity levels, desktop file-based persistence, iOS UserDefaults persistence for BGTask info.
Add the plugin to your app's Cargo.toml:
[dependencies]
tauri = { version = "2" }
tauri-plugin-notification = "2"
tauri-plugin-background-service = "0.7"npm install tauri-plugin-background-serviceuse async_trait::async_trait;
use tauri::Runtime;
use tauri_plugin_background_service::{BackgroundService, ServiceContext, ServiceError};
pub struct MyService {
tick_count: u64,
}
impl MyService {
pub fn new() -> Self {
Self { tick_count: 0 }
}
}
#[async_trait]
impl<R: Runtime> BackgroundService<R> for MyService {
async fn init(&mut self, _ctx: &ServiceContext<R>) -> Result<(), ServiceError> {
// One-time setup: load config, open handles, seed state
Ok(())
}
async fn run(&mut self, ctx: &ServiceContext<R>) -> Result<(), ServiceError> {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(10));
loop {
tokio::select! {
_ = ctx.shutdown.cancelled() => break,
_ = interval.tick() => {
self.tick_count += 1;
// Emit events to JS
let _ = ctx.app.emit("my-service://tick", self.tick_count);
// Show local notifications
ctx.notifier.show("Tick", "Service is alive");
}
}
}
Ok(())
}
}Register tauri-plugin-notification before the background-service plugin:
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_background_service::init_with_service(
|| MyService::new(),
))
.run(tauri::generate_context!())
.expect("error while running application");
}In your tauri.conf.json, add the plugin configuration. It can be empty:
{
"plugins": {
"background-service": {}
}
}import {
startService,
stopService,
isServiceRunning,
getServiceState,
onPluginEvent,
} from 'tauri-plugin-background-service';
// Start the service
await startService({ serviceLabel: 'Syncing data' });
// Query detailed service state
const status = await getServiceState();
console.log(status.state); // 'idle' | 'initializing' | 'running' | 'stopped'
// Listen to lifecycle events
const unlisten = await onPluginEvent((event) => {
switch (event.type) {
case 'started':
console.log('Service started');
break;
case 'stopped':
console.log('Service stopped:', event.reason);
break;
case 'error':
console.error('Service error:', event.message);
break;
}
});
// Stop the service
await stopService();
unlisten();tauri-background-service/
├── tauri-plugin-background-service/ Plugin crate (Rust + TypeScript + native)
│ ├── src/ Rust source (actor loop, trait, models)
│ ├── guest-js/ TypeScript API bindings
│ ├── android/ Kotlin foreground service
│ ├── ios/ Swift BGTaskScheduler
│ ├── examples/ Usage examples
│ ├── docs/ Platform guides and API reference
│ └── tests/ Integration tests
└── test-app/ Manual test harness (Android device)
- Getting Started
- API Reference
- Android Guide
- iOS Guide
- Desktop Guide
- Migration Guide
- Troubleshooting
- Architecture
# Build
cargo build
# Test
cargo test
# Lint
cargo clippy
# Build TypeScript bindings
cd tauri-plugin-background-service/guest-js && npm run buildSee Contributing for the full development guide.
SPDX-License-Identifier: MIT OR Apache-2.0