npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tauri-plugin-background-service

v0.5.2

Published

Manage long-lived background service lifecycle in Tauri v2 apps across Android, iOS, and desktop.

Readme

tauri-plugin-background-service

crates.io docs.rs npm License

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.

Platform Support

| 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 |

Installation

Rust

Add the plugin to your app's Cargo.toml:

[dependencies]
tauri = { version = "2" }
tauri-plugin-notification = "2"
tauri-plugin-background-service = "0.5"

npm (TypeScript API)

npm install tauri-plugin-background-service

Rust Usage

1. Implement the BackgroundService trait

Create a struct and implement BackgroundService<R> with init() and run() methods:

use 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(())
    }
}

2. Register the plugin

In your main.rs, 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");
}

ServiceContext

The ServiceContext<R> passed to init() and run() provides:

  • notifier — Fire local notifications via ctx.notifier.show("Title", "Body")
  • app — Emit events to JS via ctx.app.emit("my-event", &payload)
  • shutdown — A CancellationToken that resolves when stopService() is called. Always include it in tokio::select!

TypeScript Usage

import {
  startService,
  stopService,
  isServiceRunning,
  getServiceState,
  onPluginEvent,
  type ServiceState,
  type ServiceStatus,
} from 'tauri-plugin-background-service';

// Start the service (optionally configure the Android notification label)
await startService({ serviceLabel: 'Syncing data' });

// Check if running (simple boolean)
const running = await isServiceRunning();

// Query detailed service state
const status = await getServiceState();
console.log(status.state); // 'idle' | 'initializing' | 'running' | 'stopped'
console.log(status.lastError); // null or error message

// 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();

// Clean up listener
unlisten();

Desktop Service API

When the desktop-service Cargo feature is enabled:

import {
  installService,
  uninstallService,
} from 'tauri-plugin-background-service';

// Install as OS-level daemon (systemd / launchd)
await installService();

// Uninstall the OS service
await uninstallService();

Foreground Service Types

The foregroundServiceType parameter in StartConfig accepts these values:

| Type | Use Case | |------|----------| | "dataSync" (default) | Data synchronization, file uploads/downloads | | "mediaPlayback" | Audio/video playback | | "phoneCall" | Ongoing phone calls | | "location" | Location tracking | | "connectedDevice" | Communication with external devices | | "mediaProjection" | Screen sharing/recording | | "camera" | Camera access | | "microphone" | Microphone access | | "health" | Health/fitness data | | "remoteMessaging" | Push messaging | | "systemExempted" | System-critical operations | | "shortService" | Short-lived tasks (< 3 minutes) | | "specialUse" | Custom use cases (requires Play Console justification) | | "mediaProcessing" | Media transcoding/processing |

Permissions

Add these to your app's capability configuration:

{
  "permissions": [
    "background-service:allow-start",
    "background-service:allow-stop",
    "background-service:allow-is-running",
    "background-service:allow-get-service-state"
  ]
}

For desktop service mode, also add:

"background-service:allow-install-service",
"background-service:allow-uninstall-service"

Platform Notes

Android

The plugin uses a Foreground Service with a persistent notification to keep the process alive. Required additions to your app's AndroidManifest.xml (the plugin's manifest already declares these):

  • FOREGROUND_SERVICE and FOREGROUND_SERVICE_DATA_SYNC permissions
  • POST_NOTIFICATIONS runtime permission (requested automatically on Android 13+)
  • foregroundServiceType="dataSync" on the service declaration (see Android Guide for all 14 valid types)
  • stopWithTask="false" ensures the service survives when the user swipes the app away
  • START_STICKY causes the OS to restart the service if killed under memory pressure

When the service is restarted by the OS, the Rust process is new. Persist any state you need to restore in run() and reload it in init().

iOS

iOS background execution is best-effort. The plugin uses BGTaskScheduler to request periodic execution windows (~30 seconds every 15+ minutes). Required Info.plist additions:

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER).bg-refresh</string>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER).bg-processing</string>
</array>
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>processing</string>
</array>

While the app is foregrounded, your run() loop executes continuously. When backgrounded, Tokio freezes after ~30 seconds. Design your service to handle intermittent execution windows gracefully.

Desktop (Windows, macOS, Linux)

No special OS integration is needed. The service runs as a standard Tokio task and continues as long as the app process is alive.

For OS-level daemon mode (systemd / launchd), enable the desktop-service Cargo feature:

tauri-plugin-background-service = { version = "0.5", features = ["desktop-service"] }

Links

Documentation (relative paths — works on GitHub and crates.io):

Community (absolute URLs — required for crates.io compatibility):

License

SPDX-License-Identifier: MIT OR Apache-2.0