Claude Code Plugins

Community-maintained marketplace

Feedback

jutsu-expo:expo-config

@TheBushidoCollective/han
38
0

Use when configuring Expo apps with app.json, app.config.js, and EAS configuration. Covers app metadata, plugins, build configuration, and environment variables.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name jutsu-expo:expo-config
description Use when configuring Expo apps with app.json, app.config.js, and EAS configuration. Covers app metadata, plugins, build configuration, and environment variables.
allowed-tools Read, Write, Edit, Bash, Grep, Glob

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