Expo Projects
Swiftpatch works seamlessly with both Expo bare and Expo managed workflows. The Expo config plugin wires AppDelegate, MainApplication, Info.plist, and AndroidManifest automatically at prebuild time.
Managed workflow
Add the plugin to app.json (or app.config.js):
{
"expo": {
"plugins": [
["@swiftpatch/react-native", { "deploymentKey": "dep_xxx" }]
]
}
}
Then regenerate the native projects:
npx expo prebuild --clean
That's it. The plugin injects:
SwiftPatchModule.getJSBundleFile()override inMainApplication.kt.bundleURLoverride inAppDelegate.swift.SwiftPatchDeploymentKeyinInfo.plist.swiftpatch_deployment_keyinstrings.xml.pod 'SwiftPatch'in thePodfile(when using custom pods).
Once prebuild completes, build with EAS or run locally:
npx expo run:ios
# or
eas build --platform ios
Omitting the deployment key
If you prefer to set the key at runtime (for example, per-environment config), omit it from the plugin options:
{
"expo": {
"plugins": ["@swiftpatch/react-native"]
}
}
Then pass it via the provider:
<SwiftPatchProvider config={{ deploymentKey: process.env.DEPLOYMENT_KEY }}>
<App />
</SwiftPatchProvider>
Bare workflow
For bare Expo projects (you've already run expo prebuild or started with expo-template-bare-minimum), use the regular CLI flow:
npm install @swiftpatch/react-native
npx swiftpatch init
swiftpatch init detects the ios/ and android/ directories and patches them directly. If you later re-run npx expo prebuild --clean, the native directories will be regenerated and you'll need to re-run swiftpatch init to re-apply the patches.
For the cleanest CI flow in a bare Expo project, also add the config plugin to app.json. That way npx expo prebuild --clean regenerates your natives with Swiftpatch already wired in, and you don't need to run swiftpatch init after each prebuild.
Expo Updates coexistence
Swiftpatch and expo-updates are mutually exclusive — both try to own the JS bundle loader. Pick one.
If you're migrating from expo-updates:
- Remove the
expo-updatespackage. - Remove
expo-updatesfrom thepluginsarray inapp.json. - Add
@swiftpatch/react-nativetoplugins. - Run
npx expo prebuild --clean.
See Migrating from Expo Updates for a full port guide.
EAS Build + CI/CD
Swiftpatch deploys don't touch the native binary — they only ship JS bundles. So EAS Build is for store releases (the binary), and swiftpatch deploy is for OTA releases (JS).
Typical flow:
- name: Deploy OTA
env:
SWIFTPATCH_CI_TOKEN: ${{ secrets.SWIFTPATCH_CI_TOKEN }}
run: |
npx swiftpatch-cli deploy -p ios --hermes
npx swiftpatch-cli deploy -p android --hermes
See CI/CD for full examples.
Troubleshooting
Prebuild fails with "plugin not found"
Ensure the SDK is installed:
npm install @swiftpatch/react-native
Prebuild succeeds but OTA updates don't work
Verify the plugin ran. After expo prebuild, check that:
ios/YourApp/AppDelegate.swiftcontainsSwiftPatchModule.getBundleURL().android/app/src/main/java/.../MainApplication.ktcontainsSwiftPatchModule.getJSBundleFile.ios/YourApp/Info.plistcontainsSwiftPatchDeploymentKey.android/app/src/main/res/values/strings.xmlcontainsswiftpatch_deployment_key.
If any are missing, re-run npx expo prebuild --clean and watch the plugin logs.
Custom AppDelegate doesn't work with the plugin
The plugin assumes a stock Expo AppDelegate layout. If you've heavily customized, use the config plugin with deploymentKey for the Info.plist entry and apply the AppDelegate changes manually — see iOS setup.
Next steps
- Provider reference — wrap your Expo app.
- CLI commands — deploy OTA updates.
- CI/CD — automate OTA deploys from EAS-adjacent pipelines.