Live Activities
Drive iOS Live Activities and Dynamic Island surfaces from a Capacitor app.
@capgo/capacitor-live-activities starts, updates, and ends iOS Live Activities from JavaScript. It lets a Capacitor app expose important live state outside the WebView, including Lock Screen and Dynamic Island surfaces.
Use it for flows where the user should keep seeing progress after leaving the app: delivery tracking, workouts, timers, rides, orders, audio sessions, sports scores, or any state that should remain glanceable.
Demo

What It Does
- Starts Live Activities with a layout and data payload.
- Updates an existing activity when app state changes.
- Ends an activity when the live flow is complete.
- Supports Dynamic Island layouts for expanded, compact, and minimal states.
- Can save images into a shared App Group container for Live Activity rendering.
- Includes timer sequence helpers for workouts, countdowns, and step-based flows.
What It Does Not Replace
- It does not render normal in-app screens.
- It does not replace push notifications.
- It does not remove the need for native iOS configuration.
- It is not a cross-platform UI component for Android and Web. Android support is limited to fallback notification-style behavior for specific timer flows.
Installation
npm install @capgo/capacitor-live-activities
npx cap synciOS Setup
Installing and syncing the plugin does not create the native Live Activity UI. ActivityKit requires a Widget Extension that registers a Live Activity configuration before startActivity() can display anything.
Requirements
- Use iOS 16.1 or later for both the app target and Widget Extension target.
- Test on an iOS device or a compatible simulator. The Dynamic Island only appears on supported device models; other devices use the Lock Screen presentation.
- Keep the combined static and dynamic ActivityKit data below Apple's 4 KB limit.
1. Create a Widget Extension
Open the native iOS project:
bunx cap open iosThen:
- Select File > New > Target.
- Add a Widget Extension.
- Enable Include Live Activity.
- Disable Include Configuration Intent unless the app also needs a configurable widget.
- Ensure the generated extension is embedded in the main app target.
The Widget Extension must contain an ActivityConfiguration and register it in its WidgetBundle. It must provide every required Live Activity presentation:
- Lock Screen
- Dynamic Island expanded
- Dynamic Island compact leading and trailing
- Dynamic Island minimal
Adding the target alone is not sufficient. The native app or plugin must call ActivityKit's request, update, and end APIs. The extension must contain SwiftUI code that can decode and render the same ActivityAttributes and content state used by those calls. Include shared ActivityKit models in both the main app and Widget Extension targets.
The Xcode-generated Live Activity template does not automatically render the JSON layouts passed to this plugin. The extension also needs a compatible native layout renderer.
2. Enable Live Activities
Add the following key to the main app target's Info.plist:
<key>NSSupportsLiveActivities</key>
<true/>If the project generates its Info.plist, add Supports Live Activities with a Boolean value of YES under the main app target's custom iOS target properties instead.
3. Configure the App Group for Shared Images
An App Group is only required when using saveImage(), removeImage(), listImages(), or cleanupImages(). The plugin derives the App Group identifier from the main app bundle identifier using this exact format:
group.<MAIN_APP_BUNDLE_ID>.liveactivitiesFor example, an app with the bundle identifier com.example.delivery must use:
group.com.example.delivery.liveactivitiesIn Xcode, add the App Groups capability to both the main app target and the Widget Extension target, then enable the same identifier on both targets.
Live Activity extensions cannot access the network. Download remote images in the main app and save them to the shared App Group before referencing them from a Live Activity. For bundled images, also enable the Widget Extension in the asset's target membership.
4. Configure Deep Links
When using behavior.widgetUrl or a timer sequence tapUrl, register the matching URL scheme or Universal Link in the main app. For a custom scheme such as myapp://order/12345, add the scheme under the main app target's Info > URL Types settings.
5. Optional: Enable Server-Driven Updates
Push Notifications are not required for local updates initiated by the app. To start, update, or end Live Activities from a server:
- Add the Push Notifications capability to the main app target.
- Obtain ActivityKit push tokens and send them to the server.
- Send ActivityKit notifications through APNs using the
liveactivitypush type. - Add
NSSupportsLiveActivitiesFrequentUpdatesto the main appInfo.plistonly when the use case requires frequent push updates.
ActivityKit push tokens are separate from standard user-notification device tokens. Enabling the Push Notifications capability alone is not sufficient; server-driven updates require native token handling and an APNs backend.
Native Setup Checklist
Before calling startActivity(), verify that:
NSSupportsLiveActivitiesis enabled on the main app target.- The Widget Extension is embedded and registers an
ActivityConfiguration. - The native ActivityKit implementation and Widget Extension use the same
ActivityAttributestype. - The app and Widget Extension deployment targets are iOS 16.1 or later.
- Live Activities are enabled for the app in iOS Settings.
- The matching App Group is enabled on both targets when using shared images.
- Any custom URL scheme used by
widgetUrlortapUrlis registered.
Minimal Usage
Start a Live Activity with a simple layout and dynamic data:
import { CapgoLiveActivities } from '@capgo/capacitor-live-activities';
const { activityId } = await CapgoLiveActivities.startActivity({
layout: {
type: 'container',
direction: 'horizontal',
children: [
{ type: 'text', content: 'Order #{{orderNumber}}', fontSize: 16, fontWeight: 'bold' },
{ type: 'text', content: '{{status}}', fontSize: 14, color: '#666666' },
],
},
dynamicIslandLayout: {
expanded: {
leading: { type: 'image', source: 'sfSymbol', value: 'box.truck' },
trailing: { type: 'text', content: '{{eta}}' },
center: { type: 'text', content: '{{status}}' },
bottom: { type: 'progress', value: 'progress' },
},
compactLeading: { type: 'image', source: 'sfSymbol', value: 'box.truck' },
compactTrailing: { type: 'text', content: '{{eta}}' },
minimal: { type: 'image', source: 'sfSymbol', value: 'box.truck' },
},
data: {
orderNumber: '12345',
status: 'On the way',
eta: '10 min',
progress: 0.6,
},
});Update the activity when the state changes:
await CapgoLiveActivities.updateActivity({
activityId,
data: {
status: 'Arrived',
eta: 'Now',
progress: 1,
},
});End it when the flow is done:
await CapgoLiveActivities.endActivity({
activityId,
data: { status: 'Delivered' },
});Platforms
| Platform | Role |
|---|---|
| iOS | Primary target. Uses Live Activities and Dynamic Island when supported. |
| Android | Fallback behavior for timer-style foreground notification flows. |
| Web | Not a meaningful UI target for Live Activities. |
Core API
| Method or event | Usage |
|---|---|
areActivitiesSupported() | Checks whether Live Activities can run on the current device. |
startActivity() | Starts a new Live Activity with layout and data. |
updateActivity() | Updates an existing Live Activity. |
endActivity() | Ends a Live Activity. |
getAllActivities() | Lists currently active activities. |
saveImage() | Saves an image for Live Activity rendering. |
startTimerSequence() | Starts a timer sequence for workout or countdown flows. |