Reference version

This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Expo Widgets

A library to build iOS home screen widgets and Live Activities using Expo UI components.

Android
iOS

expo-widgets enables the creation of iOS home screen widgets and Live Activities using Expo UI components, without writing native code. It provides a simple API for creating and updating a widget's timeline, as well as starting and managing Live Activities. You can build the layout using expo/ui components and modifiers.

Watch: How to build an iOS widget
Watch: How to build an iOS widget

Build a native iOS home screen widget in TypeScript with expo-widgets.

Known limitations

  • Widget runtime. Code inside a 'widget'-marked component runs in an isolated runtime and can only use @expo/ui/swift-ui components, with no React hooks, app state, or asynchronous work. See The 'widget' directive.

Installation

Terminal
- npx expo install expo-widgets
- yarn expo install expo-widgets
- pnpm expo install expo-widgets
- bun expo install expo-widgets

If you are installing this in an existing React Native app, make sure to install expo in your project.

Configuration in app config

You can configure expo-widgets using its built-in config plugin if you use config plugins in your project (Continuous Native Generation (CNG)). The plugin allows you to configure various properties that cannot be set at runtime and require building a new app binary to take effect.

Example app.json with config plugin

app.json
{ "expo": { "plugins": [ [ "expo-widgets", { "widgets": [ { "name": "MyWidget", "displayName": "My Widget", "description": "A sample home screen widget", "ios": { "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"] } } ] } ] ] } }

Configurable properties

NameDefaultDescription
bundleIdentifier"<app bundle identifier>.ExpoWidgetsTarget"

The bundle identifier for the widget extension target. If not specified, defaults to <main app bundle identifier>.ExpoWidgetsTarget.

groupIdentifier"group.<app bundle identifier>"

The app group identifier used for communication and data sharing between the main app and widgets, which widgets require to work. If not specified, it defaults to group.<main app bundle identifier>. If ios.bundleIdentifier is also unset, prebuild fails because the bundle identifier is required to derive this value.

enablePushNotificationsfalse

Whether to enable custom notifications on Android and push notifications for Live Activities on iOS. On Android, this adds the POST_NOTIFICATIONS permission to AndroidManifest.xml. On iOS, this adds the aps-environment entitlement and sets ExpoWidgets_EnablePushNotifications in the Info.plist.

frequentUpdatesfalse

Whether an app can update its Live Activities frequently. When enabled, your Live Activities can receive frequent updates with remote push notifications. This sets NSSupportsLiveActivitiesFrequentUpdates in the Info.plist.

widgets-

An array of widget configurations. Each widget in the array will be generated as a separate widget kind in your widget extension.

widgets[].name-

The internal name (identifier) of the widget. This is used as the Swift struct name and should be a valid Swift identifier (no spaces or special characters). It must match the name passed to createWidget.

widgets[].displayName-

The user-facing name of the widget that appears in the widget gallery when users add widgets to their home screen.

widgets[].description-

A brief description of what the widget does. This appears in the widget gallery to help users understand the widget's purpose.

widgets[].ios.supportedFamilies-

An array of widget sizes that this widget supports. Available options:

  • systemSmall - Small square widget (2x2 grid)
  • systemMedium - Medium rectangular widget (4x2 grid)
  • systemLarge - Large square widget (4x4 grid)
  • systemExtraLarge - Extra large widget (iPad only, 6x4 grid)
  • accessoryCircular - Circular widget for Lock Screen
  • accessoryRectangular - Rectangular widget for Lock Screen
  • accessoryInline - Inline text widget for Lock Screen
widgets[].ios.contentMarginsDisabledfalse

When you disable content margins for a widget, the system doesn't automatically add margins around the widget's content, and you are responsible for specifying margins and padding around your widget content for each context.

widgets[].ios.initialLayout-

A path to the file that registers this widget with createWidget. The path is relative to the project root. Set this so the widget appears in the widget gallery before the app is open for the first time.

widgets[].ios.configuration-

Makes the widget configurable. The values the user picks are passed to your widget at runtime through environment.configuration. An object with:

  • title - The title shown when the user edits the widget.
  • description - An optional description shown when the user edits the widget.
  • parameters - A map of parameter keys to parameter definitions. Each parameter has a title, a type (string, number, boolean, or enum), and a default. An enum parameter additionally takes a values array of { name, value } options and can set dynamic: true to let the app replace those options at runtime.

Full example with all options

app.json
{ "expo": { "plugins": [ [ "expo-widgets", { "bundleIdentifier": "com.example.myapp.widgets", "groupIdentifier": "group.com.example.myapp", "enablePushNotifications": true, "widgets": [ { "name": "StatusWidget", "displayName": "Status", "description": "Shows your current status at a glance", "ios": { "contentMarginsDisabled": true, "supportedFamilies": ["systemSmall", "systemMedium"] } }, { "name": "WeatherWidget", "displayName": "Weather", "description": "Shows the weather for a city you choose", "ios": { "supportedFamilies": ["systemSmall", "systemMedium"], "configuration": { "title": "Choose a city", "description": "Pick which city to show the weather for", "parameters": { "city": { "title": "City", "type": "enum", "default": "sf", "values": [ { "name": "San Francisco", "value": "sf" }, { "name": "New York", "value": "nyc" } ], "dynamic": true } } } } }, { "name": "LockScreenWidget", "displayName": "Quick View", "description": "View info on your Lock Screen", "ios": { "supportedFamilies": [ "accessoryCircular", "accessoryRectangular", "accessoryInline" ] } } ] } ] ] } }

