Claude
Skills
Sign in
Back

configuring-expo

Included with Lifetime
$97 forever

Expo app.config.js patterns, environment variables, and project configuration. Use when setting up dynamic config, environment-specific bundle IDs, or troubleshooting Expo project issues.

Web Dev

What this skill does


# Expo Configuration Reference

## app.config.js vs app.json

**Use `app.config.js` (recommended):**
- Dynamic configuration based on environment
- Conditional logic for different build variants
- Access to environment variables
- Can import from other files

**Use `app.json`:**
- Simple static configuration
- No environment-specific needs
- Quick prototyping

### Converting app.json to app.config.js

If you have an existing `app.json`:

```javascript
// app.config.js
export default ({ config }) => {
  return {
    ...config,
    // Your customizations here
  };
};
```

Or start fresh:

```javascript
// app.config.js
export default {
  name: "My App",
  slug: "my-app",
  // ... rest of config
};
```

## Environment-Specific Bundle IDs

The recommended pattern for running dev/preview/production builds side-by-side on the same device:

```javascript
// app.config.js
const IS_DEV = process.env.APP_VARIANT === 'development';
const IS_PREVIEW = process.env.APP_VARIANT === 'preview';

const getUniqueIdentifier = () => {
  if (IS_DEV) {
    return 'com.yourcompany.yourapp.dev';
  }
  if (IS_PREVIEW) {
    return 'com.yourcompany.yourapp.preview';
  }
  return 'com.yourcompany.yourapp';
};

const getAppName = () => {
  if (IS_DEV) {
    return 'YourApp (Dev)';
  }
  if (IS_PREVIEW) {
    return 'YourApp (Preview)';
  }
  return 'YourApp';
};

export default {
  name: getAppName(),
  slug: 'your-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: getUniqueIdentifier(),
  },
  android: {
    adaptiveIcon: {
      foregroundImage: './assets/adaptive-icon.png',
      backgroundColor: '#ffffff',
    },
    package: getUniqueIdentifier(),
  },
  web: {
    favicon: './assets/favicon.png',
  },
  extra: {
    eas: {
      projectId: 'your-project-id',
    },
  },
};
```

### Corresponding eas.json

```json
{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "env": {
        "APP_VARIANT": "development"
      }
    },
    "preview": {
      "distribution": "internal",
      "env": {
        "APP_VARIANT": "preview"
      }
    },
    "production": {
      "env": {
        "APP_VARIANT": "production"
      }
    }
  }
}
```

## Environment Variables

### Build-time vs Runtime Variables

| Type | Prefix | Available | Use Case |
|------|--------|-----------|----------|
| Build-time | None | `app.config.js` only | Bundle ID, app name |
| Runtime | `EXPO_PUBLIC_` | App code | API URLs, feature flags |

### Build-time Variables

Used in `app.config.js` during build:

```javascript
// app.config.js
const API_URL = process.env.API_URL || 'https://api.default.com';

export default {
  // ...
  extra: {
    apiUrl: API_URL,
  },
};
```

Set in `eas.json`:
```json
{
  "build": {
    "production": {
      "env": {
        "API_URL": "https://api.production.com"
      }
    }
  }
}
```

### Runtime Variables (Client-side)

Variables accessible in your app code:

```javascript
// In eas.json
{
  "build": {
    "production": {
      "env": {
        "EXPO_PUBLIC_API_URL": "https://api.example.com"
      }
    }
  }
}

// In your app code
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
```

**IMPORTANT:** `EXPO_PUBLIC_` variables are embedded in the JS bundle. Never use for secrets!

### Local Development (.env)

For local development, use `.env` files:

```bash
# .env.local (gitignored)
EXPO_PUBLIC_API_URL=http://localhost:3000
```

Install `expo-env`:
```bash
npx expo install expo-env
```

## Complete app.config.js Template

