react-native-app-clip, an Expo config plugin for iOS App Clips
Easily add an App Clip for iOS apps built with React Native
At a glance
- What is it?
- react-native-app-clip is an Expo config plugin that adds an iOS App Clip target to a React Native app, including the scheme, the app group, the size trimming and the native capability flags. It saves days of Xcode plumbing, and it cannot be tested in Expo Go, which is the constraint that shapes the whole workflow.
- Who is it for?
- This plugin suits an Expo-managed React Native team that has decided to ship an iOS App Clip and wants the target, scheme and capability wiring generated rather than hand-built in Xcode. It does not suit a bare React Native project without the Expo config plugin pipeline, and it does not suit a team that needs to test the clip in Expo Go, because App Clips cannot be run that way at all.
- 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 8 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 plumbing an App Clip needs, generated by a config plugin
Adding an App Clip to an iOS app by hand is Xcode work: a second target, a new scheme, an app group for shared data, capabilities such as Sign in with Apple and Apple Pay, and a bundle identifier that Apple has to recognise. None of it is React Native work, and none of it survives a clean regeneration of the native project. That is the gap this plugin fills.
The repository describes itself as an Expo config plugin that generates an App Clip for iOS apps built with Expo. The mechanism is the standard Expo one. You add the package to the plugins array of your Expo config, and the plugin writes the target into the native iOS project during prebuild. Since the project is generated rather than hand-edited, the change is reproducible and reviewable as configuration, which is the property that matters once you have more than one developer on the app.
The audience is specific. It is for teams already on Expo with a managed or prebuild workflow, targeting iOS, who want a lightweight clip experience alongside their full app. The typical use case is a link or a code that opens a small version of the app for a one-off task, with the option to promote the full app afterwards.
What the plugin is not is a runtime library. There is no App Clip runtime, no deep linking scheme, no session handling. The native side it produces is thin, and the React Native code inside the clip is the same code you already have, which is exactly why the size limits become the central engineering problem.
Installation, prebuild and the scheme you actually run
The setup sequence in the project documentation is short. Install the package into the project.
npx expo install react-native-app-clipThen add it to the plugins list in app.json or app.config.js, optionally with a name that becomes the display name of the clip.
"expo": {
"name": "my-app",
"plugins": [
["react-native-app-clip", { "name": "My App Clip" }]
]
}Without a name, the display name becomes the app name with " Clip" appended. Next, generate the native code with `npx expo prebuild`, which is the step that actually creates the Xcode target, and then run the App Clip scheme, where the documentation notes that Release mode tends to be more reliable.
npx expo run:ios --configuration Release --schemeOnce the clip is installed, you can launch it directly by bundle identifier.
xcrun simctl launch <DEVICE-UUID> <APP_CLIP_BUNDLE_ID>The bundle identifier is not a literal. The documentation states that it is the `bundleIdentifier` followed by the `bundleIdSuffix`, joined by a dot, and that you can find the simulator device UUID with `xcrun simctl list`. Since the default suffix is Clip, the identifier for a main app with the bundle identifier com.example.my-app is com.example.my-app.Clip, and that same string is what Apple has to have registered on your side.
One nuance in the naming: the suffix defaults to Clip and the target suffix also defaults to Clip, and they are separate settings. The `bundleIdSuffix` is what forms the bundle identifier, and the `targetSuffix` is appended to the target name. If you change the bundle identifier suffix, you have to pass the new value to the `isClip()` function at runtime, which is a small trap worth knowing before you rename anything.
The size limit is the real design constraint
App Clips have to launch fast, and the platform enforces that with a hard size cap on the uncompressed binary. The plugin documents the caps by deployment target: 10 MB for iOS 15 and earlier, 15 MB for iOS 16 and later, and 100 MB for iOS 17 and later with restrictions.
The 100 MB tier is conditional. The plugin lists what the larger limit requires: the clip must support only digital invocations such as website links and Spotlight search, it cannot support physical invocations like App Clip Codes, QR codes or NFC tags, it requires reliable internet connection usage scenarios, and it does not support iOS 16 and earlier.
Two plugin options exist to get under the cap, and the documentation is refreshingly honest that they can work against each other. `enableCompression` turns on gzip compression of the clip's JavaScript bundle, and `excludedPackages` names node modules to leave out of Expo's autolinking for the clip target so they are not compiled in. But the note is that since Apple compresses the App Clip binary itself, pre-compressing the JS bundle might sometimes be counterproductive, and you should always verify the final size in TestFlight or the App Store Connect dashboard.
That is the honest state of affairs: the plugin gives you the levers, and it cannot tell you the answer, because the answer depends on your dependency tree and on what Apple does at the packaging stage. The excluded packages are removed via `use_expo_modules!`, and you list them by node module package name.
"excludedPackages": [
"expo-notifications",
"react-native-nfc-manager"
]Excluding a module that the clip's flow needs is a runtime failure, not a build error, so this list deserves the same care as any other capability decision.
Capability flags map to Apple entitlements
The additional parameters are where the plugin earns its keep, because each one corresponds to a capability you would otherwise configure by hand in Xcode.
`groupIdentifier` configures an app group so the clip and the full app can share data. `deploymentTarget` sets the clip's deployment target, and the documentation notes that higher targets allow larger clip sizes, which is the knob that determines which of the three size limits applies to you. `requestEphemeralUserNotification` enables notifications for the clip, and `requestLocationConfirmation` allows the clip to access location data with the user's physical confirmation. `appleSignin` turns on Sign in with Apple for the clip, and `applePayMerchantIds` enables the Apple Pay capability with the merchant IDs you supply. `pushNotifications` enables push notification compatibility. `enabled`, defaulting to true, decides whether the clip target is built at all, which is a convenient switch during development.
Two native functions are exported for the React Native side.
import {
isClip,
displayOverlay,
} from "react-native-app-clip";`isClip()` tells you whether the running code is inside the clip rather than the full app, so you can render different content and behaviour for the two. `displayOverlay()` shows the native iOS banner that recommends the full app to clip users. Both are small, and both are the sort of thing you would otherwise write in Swift and expose through a native module.
Because the plugin's own `src` directory ships TypeScript, the parameter names in the Expo config are the contract. The documentation lists them with types and defaults, which is what makes the config usable without reading the plugin source.
Sharing data with the full app depends on the keychain
A clip is a separate app with a separate bundle identifier, so the question of how state crosses the boundary to the full app is a real design problem. The plugin's answer is to use the keychain rather than the app group for data that the full app should receive later.
The documentation states that data can be shared from the clip to the full app by using `expo-secure-store` from iOS 15.4 onwards, that the data is securely shared using the keychain, and that the full app can access it after installation. It points at Apple's documentation on sharing data between your App Clip and your full app and on the review of keychain usage.
The constraint to note is the version floor. Keychain sharing between clip and full app requires iOS 15.4 or newer, which interacts with the size limit rules, since a clip targeting iOS 16 or later gets the 15 MB cap and one targeting iOS 17 gets the 100 MB cap under restrictions. The `groupIdentifier` option is still there for the app group, which is what you would use for the kind of shared container the two targets genuinely need.
What the documentation does not spell out is a recommended pattern for the moment a clip user decides to install the full app, beyond the keychain mechanism and the `displayOverlay()` banner. If your product depends on a specific handoff, such as a partially completed form, you will need to design that yourself.
The Apple Developer App ID step is not optional
Here is the step that catches teams out, because it lives outside the codebase entirely. Before building for TestFlight or the App Store, you must register a new App ID for the clip in your Apple Developer profile. Under Certificates, Identifiers and Profiles you go to Identifiers, click the plus icon, select App IDs, choose App Clip as the type, then on the next screen select your main app as the Parent App ID and enter Clip as the product name.
The documentation is emphatic about the product name, saying it is essential that the product name is Clip and nothing else, and explaining that Apple shows a preview of the App Clip Bundle ID at the bottom of the page. If the main app's bundle ID is com.example.my-app, the preview should read com.example.my-app.Clip. If it does not, the identifier your simulator launch command needs will not match the one Apple has registered.
That dependency on a specific registered product name is also why the plugin's `bundleIdSuffix` default of Clip is not arbitrary. Change the suffix and you are also changing the identifier Apple has to have, which means a different App ID registration and a new decision in the Apple Developer portal.
For an adopter, this is the part that cannot be automated. The plugin generates the Xcode side, and the Apple Developer portal remains a manual, human step that has to be right before anything ships.
Testing means a native build, never Expo Go
The testing story is the sharpest constraint in the whole workflow. The documentation states that App Clips cannot be tested with Expo Go or expo-dev-client, and offers two routes instead.
The first is the simulator. You build the development client once with `expo run:ios` and open the app in Simulator. After that you can run `expo run:ios --scheme` and pick the App Clip scheme, the one whose name ends in Clip. The documentation also suggests adding a script to your package.json so you do not have to remember the flags.
"scripts": {
"clip": "expo run:ios --scheme my-appClip"
}The second route is building the App Clip with Xcode and opening it on a connected device.
What this means in practice is that every iteration on the clip goes through a native build. There is no fast refresh loop for the clip, and there is no way to sidestep Xcode entirely. For a small team that is a tax paid on every change to the clip's code, and it is the strongest argument for keeping the clip's surface area small, which is also what the size limits push you toward.
The second route is also the only one that tests the physical invocation path, since App Clip Codes, QR codes and NFC tags are exactly what a simulator cannot do.
A young package with a per-commit release rhythm
The release record shows v0.9.2 on 2026-10-15, v0.9.0 on 2026-05-25 and v0.8.0 on 2026-03-15, and the last push to the repository was on 2026-10-15, the same day as the newest tag. The project is not archived and the numbering shows pre-1.0 software that is still moving.
The version numbers are the practical risk. A 0.x line carries no stability promise, and this one is moving through 0.8, 0.9 and beyond inside a single year, so a plugin option can be renamed or its default changed between minor versions. The documentation's own advice, to verify the final size in TestFlight or the App Store Connect dashboard, is really a piece of advice about not trusting the build locally.
The package metadata shows the shape of the project. It is built with the expo-module scripts, with build, build:plugin, clean, lint, test and prepare scripts, and it depends on `@expo/config-plugins` and `@expo/plist` at runtime, with `expo-modules-core` and TypeScript as dev dependencies. The peer dependencies are `expo`, `react` and `react-native`, all pinned to `*`, which means the plugin does not constrain the versions of the framework it plugs into and will resolve against whatever the host app has.
The repository layout matches a standard Expo module: an `ios` directory with the native code, a `plugin` directory holding the config plugin itself, `src` for the TypeScript, an `app.plugin.js` entry point, and an `example` project that is wired into the scripts through `open:ios` and `open:android`, which is how a maintainer checks the plugin against a real app.
On licensing, the package manifest declares MIT and names Benedikt Müller as the author, which permits commercial use and modification with the notice retained. Note that the licence field GitHub itself shows for this repository is not classified, so the manifest is the place to look.
Editorial conclusion
This plugin suits an Expo-managed React Native team that has decided to ship an iOS App Clip and wants the target, scheme and capability wiring generated rather than hand-built in Xcode. It does not suit a bare React Native project without the Expo config plugin pipeline, and it does not suit a team that needs to test the clip in Expo Go, because App Clips cannot be run that way at all. Verify first that you have registered a separate App Clip App ID in your Apple Developer profile with the product name exactly Clip, that your chosen deployment target fits the size limit you are aiming at, and that the modules you exclude with excludedPackages are not the ones the clip's flow actually needs.
Frequently asked questions
How do I add an App Clip to an Expo React Native app?
Install react-native-app-clip, add it to the plugins array of your Expo config with an optional name, then run npx expo prebuild to generate the native code. The plugin creates the App Clip target, its scheme and its capabilities in the Xcode project.
Can I test an App Clip in Expo Go?
No. The documentation states that App Clips cannot be tested with Expo Go or expo-dev-client. You build the development client with expo run:ios once, then run expo run:ios --scheme and select the App Clip scheme, or build with Xcode for a connected device.
How do I keep the App Clip under the size limit?
The plugin documents limits of 10 MB uncompressed for iOS 15 and earlier, 15 MB for iOS 16 and later, and 100 MB for iOS 17 and later with restrictions. You can use the enableCompression option for the JavaScript bundle and the excludedPackages option to leave node modules out of autolinking, and the documentation advises checking the final size in TestFlight or App Store Connect.
How do I share data between the App Clip and the full app?
The documentation states that data can be shared from the clip to the full app by using expo-secure-store from iOS 15.4 onwards, with the data securely shared using the keychain and accessible by the full app after installation. The groupIdentifier option configures an app group for data the two targets share directly.
What Apple Developer setup does an App Clip need?
You must register a new App ID for the clip under Certificates, Identifiers and Profiles, choosing App IDs, then App Clip as the type, selecting your main app as the Parent App ID and entering Clip as the product name. Apple then shows a preview of the App Clip Bundle ID, for example com.example.my-app.Clip.
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-app-clip)Was this useful?