React Native package for iOS UIApplicationShortcutItem (Quick Actions) and Android App Shortcuts.
This package lets you add home screen shortcuts to your React Native app, so users can jump straight to a specific action from the home screen.
- Dynamic shortcuts on iOS and Android
- Handles both cold launch and warm taps
- SF Symbols on iOS
- Round-trips arbitrary extra data (
userInfo) on both platforms - TypeScript types
- New Architecture (TurboModules) with codegen; Old Architecture still supported
| Minimum | |
|---|---|
| React Native | 0.76 |
| iOS | 15.1 |
| Android | API 24 (shortcuts themselves need API 25 / Android 7.1 — see isSupported()) |
Verified against React Native 0.85.3.
npm install @mbdayo/react-native-app-shortcutsor
yarn add @mbdayo/react-native-app-shortcutscd ios && pod installThen forward the tapped shortcut from your AppDelegate. React Native 0.77+ ships AppDelegate.swift by default, so that is covered first.
Add a bridging header — create ios/<YourApp>/<YourApp>-Bridging-Header.h if you do not already have one:
#import <RNAppShortcuts/RNAppShortcuts.h>and point Xcode at it: select your target → Build Settings → Objective-C Bridging Header →
<YourApp>/<YourApp>-Bridging-Header.h.
Then add this method to your AppDelegate class:
func application(
_ application: UIApplication,
performActionFor shortcutItem: UIApplicationShortcutItem,
completionHandler: @escaping (Bool) -> Void
) {
RNAppShortcuts.handle(shortcutItem)
completionHandler(true)
}The RN 0.77+ template's
AppDelegateconforms toUIApplicationDelegatedirectly rather than subclassing, so this is a plainfunc, not anoverride func.
handle(_:)is how Swift imports the Objective-C+handleShortcutItem:— the importer prunes the type-name suffix, the same wayperformActionForShortcutItem:becomesperformActionFor shortcutItem:.
Add the import at the top of the file:
#import <RNAppShortcuts/RNAppShortcuts.h>and add this method to your @implementation AppDelegate:
- (void)application:(UIApplication *)application
performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
completionHandler:(void (^)(BOOL))completionHandler {
[RNAppShortcuts handleShortcutItem:shortcutItem];
completionHandler(YES);
}No additional setup required.
The New Architecture is enabled by default from React Native 0.76 and is fully supported, including codegen — the module extends the generated spec on both platforms. No configuration is needed.
The Old Architecture is still supported on the React Native versions that offer it: the native code
is #ifdef-guarded and codegen runs for this library either way, so newArchEnabled=false builds
and works on Android. Note that on iOS from React Native 0.85, React Native itself forces
RCT_NEW_ARCH_ENABLED=1 during pod install, so the New Architecture is always used there
regardless of what you configure.
import AppShortcuts from '@mbdayo/react-native-app-shortcuts';
// Check if shortcuts are supported
const checkSupport = async () => {
const isSupported = await AppShortcuts.isSupported();
console.log('Shortcuts supported:', isSupported);
};
// Set dynamic shortcuts
const setShortcuts = async () => {
const shortcuts = [
{
type: 'compose', // unique identifier
title: 'New Message',
subtitle: 'Create a new message', // optional
symbolName: 'square.and.pencil', // optional, SF Symbol, iOS only
iconName: 'compose_icon', // optional, asset catalog / res/drawable
userInfo: { screen: 'Compose' }, // optional, comes back on the event
},
{
type: 'search',
title: 'Search',
iconName: 'search_icon',
},
];
const success = await AppShortcuts.setShortcuts(shortcuts);
console.log('Shortcuts set:', success);
};
// Clear all shortcuts
const clearShortcuts = async () => {
const success = await AppShortcuts.clearShortcuts();
console.log('Shortcuts cleared:', success);
};
// Check if app was launched from a shortcut (consume-once)
const checkInitialShortcut = async () => {
const shortcut = await AppShortcuts.getInitialShortcut();
if (shortcut) {
handleShortcut(shortcut);
}
};
// Listen for shortcut selection while the app is running
useEffect(() => {
checkInitialShortcut();
const removeListener = AppShortcuts.addListener(handleShortcut);
return removeListener;
}, []);
// Handle shortcut action
const handleShortcut = (event) => {
switch (event.type) {
case 'compose':
// Navigate to compose screen, using event.userInfo if you attached any
break;
case 'search':
// Navigate to search screen
break;
}
};A runnable example lives in example/.
| Feature | iOS | Android |
|---|---|---|
setShortcuts / clearShortcuts |
✅ | ✅ (API 25+) |
getInitialShortcut (cold launch) |
✅ | ✅ |
addListener (warm tap) |
✅ | ✅ |
subtitle |
✅ | ✅ (long label) |
iconName |
✅ asset catalog | ✅ res/drawable |
symbolName (SF Symbols) |
✅ iOS 13+ | ➖ ignored |
userInfo |
✅ | ✅ |
getInitialShortcut() is consume-once on both platforms: the second call resolves null unless
another shortcut has been used since.
Use symbolName for an SF Symbol (e.g. 'square.and.pencil'), or iconName for an image in your
app's asset catalog. If both are given, symbolName wins.
iconName must match a drawable resource name in res/drawable (without the file extension). If
the drawable is not found the shortcut is still created, without an icon. symbolName is ignored.
Whether dynamic shortcuts are supported. Always true on iOS (the minimum deployment target is
already above the iOS 9 floor for Quick Actions); on Android, true from API 25 up.
The shortcut the app was launched from, or null. Consume-once.
Replace the app's dynamic shortcuts. Rejects if any shortcut is missing type or title.
Remove all dynamic shortcuts.
Listen for shortcuts used while the app is running. Returns a function that removes the listener.
interface Shortcut {
type: string; // Unique identifier
title: string; // Display title
subtitle?: string; // Optional subtitle / long label
iconName?: string; // Optional asset catalog or drawable name
symbolName?: string; // Optional SF Symbol name (iOS only)
userInfo?: Record<string, unknown>; // Optional extra data
}interface ShortcutEvent {
type: string; // The type of the shortcut that was used
userInfo?: Record<string, unknown>; // Whatever you attached to the shortcut
/** @deprecated Read `type` instead. Removed in 3.0.0. */
shortcutId: string;
}1.x could not install or build on React Native 0.76+, and the API the README documented was rejected
by the native code. 2.0.0 makes one contract true everywhere: type identifies a shortcut and
userInfo carries its extra data, on the JS types, the codegen spec, and both native
implementations.
| 1.x | 2.0.0 | Notes |
|---|---|---|
setShortcuts([{ id, data }]) |
setShortcuts([{ type, userInfo }]) |
id/data still accepted at runtime until 3.0.0 |
event.shortcutId |
event.type |
shortcutId still emitted until 3.0.0 |
getInitialShortcut() repeatable on Android |
consume-once on both | iOS already behaved this way |
Pod name @mbdayo/react-native-app-shortcuts |
Pod name RNAppShortcuts |
changes the header import, below |
React Native * |
React Native >=0.76 |
|
Android minSdkVersion 21 |
minSdk 24 |
matches React Native 0.76+ |
1. Rename id → type and data → userInfo when calling setShortcuts:
AppShortcuts.setShortcuts([
- { id: 'compose', title: 'New Message', data: { screen: 'Compose' } },
+ { type: 'compose', title: 'New Message', userInfo: { screen: 'Compose' } },
]);The old keys still work for one release, so you can do this after upgrading. They are removed in 3.0.0.
2. Read event.type instead of event.shortcutId:
-AppShortcuts.addListener((event) => handle(event.shortcutId));
+AppShortcuts.addListener((event) => handle(event.type));shortcutId is still present on the event until 3.0.0.
3. Update the iOS header import. The pod is now named RNAppShortcuts, because @ and / are
not valid in a CocoaPods pod name:
-#import <@mbdayo/react-native-app-shortcuts/RNAppShortcuts.h>
+#import <RNAppShortcuts/RNAppShortcuts.h>4. Re-run pod install and rebuild both platforms.
5. If you called getInitialShortcut() more than once on Android and relied on it returning the
same value every time, keep the result yourself — it is now consume-once, matching iOS.
pod installfailed on React Native 0.76+ (staleReact-Codegen/RCT-Follydependencies, and an invalid pod name). The podspec now delegates toinstall_modules_dependencies.RNAppShortcuts.hwas invalid Objective-C under the New Architecture (two adjacent protocol lists).- The Android module did not configure under AGP 8 (no
namespace, no manifest, stalecompileSdk/AGP/react-native:+dependency, no codegen wiring). - A hand-written
BuildConfig.javaread a JVM system property that is never set on Android, so the module was never registered as a TurboModule. setShortcutsrejected every call written the way the README documented.- Android shortcuts could not launch the app — the intent targeted
ReactApplicationContext, which is not a component. It now targets the launcher activity. - The iOS warm-tap event never fired (it checked
bridgeon a freshly allocated, uninitialised object). It now emits from the live instance, gated on attached listeners. - Android re-fired the shortcut event on every resume, because the launching intent's extra was never cleared.
- Importing the package threw when the native module was missing, instead of failing at first use.
MIT