# Eas

> Use when configuring EAS Build, Submit, or Update -- eas.json profiles, OTA updates, App Store/Play Store submission, GitHub Actions CI, EAS secrets.

- Skill: `shankulkarni/eas` (Agent Skill)
- Install (CLI): `npx skillmds@latest add shankulkarni/eas`
- Raw SKILL.md: https://api.skillmd.com/api/skills/shankulkarni/eas/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Shankulkarni (https://skillmd.com/u/shankulkarni)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/shankulkarni/eas

---


# EAS -- Build + Submit + Update

Expo Application Services. Builds, submits, and updates your app.

## `eas.json`

```json
{
  "cli": { "version": ">= 12.0.0", "appVersionSource": "remote" },
  "build": {
    "base": {
      "node": "22.0.0",
      "env": {
        "EXPO_PUBLIC_API_URL": "https://api.example.com"
      }
    },
    "development": {
      "extends": "base",
      "developmentClient": true,
      "distribution": "internal",
      "env": { "EXPO_PUBLIC_API_URL": "https://dev-api.example.com" },
      "ios": { "simulator": true }
    },
    "preview": {
      "extends": "base",
      "distribution": "internal",
      "channel": "preview"
    },
    "production": {
      "extends": "base",
      "autoIncrement": true,
      "channel": "production"
    }
  },
  "submit": {
    "production": {
      "ios": { "appleId": "dev@example.com", "ascAppId": "1234567890" },
      "android": { "serviceAccountKeyPath": "./service-account.json", "track": "internal" }
    }
  }
}
```

## Build Commands

```bash
# Development build (custom dev client)
bunx eas build --profile development --platform ios
bunx eas build --profile development --platform android

# Preview (internal distribution)
bunx eas build --profile preview --platform all

# Production
bunx eas build --profile production --platform all

# Local build (no EAS servers)
bunx eas build --profile development --platform ios --local
```

## OTA Updates

```bash
# Publish update to a channel
bunx eas update --channel preview --message "Fix login crash"
bunx eas update --channel production --message "v1.2.3 release"

# Check update status
bunx eas update:list
```

`app.json` update config:
```json
{
  "expo": {
    "updates": {
      "url": "https://u.expo.dev/YOUR-PROJECT-ID",
      "enabled": true,
      "fallbackToCacheTimeout": 0,
      "checkAutomatically": "ON_LOAD"
    },
    "runtimeVersion": { "policy": "appVersion" }
  }
}
```

Handle updates in root `_layout.tsx`:
```tsx
import * as Updates from 'expo-updates'
import { useEffect } from 'react'

export default function RootLayout() {
  // OTA update check is an SDK imperative call, not server data fetching --
  // React Query doesn't apply here. This is the only acceptable useEffect+async pattern.
  useEffect(() => {
    async function checkUpdate() {
      if (__DEV__) return
      const update = await Updates.checkForUpdateAsync()
      if (update.isAvailable) {
        await Updates.fetchUpdateAsync()
        await Updates.reloadAsync()
      }
    }
    checkUpdate()
  }, [])
  // ...
}
```

## Submit to Stores

```bash
# After a production build
bunx eas submit --platform ios --latest
bunx eas submit --platform android --latest

# Submit specific build
bunx eas submit --platform ios --id BUILD_ID
```

## EAS Secrets

```bash
# Set secrets (never commit these)
bunx eas secret:create --scope project --name API_SECRET_KEY --value "..."
bunx eas secret:create --scope project --name SENTRY_DSN --value "..."

# List secrets
bunx eas secret:list
```

Access in build via `eas.json` `env`:
```json
{ "build": { "production": { "env": { "SENTRY_DSN": "$SENTRY_DSN" } } } }
```

## GitHub Actions CI

`.github/workflows/eas-build.yml`:
```yaml
name: EAS Build
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - uses: expo/expo-github-action@v8
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: bunx eas build --profile production --platform all --non-interactive
```

`.github/workflows/eas-update.yml`:
```yaml
name: EAS Update
on:
  push:
    branches: [main]

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - uses: expo/expo-github-action@v8
        with: { eas-version: latest, token: '${{ secrets.EXPO_TOKEN }}' }
      - run: bunx eas update --channel production --message "${{ github.event.head_commit.message }}" --non-interactive
```

## Rules

- `eas.json` must have `development`, `preview`, and `production` profiles
- Never hardcode API URLs in `eas.json` -- use EAS secrets or env vars
- Never commit `service-account.json` or Apple credentials -- use EAS secrets
- `concurrency.cancel-in-progress: false` in CI -- never cancel in-flight builds
- `runtimeVersion` policy must be consistent across OTA updates and builds
- Always test OTA update compatibility before shipping to production