Usage

The 'widget' directive

Components passed to createWidget and createLiveActivity must start with the 'widget' directive. The directive tells the bundler to compile that component into a separate JavaScript bundle that runs in an isolated runtime inside the widget extension, not in your app's React Native runtime.

Because of this isolation, code inside a 'widget'-marked component is limited:

  • It can only render @expo/ui/swift-ui components and modifiers. Standard React Native components (such as View and Text from react-native) are not available.
  • It cannot use React hooks (useState, useEffect, and others), component state, or context. The function must be pure and return its layout synchronously.
  • It cannot perform asynchronous work, import other modules, or access your app's runtime or in-memory state.
  • It cannot reference anything declared outside the component function, including plain top-level consts in the same file. The bundler serializes only the function body, so module-scope values are not present at runtime. Declare every constant and helper inside the widget function, or pass it in through props.

All data a widget needs must come in through its props (set with updateSnapshot, updateTimeline, or a Live Activity's start and update) and the environment argument. To use images, write them to widgetsDirectory from your app and reference them by path.

import { Text } from '@expo/ui/swift-ui'; import { createWidget, type WidgetEnvironment } from 'expo-widgets'; // Declared at module scope — not included in the widget bundle. const CITY_NAMES: Record<string, string> = { sf: 'San Francisco' }; const CityWidget = (props: object, environment: WidgetEnvironment<{ city: string }>) => { 'widget'; // Throws at runtime: Can't find variable: CITY_NAMES return <Text>{CITY_NAMES[environment.configuration.city]}</Text>; }; export default createWidget('CityWidget', CityWidget);

Move CITY_NAMES inside CityWidget (or pass the resolved value through props) to fix it.

Widgets

Prerequisite: Creating a widget

Start by creating a widget using the createWidget function and pass the widget component marked with the 'widget' directive. The component receives your widget props as the first argument and a WidgetEnvironment object as the second.

import { Text, VStack } from '@expo/ui/swift-ui'; import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers'; import { createWidget, type WidgetEnvironment } from 'expo-widgets'; type MyWidgetProps = { count: number; }; const MyWidget = (props: MyWidgetProps, environment: WidgetEnvironment) => { 'widget'; return ( <VStack> <Text modifiers={[font({ weight: 'bold', size: 16 }), foregroundStyle('#000000')]}> Count: {props.count} </Text> <Text>Family: {environment.widgetFamily}</Text> </VStack> ); }; export default createWidget('MyWidget', MyWidget, { count: 0 });

The widget name ('MyWidget') must match the name field in your widget configuration in the app config. The optional third argument provides the initial props used before the widget timeline is updated.

Basic widget

An effective way to update a widget is to use the updateSnapshot method. This creates a widget timeline with a single entry that displays immediately.

The example below continues from Creating a widget.

import MyWidget from './MyWidget'; // Update the widget MyWidget.updateSnapshot({ count: 5 });

Timeline widget

Use the updateTimeline method to schedule widget updates at a specific time. The system automatically updates the widget based on the timeline.

The example below continues from Creating a widget.

import MyWidget from './MyWidget'; MyWidget.updateTimeline([ { date: new Date(), props: { count: 1 } }, { date: new Date(Date.now() + 3600000), props: { count: 2 } }, // 1 hour from now { date: new Date(Date.now() + 7200000), props: { count: 3 } }, // 2 hours from now { date: new Date(Date.now() + 10800000), props: { count: 4 } }, // 3 hours from now ]);

Reading the current timeline

Use getTimeline to read the entries currently scheduled for a widget, including past and future entries.

import MyWidget from './MyWidget'; const entries = await MyWidget.getTimeline(); // [{ date: Date, props: { count: number } }, ...]

Reloading a widget

Use reload to force the system to refresh a widget's content and timeline immediately, for example after the underlying data changes.

import MyWidget from './MyWidget'; MyWidget.reload();

Responsive widget

Use the environment argument to adapt the layout to the current widget size and rendering context.

import { HStack, Text, VStack } from '@expo/ui/swift-ui'; import { createWidget, type WidgetEnvironment } from 'expo-widgets'; type WeatherWidgetProps = { temperature: number; condition: string; }; const WeatherWidget = (props: WeatherWidgetProps, environment: WidgetEnvironment) => { 'widget'; // Render different layouts based on size if (environment.widgetFamily === 'systemSmall') { return ( <VStack> <Text>{props.temperature}°</Text> </VStack> ); } if (environment.widgetFamily === 'systemMedium') { return ( <HStack> <Text>{props.temperature}°</Text> <Text>{props.condition}</Text> </HStack> ); } // systemLarge and others return ( <VStack> <Text>Temperature: {props.temperature}°</Text> <Text>Condition: {props.condition}</Text> <Text>Updated: {environment.date.toLocaleTimeString()}</Text> </VStack> ); }; const Widget = createWidget('WeatherWidget', WeatherWidget); export default Widget; Widget.updateSnapshot({ temperature: 72, condition: 'Sunny', });

Adapting to the rendering environment

Beyond widgetFamily and date, the environment object describes how and where the system is drawing the widget so you can adapt the layout:

  • colorScheme: 'light' or 'dark'.
  • widgetRenderingMode: 'fullColor' for home screen widgets, 'vibrant' for Lock Screen widgets (the system desaturates them into an adaptive monochrome look), and 'accented' for tinted widgets on iOS 18 and later. Use this to choose colors that read well in each mode.
  • isLuminanceReduced: true when the display requires reduced brightness (such as Always-On). Lower the overall brightness of your content, for example by using stroked rather than filled shapes.
  • widgetContentMargins: The system-suggested margins (top, bottom, leading, trailing) when content margins are not disabled.
  • showsWidgetLabel: For accessory widgets, whether an accessory label can be displayed.

Interactive widgets

Widgets can include interactive controls such as Button. The value a button's onPress callback returns becomes the widget's new props. The runtime persists it and reloads the widget on device, with no running app process required. This is the primary way to make a widget update itself in response to a tap. Interactive widgets require iOS 17 or later.

CounterWidget.tsx
import { Button, Text, VStack } from '@expo/ui/swift-ui'; import { createWidget } from 'expo-widgets'; type CounterProps = { count: number; }; const CounterWidget = (props: CounterProps) => { 'widget'; return ( <VStack> <Text>Count: {props.count}</Text> <Button label="Increment" target="increment" onPress={() => ({ count: props.count + 1 })} /> </VStack> ); }; export default createWidget('CounterWidget', CounterWidget, { count: 0 });

To also keep your running app in sync with widget interactions, give the control a target identifier (as above) and listen for taps with addUserInteractionListener. The listener receives the widget's name as source and the control's target. Unlike onPress, it only fires while the app process is alive, so use it to mirror interactions into app state, not as the widget's update mechanism.

App.tsx
import { addUserInteractionListener } from 'expo-widgets'; const subscription = addUserInteractionListener(event => { if (event.source === 'CounterWidget' && event.target === 'increment') { // The widget already updated itself via onPress; mirror the change in app state here. console.log('Counter incremented from the widget'); } }); // Later, when you no longer need updates: subscription.remove();

Sharing images with widgetsDirectory

A widget can't access files inside your app's sandbox, so to display an image in a widget you must place it in the shared app group container. widgetsDirectory is a file:// URL string pointing to a directory that both your app and its widgets can read. Write the image there from your app, then reference it by path in the widget.

import { widgetsDirectory } from 'expo-widgets'; // `widgetsDirectory` is a file:// URL to a directory shared with your widgets. console.log(widgetsDirectory);

Configurable widgets

When you add an ios.configuration to a widget, users can long-press the widget and edit its parameters. The values they choose are delivered to your widget through environment.configuration. Type the configuration by passing a second type argument to createWidget (and WidgetEnvironment). Configurable widgets require iOS 17 or later.

import { Text, VStack } from '@expo/ui/swift-ui'; import { createWidget, type WidgetEnvironment } from 'expo-widgets'; type WeatherProps = { temperature: number; }; type WeatherConfiguration = { city: string; }; const WeatherWidget = ( props: WeatherProps, environment: WidgetEnvironment<WeatherConfiguration> ) => { 'widget'; return ( <VStack> <Text>{environment.configuration.city}</Text> <Text>{props.temperature}°</Text> </VStack> ); }; export default createWidget<WeatherProps, WeatherConfiguration>('WeatherWidget', WeatherWidget);

Dynamic enum options

Set dynamic: true on an enum parameter in the app config when the list of choices is only known at runtime, such as workspaces loaded after sign-in. The values array in app config is used as the fallback list until the app provides runtime options as shown below:

WeatherWidget.setConfigurationParameterEnum('city', [ { name: 'Current City', value: 'current' }, { name: 'San Francisco', value: 'sf' }, { name: 'New York', value: 'nyc' }, ]);

Live Activities

Live Activities display real-time information on the Lock Screen and in the Dynamic Island on supported devices.

Prerequisite: Creating a Live Activity

Live Activity layouts must be created once using createLiveActivity and marked with the 'widget' directive. The component receives your props as the first argument and a LiveActivityEnvironment object as the second. It returns an object describing the layout for each presentation: the Lock Screen banner, the compact and minimal Dynamic Island states, and the expanded Dynamic Island regions.

import { Image, Text, VStack } from '@expo/ui/swift-ui'; import { font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers'; import { createLiveActivity, type LiveActivityEnvironment } from 'expo-widgets'; type DeliveryActivityProps = { etaMinutes: number; status: string; }; const DeliveryActivity = (props: DeliveryActivityProps, environment: LiveActivityEnvironment) => { 'widget'; const accentColor = environment.isLuminanceReduced ? '#FFFFFF' : '#007AFF'; return { banner: ( <VStack modifiers={[padding({ all: 12 })]}> <Text modifiers={[font({ weight: 'bold' }), foregroundStyle(accentColor)]}> {props.status} </Text> <Text>Estimated arrival: {props.etaMinutes} minutes</Text> </VStack> ), compactLeading: <Image systemName="box.truck.fill" color={accentColor} />, compactTrailing: <Text>{props.etaMinutes} min</Text>, minimal: <Image systemName="box.truck.fill" color={accentColor} />, expandedLeading: ( <VStack modifiers={[padding({ all: 12 })]}> <Image systemName="box.truck.fill" color={accentColor} /> <Text modifiers={[font({ size: 12 })]}>Delivering</Text> </VStack> ), expandedTrailing: ( <VStack modifiers={[padding({ all: 12 })]}> <Text modifiers={[font({ weight: 'bold', size: 20 })]}>{props.etaMinutes}</Text> <Text modifiers={[font({ size: 12 })]}>minutes</Text> </VStack> ), expandedBottom: ( <VStack modifiers={[padding({ all: 12 })]}> <Text>Driver: John Smith</Text> <Text>Order #12345</Text> </VStack> ), }; }; export default createLiveActivity('DeliveryActivity', DeliveryActivity);

The layout object supports these regions:

  • banner: The main Lock Screen presentation.
  • bannerSmall: A compact Lock Screen presentation used on CarPlay and watchOS. Falls back to banner when omitted.
  • compactLeading, compactTrailing, minimal: The compact and minimal Dynamic Island states.
  • expandedLeading, expandedTrailing, expandedCenter, expandedBottom: The regions of the expanded Dynamic Island.

The environment object also exposes isLuminanceReduced, isActivityFullscreen, isActivityUpdateReduced, and activityFamily so you can adapt the layout to the current presentation.

Starting a Live Activity

The example below continues from Creating a Live Activity.

import { Button, View } from 'react-native'; import DeliveryActivity from './DeliveryActivity'; function App() { const startDeliveryTracking = () => { // Start the Live Activity const instance = DeliveryActivity.start( { etaMinutes: 15, status: 'Your delivery is on the way', }, 'myapp://deliveries/12345' ); // Store instance }; return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Start delivery tracking" onPress={startDeliveryTracking} /> </View> ); } export default App;

The optional second argument is a URL associated with the Live Activity. When the user taps the activity, the system opens your app with that URL, so you can route to the relevant screen using linking (for example with Expo Router's deep linking).

Updating a Live Activity

The example below continues from Starting a Live Activity.

import { LiveActivity } from 'expo-widgets'; function updateDelivery(instance: LiveActivity<DeliveryActivityProps>) { instance.update({ etaMinutes: 2, status: 'Delivery arriving soon!', }); }

Recovering active Live Activities

A Live Activity can outlive the app process that started it. Use getInstances on the factory to retrieve the activities of that type that are currently active, for example to update or end them after your app relaunches.

import DeliveryActivity from './DeliveryActivity'; const activeInstances = DeliveryActivity.getInstances(); for (const instance of activeInstances) { await instance.update({ etaMinutes: 5, status: 'Almost there' }); }

Ending a Live Activity

Use end to finish a Live Activity. You can choose the dismissal policy, optionally provide a final content state, and pass a contentDate so the system can ignore stale updates.

import { after, type LiveActivity } from 'expo-widgets'; async function completeDelivery(instance: LiveActivity<DeliveryActivityProps>) { await instance.end( after(new Date(Date.now() + 15 * 60 * 1000)), { etaMinutes: 0, status: 'Delivered', }, new Date() ); }

You can also pass 'default' or 'immediate' instead of after(date) for the dismissal policy.

Remote updates with push notifications

When enablePushNotifications is true, you can update Live Activities remotely from your server through Apple Push Notification service (APNs).

  • Use addPushToStartTokenListener to receive the app-wide push-to-start token, which lets your server start a Live Activity remotely (requires iOS 17.2 or later).
  • Use instance.getPushToken() or instance.addPushTokenListener() to obtain the token for a specific running Live Activity, which lets your server send updates to that activity.
import { addPushToStartTokenListener } from 'expo-widgets'; import DeliveryActivity from './DeliveryActivity'; const pushToStartSubscription = addPushToStartTokenListener(event => { console.log('Push-to-start token:', event.activityPushToStartToken); }); async function startDeliveryTracking() { const instance = DeliveryActivity.start({ etaMinutes: 15, status: 'Your delivery is on the way', }); const pushToken = await instance.getPushToken(); console.log('Per-activity token:', pushToken); const subscription = instance.addPushTokenListener(event => { console.log('Updated push token:', event.activityId, event.pushToken); }); // Later, when you no longer need updates: subscription.remove(); } // Later, when you no longer need updates: pushToStartSubscription.remove();

Send the token to your server and use it to push updates. The notification must use the liveactivity push type (the apns-push-type header) and an apns-topic of <your bundle identifier>.push-type.liveactivity. Its aps payload carries an event (start, update, or end), a timestamp, and a content-state that matches your activity's props. The content-state must match the internal content state used by expo-widgets: set name to the name you passed to createLiveActivity, and set props to a JSON string of that activity's props. Use apns-priority: 10 for immediate updates and apns-priority: 5 for lower-priority updates. The timestamp, dismissal-date, and other APNs date fields are Unix timestamps in seconds.

To start a Live Activity remotely, send a start event to the push-to-start token:

{ "aps": { "timestamp": 1778832000, "event": "start", "attributes-type": "LiveActivityAttributes", "attributes": {}, "content-state": { "name": "DeliveryActivity", "props": "{\"etaMinutes\":15,\"status\":\"Your delivery is on the way\"}" }, "alert": { "title": "Delivery started", "body": "Your delivery is on the way" } } }

Remote start requires iOS 17.2 or later. Include input-push-token: 1 in the aps payload on iOS 18 or later if you want APNs to provide a new per-activity token for future updates.

To update a Live Activity remotely, send an update event to that activity's per-activity token:

{ "aps": { "timestamp": 1778832300, "event": "update", "content-state": { "name": "DeliveryActivity", "props": "{\"etaMinutes\":2,\"status\":\"Delivery arriving soon!\"}" } } }

To end a Live Activity remotely, send an end event with the final content state:

{ "aps": { "timestamp": 1778832600, "event": "end", "content-state": { "name": "DeliveryActivity", "props": "{\"etaMinutes\":0,\"status\":\"Delivered\"}" }, "dismissal-date": 1778833200 } }

For the exact payload shape and headers, follow Apple's Starting and updating Live Activities with ActivityKit push notifications.

API

import { createWidget, createLiveActivity } from 'expo-widgets';

Constants

widgetsDirectory

Android
iOS

Type: string

A directory that can be used to store shared images for widgets. The contents of this directory are accessible by both the main app and widgets.

Classes

CustomNotification

Android

A posted custom notification. It wraps a native object that knows the notification key, so an instance recovered with CustomNotificationFactory.get() or getActive() after a process restart behaves like the one returned by post().

CustomNotification Properties

id

Android
Type: string

The key of the notification, as passed to or generated by post().

CustomNotification Methods

cancel()

Android

Removes the notification from the shade immediately. Cancels any pending post or update for this notification.

Returns:
Promise<void>

getProps()

Android

Reads the props currently shown by the notification from the active notification, so they include changes made by button presses inside the notification.

Rejects when the notification is no longer active, for example after dismissal or cancellation.

Returns:
Promise<T>

A promise that resolves to the current props.

update(props, options)

Android
ParameterTypeDescription
propsT

The new props for the layout function.

options(optional)CustomNotificationUpdateOptions

Presentation options to change. Options that are omitted keep their previous value.

Default:{}

Renders the layout with new props and replaces the notification in place. The notification does not alert again when alertOnlyOnce is true.

Rejects when the notification is no longer active, for example after the user dismissed it or after cancel(). Post it again in that case. Also rejects when the notification is canceled while the update renders, or for the same reasons as post(), such as an invalid color.

Returns:
Promise<void>

CustomNotificationFactory

Android

Posts and manages custom notifications that share one layout.

CustomNotificationFactory Properties

name

Android
Read only • Type: string

The name passed to createCustomNotification().

CustomNotificationFactory Methods

cancelAll()

Android

Removes every active notification posted with this layout and cancels pending posts and updates.

Returns:
Promise<void>

get(id)

Android
ParameterTypeDescription
idstring

The key passed to or generated by post().


Returns the active notification with the given key.

Returns:
Promise<CustomNotification<T> | null>

A promise that resolves to the notification, or to null when no such notification is active.

getActive()

Android

Returns every active notification posted with this layout, including notifications posted before a process restart.

post(props, options)

Android
ParameterTypeDescription
propsT

The props for the layout function.

options(optional)CustomNotificationOptions

Presentation options.

Default:{}

Renders the layout and posts the notification. Posting with the id of an active notification replaces that notification in place.

Rejects in these cases:

  • Notifications are disabled for the app.
  • channelId names a channel that does not exist.
  • smallIcon names a drawable that does not exist.
  • color is not in #RRGGBB or #AARRGGBB format.
  • The layout returns no collapsed section.
  • The post is canceled before rendering finishes.

A promise that resolves to a handle for the posted notification.

LiveActivity

iOS

Represents a Live Activity instance. Provides methods to update its content and end it.

LiveActivity Methods

addPushTokenListener(listener)

iOS
ParameterTypeDescription
listener(event: PushTokenEvent) => void

Callback invoked when a new push token is available.


Adds a listener for push token update events on this Live Activity instance. The token can be used to send content updates to this specific activity via APNs.

Returns:
EventSubscription

An event subscription that can be used to remove the listener.

end(dismissalPolicy, props, contentDate)

iOS
ParameterTypeDescription
dismissalPolicy(optional)LiveActivityDismissalPolicy

Controls when the Live Activity is removed from the Lock Screen after ending. Can be 'default', 'immediate', or after(date).

props(optional)T

Final content properties to update after the activity ends.

contentDate(optional)Date

The time the data in the payload was generated. If this is older than a previous update or push payload, the system ignores this update.


Ends the Live Activity.

Returns:
Promise<void>

getId()

iOS

Returns the stable ActivityKit identifier for this Live Activity.

Returns:
string

getPushToken()

iOS

Returns the push token for this Live Activity, used to send push notification updates via APNs. Returns null if push notifications are not enabled or the token is not yet available.

Returns:
Promise<string | null>

update(props, staleDate)

iOS
ParameterTypeDescription
propsT

The updated content properties.

staleDate(optional)Date

When set, the system may de-emphasize the activity after this date if content has not been refreshed.


Updates the Live Activity's content. The UI reflects the new properties immediately.

Returns:
Promise<void>

LiveActivityFactory

iOS

Manages Live Activity instances of a specific type. Use it to start new activities and retrieve currently active ones.

LiveActivityFactory Methods

getInstances()

iOS

Returns all currently active instances of this Live Activity type.

start(props, url, staleDate)

iOS
ParameterTypeDescription
propsT

The initial content properties for the Live Activity.

url(optional)string

An optional URL to associate with the Live Activity, used for deep linking.

staleDate(optional)Date

When set, the system may de-emphasize the activity after this date if content has not been refreshed.


Starts a new Live Activity with the given properties.

Returns:
LiveActivity<T>

The new Live Activity instance.

Widget

Android
iOS

Represents a widget instance. Provides methods to manage the widget's timeline.

Widget Methods

getTimeline()

iOS

Returns the current timeline entries for the widget, including past and future entries.

reload()

Android
iOS

Force reloads the widget, causing it to refresh its content and timeline.

Returns:
void

setConfigurationParameterEnum(parameterName, options)

iOS
ParameterType
parameterNamekeyof ConfigurationType & string
options(optional)WidgetConfigurationEnum[]

Replaces the runtime options for a dynamic enum configuration parameter. The app config values remain the fallback when no runtime options are set.

Returns:
void

updateSnapshot(props)

Android
iOS
ParameterTypeDescription
propsPropsType

The properties to display in the widget.


Sets the widget's content to the given props immediately, without scheduling a timeline.

Returns:
void

updateTimeline(entries)

iOS
ParameterTypeDescription
entriesWidgetTimelineEntry[]

Timeline entries, each specifying a date and the props to display at that time.


Schedules a series of updates for the widget's content and reloads the widget.

Returns:
void

Methods

createCustomNotification(name, layout)

Android
ParameterTypeDescription
namestring

A unique name for the layout. It identifies the layout when a notification button is pressed and when active notifications are recovered.

layoutCustomNotificationComponent<T>

The layout component, marked with the 'widget' directive.


Creates a factory that posts notifications rendered from the given layout.

The factory can be created on every platform. Its methods reject with UnavailabilityError on platforms other than Android.

Example

import { Row, Text } from '@expo/ui/jetpack-compose'; import { createCustomNotification } from 'expo-widgets'; type Props = { status: string }; const OrderNotification = createCustomNotification('OrderNotification', (props: Props) => { 'widget'; return { collapsed: ( <Row> <Text>{props.status}</Text> </Row> ), }; }); const order = await OrderNotification.post({ status: 'Preparing' }, { id: 'order-1', ongoing: true }); await order.update({ status: 'Delivered' }, { ongoing: false, timeoutAfter: 15 * 60 * 1000 });

createLiveActivity(name, liveActivity)

iOS
ParameterTypeDescription
namestring

The Live Activity name. Must match the 'name' field in your widget configuration in the app config.

liveActivityLiveActivityComponent<T>

The Live Activity component, marked with the 'widget' directive.


Creates a Live Activity Factory for managing Live Activities of a specific type.

createWidget(name, widget, initialProps)

Android
iOS
ParameterTypeDescription
namestring

The widget name. Must match the 'name' field in your widget configuration in the app config.

widget(props: PropsType, context: WidgetEnvironment<ConfigurationType>) => Element

The widget component, marked with the 'widget' directive.

initialProps(optional)PropsType

The initial properties to display before the widget timeline is updated.


Creates a Widget instance.

Returns:
Widget<PropsType, ConfigurationType>

Event subscriptions

addPushToStartTokenListener(listener)

iOS
ParameterTypeDescription
listener(event: PushToStartTokenEvent) => void

Callback function to handle push-to-start token events.


Adds a listener for push-to-start token events. This token can be used to start live activities remotely via APNs.

Returns:
EventSubscription

An event subscription that can be used to remove the listener.

addUserInteractionListener(listener)

Android
iOS
ParameterTypeDescription
listener(event: UserInteractionEvent) => void

Callback function to handle user interaction events.


Adds a listener for widget interaction events (for example, button taps).

Returns:
EventSubscription

An event subscription that can be used to remove the listener.

Types

CustomNotificationComponent(props, environment)

Android

A function that returns the views of a custom notification. It must start with the 'widget' directive and use @expo/ui/jetpack-compose components.

ParameterType
propsT
environmentCustomNotificationEnvironment

CustomNotificationEnvironment

Android

Values the system provides to a custom notification layout.

PropertyTypeDescription
colorScheme'light' | 'dark'

The color scheme of the device at render time.

CustomNotificationLayout

Android

The views of a custom notification. Each section is rendered into its own RemoteViews at a fixed size, so a section cannot depend on the size of another section.

PropertyTypeDescription
collapsedReactNode

The collapsed row shown in the notification shade. The system gives it about 64 density-independent pixels (dp) of height.

expanded(optional)ReactNode

The expanded view shown when the user expands the notification. The system gives it up to 256 dp of height. When omitted, the system derives the expanded view from collapsed.

headsUp(optional)ReactNode

The heads-up view shown when the notification peeks over the current screen. The system gives it about 88 dp of height. When omitted, the system reuses collapsed. The channel importance decides whether a heads-up view is shown at all.

CustomNotificationOptions

Android

Presentation options of a custom notification.

PropertyTypeDescription
alertOnlyOnce(optional)boolean

Whether sound, vibration, and the heads-up view are used only for the first post and not for updates.

Default:true
autoCancel(optional)boolean

Whether a tap removes the notification. Defaults to the opposite of ongoing.

channelId(optional)string

The notification channel the notification is posted to. The channel must exist, for example, one created with expo-notifications. Defaults to a low-importance channel with the ID expo_widgets_notifications, which expo-widgets creates on first use. The system settings list it as "Notifications".

color(optional)string

The accent color in #RRGGBB or #AARRGGBB format. Defaults to the color configured by the expo-notifications config plugin.

id(optional)string

The key of the notification. Posting with the key of an active notification replaces that notification in place. Defaults to a random UUID.

ongoing(optional)boolean

Whether the user cannot dismiss the notification with a swipe.

Default:false
smallIcon(optional)string

The name of a drawable resource used as the small icon. Defaults to the icon configured by the expo-notifications config plugin, and then to the app icon.

text(optional)string

The content text. The system uses it when it cannot show the custom views.

timeoutAfter(optional)number

Time in milliseconds after which the system removes the notification. Useful after a final update that sets ongoing to false.

title(optional)string

The content title. Screen readers announce it, and the system uses it when it cannot show the custom views.

url(optional)string

A deep link opened in this app when the user taps the notification. Defaults to the launch intent of the app.

CustomNotificationUpdateOptions

Android

Type: Omit<CustomNotificationOptions, 'id'>

Presentation options that an update can override. Options that an update omits keep the value from the previous post or update.

LevelOfDetail

iOS 26+

Literal type: string

The level of detail the view is recommended to have. The system can update the levelOfDetail value based on user proximity or other system specific factors and allow content customization adapting to show different levels of details.

  • simplified — The system recommends showing a simplified view with less details.
  • default — The system has no specific recommendation for the level of detail.

Acceptable values are: 'simplified' | 'default'

LiveActivityComponent(props, environment)

iOS

A function that returns the layout for a Live Activity.

ParameterType
propsT
environmentLiveActivityEnvironment

LiveActivityDismissalPolicy

iOS

Literal type: union

Dismissal policy for ending a live activity.

  • 'default' - The system’s default dismissal policy for the Live Activity.
  • 'immediate' - The system immediately removes the Live Activity that ended.
  • after(date) - The system removes the Live Activity that ended at the specified time within a four-hour window.

Acceptable values are: 'default' | 'immediate' | ReturnType<after>

LiveActivityEnvironment

iOS
PropertyTypeDescription
activityFamily(optional)ActivityFamily
Only for: 
iOS 18+

The size family of the current Live Activity.

colorScheme'light' | 'dark'
Only for: 
iOS

The color scheme of the activity's environment.

isActivityFullscreen(optional)boolean
Only for: 
iOS 16.1+

Whether the activity is currently displayed in fullscreen.

isActivityUpdateReduced(optional)boolean
Only for: 
iOS 18+

A Boolean value that indicates whether the Live Activity update synchronization rate is reduced.

isLuminanceReduced(optional)boolean
Only for: 
iOS 16+

Whether the activity is displayed in a context with reduced luminance.

isStale(optional)boolean
Only for: 
iOS 16.2+

Whether the activity's content is out of date, based on the staleDate passed to LiveActivityFactory.start() or LiveActivity.update().

Use it to de-emphasize content the system can no longer vouch for. It becomes true without the app running, so a Live Activity can degrade while the app is suspended.

levelOfDetail(optional)LevelOfDetail
Only for: 
iOS 26+

The level of detail the view is recommended to have.

LiveActivityEvents

iOS
PropertyTypeDescription
onExpoWidgetsTokenReceived(event: PushTokenEvent) => void

Function that is invoked when a push token is received for a live activity.

event: PushTokenEvent

Token event details.

LiveActivityLayout

iOS

Defines the layout sections for an iOS Live Activity.

PropertyTypeDescription
bannerReactNode

The main banner content displayed in Notifications Center.

bannerSmall(optional)ReactNode

The small banner content displayed in CarPlay and WatchOS. Falls back to banner if not provided.

compactLeading(optional)ReactNode

The leading content in the compact Dynamic Island presentation.

compactTrailing(optional)ReactNode

The trailing content in the compact Dynamic Island presentation.

expandedBottom(optional)ReactNode

The bottom content in the expanded Dynamic Island presentation.

expandedCenter(optional)ReactNode

The center content in the expanded Dynamic Island presentation.

expandedLeading(optional)ReactNode

The leading content in the expanded Dynamic Island presentation.

expandedTrailing(optional)ReactNode

The trailing content in the expanded Dynamic Island presentation.

minimal(optional)ReactNode

The minimal content shown when the Dynamic Island is in its smallest form.

PushTokenEvent

iOS

Event emitted when a push token is received for a live activity.

PropertyTypeDescription
activityIdstring

The ID of the live activity.

pushTokenstring

The push token for the live activity.

PushToStartTokenEvent

iOS

Event emitted when a push-to-start token is received.

PropertyTypeDescription
activityPushToStartTokenstring

The push-to-start token for starting live activities remotely.

UserInteractionEvent

Android
iOS

Event emitted when a user interacts with a widget.

PropertyTypeDescription
notificationId(optional)string
Only for: 
Android

The key of the custom notification that contains the button. Absent for widget interactions.

sourcestring

Widget that triggered the interaction.

targetstring

Button/toggle that was pressed.

timestampnumber

Timestamp of the event.

type'ExpoWidgetsUserInteraction'

The event type identifier.

WidgetConfigurationEnum

Android
iOS
PropertyTypeDescription
namestring

User-visible option label.

subtitle(optional)string
Only for: 
iOS

Optional secondary text displayed to user.

valuestring

Value available in environment.configuration.

WidgetEnvironment

Android
iOS
PropertyTypeDescription
colorScheme(optional)'light' | 'dark'

The color scheme of the widget's environment.

configurationT
Only for: 
iOS 17+

Widget configuration parameters.

date(optional)Date
Only for: 
iOS

The date of this timeline entry.

isLuminanceReduced(optional)boolean
Only for: 
iOS 16+

A Boolean value that indicates whether the display or environment currently requires reduced luminance.

When you detect this condition, lower the overall brightness of your view. For example, you can change large, filled shapes to be stroked, and choose less bright colors.

levelOfDetail(optional)LevelOfDetail
Only for: 
iOS 26+

The level of detail the view is recommended to have.

showsWidgetLabel(optional)boolean
Only for: 
iOS 16+

A Boolean value that indicates whether an accessory family widget can display an accessory label.

widgetContentMargins(optional){ bottom: number, leading: number, top: number, trailing: number }
Only for: 
iOS 17+

The content margins for the widget.

widgetFamily(optional)WidgetFamily
Only for: 
iOS

The widget family.

widgetRenderingMode(optional)WidgetRenderingMode
Only for: 
iOS 16+

The widget's rendering mode, based on where the system is displaying it.

WidgetFamily

iOS

Literal type: string

The widget family (size).

  • systemSmall - Small square widget (2x2 grid).
  • systemMedium - Medium widget (4x2 grid).
  • systemLarge - Large widget (4x4 grid).
  • systemExtraLarge - Extra large widget (iPad only, 6x4 grid).
  • accessoryCircular - Circular accessory widget for the Lock Screen.
  • accessoryRectangular - Rectangular accessory widget for the Lock Screen.
  • accessoryInline - Inline accessory widget for the Lock Screen.

Acceptable values are: 'systemSmall' | 'systemMedium' | 'systemLarge' | 'systemExtraLarge' | 'accessoryCircular' | 'accessoryRectangular' | 'accessoryInline'

WidgetRenderingMode

iOS

Literal type: string

The rendering mode of the widget as provided by WidgetKit.

  • fullColor — Home screen widgets (default).
  • accented — Tinted widgets (iOS 18+) and watchOS.
  • vibrant — Lock screen widgets.

Acceptable values are: 'fullColor' | 'accented' | 'vibrant'

WidgetTimelineEntry

iOS
PropertyTypeDescription
dateDate

Date when widget should update.

propsT

Props to be passed to the widget.