Skip to main content

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):

app.json
{
"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 in MainApplication.kt.
  • bundleURL override in AppDelegate.swift.
  • SwiftPatchDeploymentKey in Info.plist.
  • swiftpatch_deployment_key in strings.xml.
  • pod 'SwiftPatch' in the Podfile (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.

Use the config plugin in bare projects too

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:

  1. Remove the expo-updates package.
  2. Remove expo-updates from the plugins array in app.json.
  3. Add @swiftpatch/react-native to plugins.
  4. 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:

.github/workflows/ota.yml
- 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.swift contains SwiftPatchModule.getBundleURL().
  • android/app/src/main/java/.../MainApplication.kt contains SwiftPatchModule.getJSBundleFile.
  • ios/YourApp/Info.plist contains SwiftPatchDeploymentKey.
  • android/app/src/main/res/values/strings.xml contains swiftpatch_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