From expo
Configures Expo apps using app.json, app.config.js/ts, and EAS for metadata, plugins, build settings, and environment variables.
npx claudepluginhub thebushidocollective/han --plugin expoThis skill is limited to using the following tools:
Use this skill when configuring Expo applications using app.json, app.config.js/ts, and EAS (Expo Application Services) configuration files.
Sets up Expo projects with managed workflow, EAS Build, development builds, and config plugins. For new React Native apps, migrations, or adding native modules.
Provides expertise for building React Native mobile apps with Expo, including Expo Router navigation, EAS Build and Submit, development builds, native module integration, and production deployment.
Builds and deploys Expo apps with EAS Build, covering eas.json profiles for development, preview, production; secrets, env vars, hooks, and multi-environment configs.
Share bugs, ideas, or general feedback.
Use this skill when configuring Expo applications using app.json, app.config.js/ts, and EAS (Expo Application Services) configuration files.
Basic static configuration:
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.mycompany.myapp",
"buildNumber": "1"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.mycompany.myapp",
"versionCode": 1
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
Use JavaScript for dynamic configuration:
export default ({ config }) => ({
...config,
name: process.env.APP_NAME || 'MyApp',
slug: 'my-app',
version: '1.0.0',
extra: {
apiUrl: process.env.API_URL,
environment: process.env.NODE_ENV,
},
ios: {
bundleIdentifier:
process.env.NODE_ENV === 'production'
? 'com.mycompany.myapp'
: 'com.mycompany.myapp.dev',
},
android: {
package:
process.env.NODE_ENV === 'production'
? 'com.mycompany.myapp'
: 'com.mycompany.myapp.dev',
},
});
Use TypeScript for type-safe config:
// app.config.ts
import { ExpoConfig, ConfigContext } from '@expo/config';
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: 'MyApp',
slug: 'my-app',
version: '1.0.0',
orientation: 'portrait',
icon: './assets/icon.png',
splash: {
image: './assets/splash.png',
resizeMode: 'contain',
backgroundColor: '#ffffff',
},
plugins: [
'expo-router',
[
'expo-camera',
{
cameraPermission: 'Allow $(PRODUCT_NAME) to access your camera.',
},
],
],
extra: {
apiUrl: process.env.API_URL,
},
});
Use environment-specific configuration:
// app.config.ts
import { ExpoConfig, ConfigContext } from '@expo/config';
const IS_DEV = process.env.NODE_ENV === 'development';
const IS_PROD = process.env.NODE_ENV === 'production';
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: IS_PROD ? 'MyApp' : 'MyApp (Dev)',
slug: 'my-app',
extra: {
apiUrl: IS_PROD
? 'https://api.myapp.com'
: 'https://dev-api.myapp.com',
environment: process.env.NODE_ENV,
eas: {
projectId: process.env.EAS_PROJECT_ID,
},
},
ios: {
bundleIdentifier: IS_PROD
? 'com.mycompany.myapp'
: 'com.mycompany.myapp.dev',
},
android: {
package: IS_PROD
? 'com.mycompany.myapp'
: 'com.mycompany.myapp.dev',
},
});
Configure Expo plugins:
{
"expo": {
"plugins": [
"expo-router",
[
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access camera"
}
],
[
"expo-location",
{
"locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location"
}
],
[
"expo-notifications",
{
"icon": "./assets/notification-icon.png",
"color": "#ffffff"
}
]
]
}
}
Configure EAS builds with eas.json:
{
"cli": {
"version": ">= 5.9.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
},
"preview": {
"distribution": "internal",
"ios": {
"simulator": false
},
"android": {
"buildType": "apk"
}
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}
Access configuration at runtime:
import Constants from 'expo-constants';
export function App() {
const apiUrl = Constants.expoConfig?.extra?.apiUrl;
const environment = Constants.expoConfig?.extra?.environment;
console.log('API URL:', apiUrl);
console.log('Environment:', environment);
return <View />;
}
// app.config.ts
import { ExpoConfig, ConfigContext } from '@expo/config';
type Environment = 'development' | 'staging' | 'production';
const ENV: Environment = (process.env.APP_ENV as Environment) || 'development';
const config: Record<Environment, { apiUrl: string; name: string }> = {
development: {
apiUrl: 'http://localhost:3000',
name: 'MyApp (Dev)',
},
staging: {
apiUrl: 'https://staging-api.myapp.com',
name: 'MyApp (Staging)',
},
production: {
apiUrl: 'https://api.myapp.com',
name: 'MyApp',
},
};
export default ({ config: baseConfig }: ConfigContext): ExpoConfig => ({
...baseConfig,
name: config[ENV].name,
slug: 'my-app',
extra: {
apiUrl: config[ENV].apiUrl,
environment: ENV,
},
});
// app.config.ts
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
extra: {
features: {
enableNewUI: process.env.ENABLE_NEW_UI === 'true',
enableAnalytics: process.env.ENABLE_ANALYTICS !== 'false',
enableBetaFeatures: process.env.ENABLE_BETA === 'true',
},
},
});
// In code
import Constants from 'expo-constants';
const features = Constants.expoConfig?.extra?.features;
if (features?.enableNewUI) {
// Show new UI
}
{
"expo": {
"ios": {
"icon": "./assets/icon-ios.png",
"splash": {
"image": "./assets/splash-ios.png"
}
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon-android.png",
"backgroundColor": "#ffffff"
},
"splash": {
"image": "./assets/splash-android.png"
}
}
}
}
{
"expo": {
"scheme": "myapp",
"slug": "my-app",
"web": {
"bundler": "metro"
},
"ios": {
"associatedDomains": ["applinks:myapp.com"]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "myapp.com",
"pathPrefix": "/app"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
// app.config.ts
import packageJson from './package.json';
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
version: packageJson.version,
ios: {
buildNumber: process.env.IOS_BUILD_NUMBER || '1',
},
android: {
versionCode: parseInt(process.env.ANDROID_VERSION_CODE || '1', 10),
},
});
// Bad - Secrets in config
export default {
extra: {
apiKey: 'sk_live_1234567890',
apiSecret: 'secret_key_here',
},
};
// Good - Use environment variables
export default {
extra: {
apiKey: process.env.API_KEY,
// Never commit secrets
},
};
// Bad - Can't use dynamic values in app.json
{
"expo": {
"name": process.env.APP_NAME
}
}
// Good - Use app.config.js/ts
// app.config.ts
export default {
name: process.env.APP_NAME || 'MyApp',
};
// Bad - Missing required fields
{
"expo": {
"name": "MyApp",
"slug": "my-app"
}
}
// Good - Include all required fields
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"ios": {
"bundleIdentifier": "com.mycompany.myapp"
},
"android": {
"package": "com.mycompany.myapp"
}
}
}
// Bad - Mixing static and dynamic
// app.json
{
"expo": {
"name": "MyApp"
}
}
// app.config.js also exists - creates conflicts
// Good - Use one or the other
// Either app.json for static config
// Or app.config.js/ts for dynamic config