Live activities
A live activity shows a running task — a delivery, a timer, a download — outside the app.
| Platform | Surface |
|---|---|
| iOS 16.2+ | Live Activity on the Lock Screen and in the Dynamic Island |
| Android 8+ | Ongoing notification; Android 16 promotes it to a Live Update chip when the user allows |
| macOS / Windows / Linux | Dynamic-Island pill at the top centre (around the notch on notched Macs); expands on hover |
import { startActivity, updateActivity, endActivity } from "tauri-plugin-widgets-api";
await startActivity({
id: "order.42",
title: "Order #42",
body: "Driver en route",
icon: "bicycle",
progress: 0.3,
endsAt: Date.now() + 12 * 60_000,
actions: [{ id: "call", label: "Call driver" }],
});
await updateActivity({ id: "order.42", title: "Order #42", body: "Arriving", progress: 0.9 });
await endActivity("order.42", 5_000);
Without layout, the plugin builds one from title, body, icon, progress, endsAt
and actions, so a plain request looks alike everywhere. Pass layout (any widget
element) for your own design, and compactLeading / compactTrailing / minimal for the
Dynamic Island. The request must stay under 4 KiB of JSON — the ActivityKit limit,
checked on every platform so nothing works on desktop and fails on an iPhone. The check
counts the request including the layout the plugin generates when layout is omitted.
Taps on actions (Android buttons, buttons in your layout) arrive through
onWidgetAction with widgetId: "activity.<id>". url opens a deep link on tap.
iOS also reads relevanceScore (which activity the system shows first), alert
({ title, body, sound? }, lights the screen with the update) and timestamp (Unix ms;
an update older than the shown one is dropped). endActivity(id, dismissAfterMs, { content, dismissalPolicy }) ends with a final state; dismissalPolicy: "immediate" removes it at
once. onActivityState / onActivityContent report what the OS did (ended, dismissed,
push-updated).
Templates: data-only updates
Store the layout on the device once, then send only data. The update stays far below 4 KiB, which matters for frequent and push updates.
import { registerActivityTemplate, startActivity, updateActivity } from "tauri-plugin-widgets-api";
await registerActivityTemplate("delivery", {
title: "Order {{data.order}}",
lockScreen: {
type: "vstack",
children: [
{ type: "text", content: "{{data.driver}} arrives in {{data.eta}} min" },
{ type: "progress", value: 0, bind: { value: "data.progress" } },
{ type: "text", content: "Running late", when: { path: "data.late" } },
],
},
compactTrailing: { type: "text", content: "{{data.eta}}′", transition: "numeric" },
});
await startActivity({ id: "order.42", template: "delivery", data: { order: "#42", driver: "Ann", eta: 12, progress: 0.2 } });
await updateActivity({ id: "order.42", template: "delivery", data: { order: "#42", driver: "Ann", eta: 4, progress: 0.8 } });
A template takes the ActivityContent fields (title, layout, lockScreen, the island
regions, compactLeading / compactTrailing / minimal, …). Every string in it is
resolved with data like a widget config: {{data.x}}, bind, when, repeat. Fields
set on the request itself override the template's (title may then be omitted). The
template is kept in the shared store (activity-template:{name}, App Group on Apple);
registerActivityTemplate(name, null) removes it. Only the request as sent — template name
plus data — counts toward 4 KiB. Starting with an unknown template fails on desktop; on
iOS / Android the renderer shows what the request itself carries.
Setup
iOS. Add TauriLiveActivity() to the extension's WidgetBundle (the init-ios
template does) and NSSupportsLiveActivities = YES to the app's Info.plist (init-ios
adds it to project.yml). With push: true the result has pushToken and an
activity-token event follows when it changes — send it to your APNs server.
Android. On Android 13+ the app needs the POST_NOTIFICATIONS permission at runtime
(for example requestPermission() of @tauri-apps/plugin-notification). The plugin
declares it in its manifest.
Desktop. The island uses the built-in renderer; allow its window label in your
capability ("windows": ["main", "activity-*"]). On macOS enable the plugin feature
macos-private-api (and app.macOSPrivateApi: true) for the transparent, rounded pill. An
id started again while its endActivity(id, dismissAfterMs) delay runs stays on.
Dynamic Island
On iPhone and on desktop the activity shows in a Dynamic-Island pill with compact, minimal and expanded regions; see Dynamic Island for the regions, desktop placement and recordings.
Push updates (iOS)
startActivity({ …, push: true })returnspushToken; rotations arrive throughonActivityTokens(kind: "activity"). Send ActivityKit pushes (event: "update"/"end") withcontent-state: { "json": "<ActivityContent JSON>" }.- Push-to-start (iOS 17.2+):
getActivityPushToStartToken()andonActivityTokens(kind: "pushToStart"). Your server starts an activity withevent: "start",attributes-type: "TauriActivityAttributes",attributes: { "id": "...", "group": "group.…" }and the samecontent-state. Thejsonmust contain a filledlayout(send whatstartActivitywould send) or a registeredtemplatewithdata, at most 4 KiB. - Android and desktop return
nullfor push-to-start.
iOS: lifecycle, relevance, alerts, final content
- Events. Every activity the app sees (started here, in an earlier launch, or by
push-to-start) reports
activity-state{ id, state }(active,stale,ended,dismissed,pending) — the user can end it from the Lock Screen — andactivity-content{ id, content }when its content changes, pushes included. relevanceScore(number) decides which activity the Dynamic Island shows when several run (higher first).alert: { title, body, sound? }onupdateActivitylights the screen and shows the alert;soundis"default"or a sound file in the app bundle.timestamp(Unix ms): an update older than the last one applied is dropped (applied: false), so late network responses cannot roll the activity back. On iOS 17.2+ it is also passed to ActivityKit.- End with final content.
endActivityaccepts the finalcontent(shown until dismissed) anddismissalPolicy: "default" | "immediate"(dismissAfterMswins when set; without either it is dismissed at once, as before). - Frequent updates.
init-iosalso setsNSSupportsLiveActivitiesFrequentUpdates, which raises the hourly budget for push and local updates.