```javascript
// app.config.js
const IS_DEV = process.env.APP_VARIANT === 'development';
const IS_PREVIEW = process.env.APP_VARIANT === 'preview';

const getUniqueIdentifier = () => {
  if (IS_DEV) return 'com.yourcompany.yourapp.dev';
  if (IS_PREVIEW) return 'com.yourcompany.yourapp.preview';
  return 'com.yourcompany.yourapp';
};

const getAppName = () => {
  if (IS_DEV) return 'YourApp (Dev)';
  if (IS_PREVIEW) return 'YourApp (Preview)';
  return 'YourApp';
};

export default {
  // Basic Info
  name: getAppName(),
  slug: 'your-app',
  version: '1.0.0',
  orientation: 'portrait',

  // Assets
  icon: './assets/icon.png',
  splash: {
    image: './assets/splash.png',
    resizeMode: 'contain',
    backgroundColor: '#ffffff',
  },
  assetBundlePatterns: ['**/*'],

  // Appearance
  userInterfaceStyle: 'automatic',

  // iOS Configuration
  ios: {
    supportsTablet: false, // or true for iPad support
    bundleIdentifier: getUniqueIdentifier(),
    buildNumber: '1',
    infoPlist: {
      NSCameraUsageDescription: 'This app uses the camera to...',
      NSPhotoLibraryUsageDescription: 'This app accesses photos to...',
      // Add other permissions as needed
    },
    config: {
      usesNonExemptEncryption: false, // If no custom encryption
    },
  },

  // Android Configuration
  android: {
    package: getUniqueIdentifier(),
    versionCode: 1,
    adaptiveIcon: {
      foregroundImage: './assets/adaptive-icon.png',
      backgroundColor: '#ffffff',
    },
    permissions: [
      // Add permissions as needed
      // 'android.permission.CAMERA',
      // 'android.permission.READ_EXTERNAL_STORAGE',
    ],
  },

  // Web Configuration (if applicable)
  web: {
    favicon: './assets/favicon.png',
    bundler: 'metro',
  },

  // Plugins
  plugins: [
    // Add Expo plugins here
    // 'expo-camera',
    // ['expo-image-picker', { photosPermission: '...' }],
  ],

  // Extra Configuration
  extra: {
    eas: {
      projectId: 'your-eas-project-id',
    },
    // Add custom config accessible via expo-constants
  },

  // Updates (EAS Update)
  updates: {
    url: 'https://u.expo.dev/your-project-id',
  },
  runtimeVersion: {
    policy: 'appVersion',
  },

  // Owner (for EAS)
  owner: 'your-expo-username',
};
```

## Key Configuration Fields

### App Identity

| Field | Description | Example |
|-------|-------------|---------|
| `name` | Display name | `"My App"` |
| `slug` | URL-friendly name | `"my-app"` |
| `version` | User-facing version | `"1.0.0"` |
| `ios.bundleIdentifier` | iOS bundle ID | `"com.company.app"` |
| `android.package` | Android package name | `"com.company.app"` |

### Versioning

| Field | Platform | Description |
|-------|----------|-------------|
| `version` | Both | Semantic version shown to users |
| `ios.buildNumber` | iOS | Internal build number (string) |
| `android.versionCode` | Android | Internal version code (integer) |

**Tip:** Use `autoIncrement` in EAS to manage build numbers automatically.

### Assets

| Field | Size | Format |
|-------|------|--------|
| `icon` | 1024x1024 | PNG |
| `splash.image` | 1284x2778 (or similar) | PNG |
| `android.adaptiveIcon.foregroundImage` | 1024x1024 | PNG |
| `web.favicon` | 48x48 | PNG |

## Permissions

### iOS Permissions (infoPlist)

Add to `ios.infoPlist`:

```javascript
infoPlist: {
  NSCameraUsageDescription: 'Required for taking photos',
  NSPhotoLibraryUsageDescription: 'Required for selecting photos',
  NSLocationWhenInUseUsageDescription: 'Required for location features',
  NSMicrophoneUsageDescription: 'Required for recording audio',
  NSFaceIDUsageDescription: 'Required for secure authentication',
}
```

### Android Permissions

Add to `android.permissions`:

```javascript
permissions: [
  'android.permission.CAMERA',
  'android.permission.READ_EXTERNAL_STORAGE',
  'android.permission.WRITE_EXTERNAL_STORAGE',
  'android.permission.ACCESS_FINE_LOCATION',
  'android.permission.RECORD_AUDIO',
]
```

## Config Plugins

For native configuration that goes beyond standard options:

```javascript
plugins: [
  // Simple plugin
  'expo-camera',

  // Plugin with options
  ['expo-image-picker', {
    photosPermission: 'Allow access to select photos',
  }],

  /
Files: 1
Size: 11.2 KB
Complexity: 15/100
Category: Web Dev

Related in Web Dev