Expo Workflow Expert
Expert in the Expo managed workflow including EAS Build/Submit/Update, config plugins, custom dev clients, and Expo Router.
Activation Triggers
Activate on: "Expo project", "EAS Build", "EAS Submit", "EAS Update", "config plugin", "expo-dev-client", "Expo Router", "Expo SDK 52", "app.json config", "expo prebuild"
NOT for: Bare React Native → react-native-architect | Native module authoring → react-native-architect | Flutter → flutter-bloc-state-manager
Quick Start
- Create project —
npx create-expo-app@latest my-app
- Configure app.json — set bundle IDs, versioning, splash screen, icons
- Set up EAS —
npx eas init and configure eas.json for build profiles
- Add Expo Router — file-based routing with typed routes
- Create dev client —
npx eas build --profile development for custom native builds
Core Capabilities
| Domain | Technologies |
|---|
| Build | EAS Build, local builds (npx expo run:ios/android), prebuild |
| Updates | EAS Update (OTA), update groups, channels, rollbacks |
| Submit | EAS Submit to App Store Connect and Google Play Console |
| Routing | Expo Router 4, file-based routing, API routes, typed routes |
| Config | Config plugins, app.json/app.config.ts, Expo Modules API |
Architecture Patterns
EAS Build Profile Configuration
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal",
"channel": "preview"
},
"production": {
"channel": "production",
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"appleId": "team@example.com",
"ascAppId": "123456789",
"appleTeamId": "ABCDE12345"
},
"android": {
"serviceAccountKeyPath": "./google-services.json",
"track": "internal"
}
}
}
}
EAS Update Strategy (OTA Updates)
Production Channel:
├─ Branch: production
├─ Update: JS-only changes ship instantly (no binary rebuild)
└─ Rollback: `eas update:rollback --channel production`
Preview Channel:
├─ Branch: staging
├─ Internal testers get latest changes
└─ QA before promoting to production
Flow:
Code change → Is it JS-only?
├─ YES → eas update --channel production --message "fix: ..."
│ (Users get update in < 1 minute, no store review)
└─ NO (native change) → eas build + eas submit
(Full store review cycle)
Config Plugin for Native Customization
// plugins/withCustomSplash.ts
import { ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';
const withCustomSplash: ConfigPlugin = (config) => {
return withAndroidManifest(config, async (modConfig) => {
const mainApplication = modConfig.modResults.manifest.application?.[0];
if (mainApplication) {
// Add custom meta-data to AndroidManifest.xml
mainApplication['meta-data'] = mainApplication['meta-data'] || [];
mainApplication['meta-data'].push({
$: {
'android:name': 'expo.modules.splashscreen.SplashScreenImageResizeMode',
'android:value': 'contain',
},
});
}
return modConfig;
});
};
export default withCustomSplash;
// app.config.ts — use the plugin
export default {
plugins: [
'./plugins/withCustomSplash',
['expo-camera', { cameraPermission: 'Allow camera for scanning' }],
],
};
Anti-Patterns
- Ejecting unnecessarily — reaching for
expo eject when a config plugin would suffice. Since Expo SDK 50+, the "managed vs bare" distinction is gone; use npx expo prebuild with config plugins.
- OTA updates for native changes — shipping native module changes via EAS Update. Only JS bundle changes work with OTA; native changes require a new binary build.
- Not using development builds — testing in Expo Go which lacks custom native modules. Create a development build (
expo-dev-client) for full-fidelity testing.
- Hardcoded app.json — static config when dynamic values are needed. Use
app.config.ts for environment-specific bundle IDs, API URLs, and feature flags.
- Skipping update channels — publishing all updates to one channel. Use separate channels (development, preview, production) for staged rollout.
Quality Checklist
[ ] app.config.ts used (not static app.json) for dynamic configuration
[ ] EAS Build configured with development, preview, and production profiles
[ ] EAS Update channels match build profiles
[ ] Development build created (not relying on Expo Go)
[ ] Config plugins used for native customization (no manual native edits)
[ ] Expo Router with typed routes enabled
[ ] Auto-increment build numbers in production profile
[ ] EAS Submit configured for both App Store and Google Play
[ ] OTA updates tested with preview channel before production
[ ] Rollback procedure documented and tested
[ ] Bundle size monitored (expo-optimize for image compression)
[ ] Expo SDK on latest stable version