Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
thebushidocollective avatar

Expo Config

  • 1 installs
  • 186 repo stars
  • Updated July 19, 2026
  • thebushidocollective/han

Configure Expo apps with app.json, app.config.js, and EAS, covering app metadata, plugins, build config, and environment variables.

About

Covers configuring Expo apps via app.json, app.config.js, and EAS configuration including metadata, plugins, and environment variables. A developer uses it when setting up or adjusting an Expo project's configuration.

  • app.json and app.config.js metadata and plugins
  • Environment variable and build configuration

Expo Config by the numbers

  • 1 all-time installs (skills.sh)
  • Ranked #959 of 1,039 Mobile Development skills by installs in the Skillselion catalog
  • Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thebushidocollective/han --skill expo-config

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1
repo stars186
Last updatedJuly 19, 2026
Repositorythebushidocollective/han

What it does

Configure Expo apps with app.json, app.config.js, and EAS, covering app metadata, plugins, build config, and environment variables.

Files

SKILL.mdMarkdownGitHub ↗

Expo Configuration

Use this skill when configuring Expo applications using app.json, app.config.js/ts, and EAS (Expo Application Services) configuration files.

Key Concepts

app.json Configuration

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"
    }
  }
}

Dynamic Configuration (app.config.js)

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',
  },
});

TypeScript Configuration

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,
  },
});

Best Practices

Environment Variables

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',
  },
});

Plugin Configuration

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"
        }
      ]
    ]
  }
}

EAS Build Configuration

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 Config in Code

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 />;
}

Common Patterns

Multi-Environment Setup

// 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,
  },
});

Feature Flags

// 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
}

Platform-Specific Assets

{
  "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"
      }
    }
  }
}

Deep Linking Configuration

{
  "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"]
        }
      ]
    }
  }
}

Version Management

// 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),
  },
});

Anti-Patterns

Don't Hardcode Secrets

// 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
  },
};

Don't Use app.json for Dynamic Config

// 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',
};

Don't Forget Platform-Specific Requirements

// 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"
    }
  }
}

Don't Mix Config Types

// 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

Related Skills

  • expo-modules: Using Expo modules configured via plugins
  • expo-build: Building apps with EAS Build
  • expo-updates: Configuring OTA updates

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.