react-native-widget-extension, adding iOS widgets and live activities to an Expo app
Add widgets and live activities to a React Native app
At a glance
- What is it?
- react-native-widget-extension is an Expo config plugin that wires a folder of Swift files into a React Native app so it can show home screen widgets and Live Activities. The widget UI still has to be written in Swift, and the plugin handles the Xcode project surgery and the bridge that lets JavaScript start, update and end an activity.
- Who is it for?
- react-native-widget-extension suits an Expo or React Native team that wants home screen widgets and Live Activities without hand-editing the Xcode project, and it suits anyone who is prepared to write the widget views in Swift while keeping the app logic in TypeScript. It does not suit a bare React Native project with no Expo config plugin pipeline, and it does not cover Android.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 151 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The plugin is the small part, the Swift folder is the real work
react-native-widget-extension is described as an Expo config plugin to add widgets and live activities to a React Native app, and the very first thing the README says about the limits is the most important fact in the project.
The widgets still need to be written in Swift, and the author's parenthesis is worth quoting in spirit: there is no way around that.
That is a platform constraint rather than a limitation of this library. A home screen widget on iOS is an app extension, an extension target in the Xcode project with its own SwiftUI views, its own Info.plist and its own asset catalogue, and the system instantiates it in a separate process from your app. There is no React Native renderer inside an extension target. So the split this plugin accepts is unavoidable, and the question is only who does the Xcode project surgery.
The answer here is that the plugin does. The mechanism is the standard Expo config plugin: you add a folder with the Swift files to the project, install the plugin, and the plugin modifies the Xcode project during prebuild so the extension target exists and is wired into the app.
The installation is one command.
npx expo install react-native-widget-extensionThe peer dependencies in the package manifest explain the constraint that follows: `expo`, `react` and `react-native` are all peer dependencies with a wildcard version, and the single runtime dependency is `@expo/config-plugins` at roughly 56.0.0, with the dev dependencies on `expo-module-scripts`, `expo-modules-core` and TypeScript. So this is a plugin for the Expo pipeline specifically, not a general React Native library.
The library is written in TypeScript, the version is 0.3.0, and the recorded releases are v0.3.0 from 2026-05-25, v0.2.0 from 2025-10-07 and v0.1.1 from 2025-09-13. The last push to the repository was on 2026-05-25.
The app.json configuration and what each option does
The plugin is configured through the `expo` section of `app.json`, and the configuration is a plugin entry with an options object.
"expo": {
"name": "my-app",
"plugins": [
[
"react-native-widget-extension",
{ "frequentUpdates": true, "widgetsFolder": "SampleWidgetExtension" },
],
]
}The `widgetsFolder` option is the one that matters most, and it is the path from the project root to the folder containing the Swift widget files, with a default of `widgets`. Everything else is a detail of the generated Xcode project.
`frequentUpdates` is a boolean, default false, and it controls whether `NSSupportsLiveActivitiesFrequentUpdates` is set. That is the Info.plist key the system uses to decide whether an app is allowed to update a Live Activity frequently, and it is a permission the platform grants rather than a behaviour you can turn on freely.
`deploymentTarget` is a string with a default of `16.2`, and it is the minimum deployment target for the app. The reason for that default is explained further down in the README, and it is about Swift compilation rather than about the plugin.
`groupIdentifier` is optional, and it is the app group identifier required for communication with the main app. The README is explicit that it must start with `group.`, which is the Apple convention for app group identifiers. This is the mechanism by which the extension and the app share data, and without it the widget cannot read anything the app wrote, so it is the option to get right when the widget appears to show stale or empty data.
`keychainAccessGroup` is optional too, and it is for securely sharing secrets between the app and the extension. The README says it should start with your `<TEAM_ID>.` prefix, which is the Apple team identifier, since a keychain access group has to be scoped to your team for the sharing to work.
There is also a commented-out pair of options in the README, `moduleFileName` with a default of `Module.swift` and `attributesFileName`. Being commented out, they are not currently active configuration, which tells you the file naming is fixed at the moment rather than configurable.
Starting, updating and ending a Live Activity from JavaScript
The React-side API is what removes the need to write a native module by hand, and it is small.
import {
areActivitiesEnabled,
startActivity,
updateActivity,
endActivity,
} from "react-native-widget-extension";
startActivity(3, "4343", "$32.23", driverName, 47, 43);Four functions, and the shape of them tells you the model. `areActivitiesEnabled` asks the system whether Live Activities are available, which is a runtime question because the user can disable them in system settings and because the feature requires a recent enough OS. `startActivity` begins one with a set of values. `updateActivity` changes the running one. `endActivity` finishes it.
The `startActivity` call in the example takes a positional list of arguments, and the argument count and order are dictated by the `ActivityAttributes` struct you write in Swift. The example's arguments are a number, a string, a dollar-formatted string, a driver name, and two more numbers, which correspond to the pizza delivery attributes in the sample project.
That coupling is the thing to understand before you start. The TypeScript signature is not described in the README in terms of named fields, because the fields are yours. You define an `ActivityAttributes` type in Swift, and the plugin's native module exposes it to JavaScript, and the JavaScript call has to supply values in the same order with the same types. Changing the attributes means changing both sides.
The example project makes the file layout explicit, and it is the part of the plugin that does the most work for you.
The `Assets.xcassets` and `Info.plist` are created automatically by Xcode when you create a widget. `Attributes.swift` holds the ActivityAttributes for the Live Activity, and by default this file should be named `Attributes.swift`. `Module.swift` defines the native module used from React land to start, stop and update the live activity, and by default this file should be named `Module.swift`. The rest of the folder can be Swift files defining widgets and views, named and divided however you want.
So the plugin looks for two conventionally named files in your widget folder and wires the rest of the folder in as Swift sources. The naming convention is the API.
The iOS 16.2 floor and the Swift guard you have to write yourself
The deployment target section is the part of the README most likely to cost you an afternoon, and it is worth reading closely.
By default this module adds a minimum deployment target of iOS 16.2, because otherwise Swift compilation fails if you try to use Live Activities. That is a build-time failure rather than a runtime one, which is the friendlier kind, but it means an app that currently supports older iOS versions cannot use the plugin at all until it either raises its floor or takes the alternative route.
The alternative route is setting the deployment target manually through the plugin config.
"expo": {
"name": "my-app",
"plugins": [
[
"react-native-widget-extension",
{ "deploymentTarget": "14.0" },
],
]
}But lowering the floor does not remove the constraint, it moves it into your own Swift code. The README is explicit that if you do this and still use Live Activities in Swift, you have to guard the code that can only run on iOS 16.2 and later.
import SwiftUI
import WidgetKit
@main
struct PizzaDeliveryWidgetBundle: WidgetBundle {
var body: some Widget {
PizzaDeliveryWidgets()
if #available(iOS 16.2, *) {
PizzaDeliveryLiveActivity()
}
}
}And the view itself carries an availability annotation.
The same annotation goes on the live activity view itself, with an `@available(iOS 16.2, *)` attribute above the struct declaration.
The availability check in the widget bundle body and the attribute on the view are both needed, and the reason is the structure of the bundle. The `@main` widget bundle returns a single `some Widget` from its body, and Swift's result builder cannot return a different concrete type depending on a runtime condition. The `if #available` block handles the type difference, and the `@available` annotation on the concrete view keeps the compiler from letting you call its initialisers on an older OS.
So the plugin saves you the Xcode project work and the native module, and the platform still asks you to write the version guard yourself. That is the boundary of the tool: the project configuration is automated, the platform's own Swift rules are not.
Running the example, and what the repository is made of
For a minimal example app, the README points at the `example` folder, with example code for a widget and a live activity in `example/SampleWidgetExtension`. To run the example you need `bun run build` in the root directory.
That build step is the package manifest's build script, and the toolchain choice is visible in it.
"scripts": {
"ncu": "bun x npm-check-updates -p bun",
"build": "bun x tsc --build ./ ./plugin",
"dev": "bun x tsc --build ./ ./plugin --watch"
},Two TypeScript projects are compiled at once, the root and the `plugin` directory, with the plugin compiled separately because an Expo config plugin is loaded by the prebuild tooling as its own bundle rather than as part of the app's runtime code. The `dev` script adds watch mode to the same pair. The `ncu` script runs npm-check-updates against the bun package manager, which is the routine dependency-update check.
The published files tell you what actually ships. The `files` array lists `ios`, `plugin/build`, `build`, `README.md`, `expo-module.config.json` and `app.plugin.js`. So the package carries the native iOS side, the compiled plugin, the compiled library, and the two files the Expo tooling needs to find the plugin, namely the Expo module config and the plugin entry point.
The top-level repository entries match that shape: `app.plugin.js` and `expo-module.config.json` at the root for tooling discovery, an `ios/` directory for the native side, a `plugin/` directory for the config plugin itself, `src/` for the TypeScript library, and the `example/` folder with its own `app.json`, `babel.config.js`, `metro.config.js`, `webpack.config.js`, `eas.json` and its own `bun.lock`. The presence of an `eas.json` in the example is the sign that this is set up for EAS builds, where the prebuild step that runs the config plugin happens on the build server rather than on your machine.
The iOS side being a directory in the package rather than a compiled artefact is worth noting. It means the plugin ships Swift sources and lets Xcode compile them during the app build, which is how it can add an extension target, but it also means the package carries platform-specific code that only builds on a Mac with the right toolchain.
The limits and the licence question
Three boundaries are worth stating before you adopt this.
The first is iOS only, and specifically Live Activities, which are an Apple feature with no Android equivalent in this plugin. The package contains an `ios/` directory and no `android/`, and the whole configuration surface is about Info.plist keys and Xcode targets. If your app needs a home screen widget on both platforms, this handles one of them.
The second is the Swift requirement, which was covered above but deserves to be restated as a cost. The plugin removes the Xcode project wiring and the native module boilerplate, and it does not remove the need to write SwiftUI views, to define the ActivityAttributes, and to handle the iOS availability rules. For a team that is entirely TypeScript, that is a second language in the codebase for the widget layer.
The third is the licence, and it needs attention. The repository shows no licence type in its metadata, while the package manifest declares `"license": "MIT"` and the author as Benedikt Müller. Those are two different claims about the same repository, and the package manifest is the more specific one, but a licence file that is not classified in the repository is a gap worth closing before you depend on it. This is not legal advice, it is the reason to confirm the licence terms before you depend on the package.
The realistic alternatives are worth naming concretely. A bare React Native app can add an extension target to the Xcode project directly and write the native module by hand, which is more work but no plugin and no Expo dependency. A native Swift widget library can be integrated into a React Native app through a custom native module, which is the same Swift work without the Expo prebuild assumption. And on the Expo side, a custom config plugin written for your own project does the same job with only the options you actually need.
The difference in approach is that this plugin is a general, published one that assumes a conventional file layout and gives you five configuration options. Writing your own gives you exactly the behaviour you need and nothing else, at the cost of owning the project surgery and the native module.
Editorial conclusion
react-native-widget-extension suits an Expo or React Native team that wants home screen widgets and Live Activities without hand-editing the Xcode project, and it suits anyone who is prepared to write the widget views in Swift while keeping the app logic in TypeScript. It does not suit a bare React Native project with no Expo config plugin pipeline, and it does not cover Android. Verify first that your deployment target can be raised to iOS 16.2, which is what Live Activities require, that the app group identifier you set starts with `group.`, and that the MIT licence named in the package manifest is what you intend to rely on, since the repository itself carries no licence classification.
Frequently asked questions
What does react-native-widget-extension do?
It is an Expo config plugin that adds a folder of Swift files to a React Native project as an iOS widget extension target, and exposes a native module so JavaScript can start, update and end a Live Activity. The widget views themselves still have to be written in Swift.
How do I add react-native-widget-extension to an Expo app?
Run npx expo install react-native-widget-extension, then add the plugin to the plugins array in app.json with a widgetsFolder option pointing at the folder that holds your Swift widget files. The example app in the repository shows the full configuration.
Why does react-native-widget-extension require iOS 16.2?
The default deployment target of 16.2 is set because otherwise Swift compilation fails when Live Activities are used. You can lower it through the deploymentTarget plugin option, but then you have to guard the iOS 16.2-only code yourself with an availability check in the widget bundle and an @available attribute on the view.
How do the app and the widget share data?
Through the groupIdentifier plugin option, which is the app group identifier required for communication with the main app and must start with group. The keychainAccessGroup option, which should start with your team identifier, is for securely sharing secrets.
What files does the plugin expect in the widgets folder?
Attributes.swift, which defines the ActivityAttributes for the Live Activity, and Module.swift, which defines the native module used from React to start, stop and update the live activity. The remaining files in the folder are Swift files defining widgets and views and can be named and divided as you like.
What licence is react-native-widget-extension released under?
The package manifest declares MIT and names Benedikt Müller as the author, while the repository shows no licence type in its metadata. Since the repository metadata shows no licence type, confirm the MIT terms in the package manifest before relying on them.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bndkt-react-native-widget-extension)Was this useful?