> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revyl.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Expo

> Run the dev loop on an Expo project

Expo projects use full hot reload via a Revyl relay to your local Metro server. JS/TS changes appear on the device within seconds. Press `[r]` only when native dependencies change.

For build and upload steps, see [Expo Build Guide](/builds/frameworks/expo).

### How detection works

The CLI looks for two things in the current directory:

1. `"expo"` in `package.json` dependencies or devDependencies
2. At least one project indicator: `app.json`, `app.config.js`, `app.config.ts`, `eas.json`, or `.expo/` directory

If both are present, the Expo provider matches at **confidence 0.9** — the highest of any provider. This means Expo wins over Swift (0.7) and Android (0.6) when all three detect the same directory.

If either condition fails (common in monorepos), the Expo provider returns nil and lower-confidence providers may match instead. See the monorepo section below.

### URL schemes

Hot reload deep-links the dev client to your local Metro server via a custom URL scheme:

```
myapp://expo-development-client/?url=https://hr-abc123.revyl.ai
```

This requires a **custom URL scheme** (`myapp://`) registered in the dev client binary.

#### Where to find your scheme

Check `app.json` under `expo.scheme`:

```json theme={null}
{
  "expo": {
    "scheme": "myapp"
  }
}
```

Or in `app.config.js` / `app.config.ts`:

```javascript theme={null}
export default {
  expo: {
    scheme: "myapp",
  }
};
```

The CLI reads this value during `revyl init` and stores it as `hotreload.providers.expo.app_scheme` in `.revyl/config.yaml`.

#### Why universal links don't work

Apps that only use universal links (`https://example.com/...`) for deep linking cannot use those for hot reload. Apple's associated domains system requires a live HTTPS domain serving an `apple-app-site-association` file. Revyl hot reload uses short-lived relay hosts that are not suitable as stable associated domains. Custom URL schemes (`myapp://`) bypass this entirely — no server verification needed.

#### No URL scheme in the app

If your app has no `expo.scheme` (common in apps that only use universal links), you need to add one. The name is arbitrary and only affects the dev client:

```json theme={null}
{
  "expo": {
    "scheme": "myapp-dev"
  }
}
```

After adding the scheme, **rebuild the dev client** — the scheme is baked into the native binary at build time:

```bash theme={null}
revyl build --platform ios-dev
```

Then set `app_scheme: myapp-dev` in `.revyl/config.yaml`.

#### Checking existing schemes without rebuilding

The dev client may already have a scheme registered by `expo-dev-client`. Check the iOS build:

```bash theme={null}
grep -r "CFBundleURLSchemes" ios/ --include="*.plist" -A3
```

If this returns something like `exp+myslug`, you can use that scheme in your config without rebuilding.

#### The `use_exp_prefix` option

Expo dev clients can register URL schemes in two formats:

* **Base scheme**: `myapp://` — registered when `expo.scheme` is set in app.json
* **Prefixed scheme**: `exp+myapp://` — auto-registered by `expo-dev-client` based on the app slug

The CLI defaults to the base scheme. If deep links fail with "No application is registered to handle this URL scheme", the binary may only have the prefixed variant. Toggle `use_exp_prefix` in your config:

```yaml theme={null}
hotreload:
  providers:
    expo:
      app_scheme: myapp
      use_exp_prefix: true
```

**Why isn't this automatic?** The CLI runs on your local machine while the dev client runs on a remote cloud simulator. The CLI cannot introspect which URL schemes the installed binary has registered. The `expo.scheme` field in `app.json` defines the base scheme, but whether `expo-dev-client` also registers `exp+<scheme>://` depends on the Expo SDK version and the `addGeneratedScheme` setting at build time. Since there's no way to query this at runtime, the config toggle is the escape hatch.

### Monorepo setup

In monorepos (Turborepo, Nx, pnpm workspaces), the Expo app typically lives in a subdirectory like `apps/native/`, `apps/mobile/`, or `packages/app/`.

#### Run from the Expo app directory

All Revyl commands must run from the directory containing the Expo app's `package.json` — **not the monorepo root**.

```bash theme={null}
cd apps/native
revyl init --provider expo
revyl dev --platform ios
```

The CLI resolves the working directory by calling `FindRepoRoot`, which walks up from the current directory looking for `.revyl/`. If the monorepo root also has a `.revyl/` directory (common for CI test configs), make sure you're in the correct subdirectory.

#### Why detection fails in monorepos

Two things go wrong:

1. **Hoisted dependencies** — Package managers like pnpm, Yarn, and npm may hoist `expo` to the root `node_modules/` or use `workspace:*` protocol in the local `package.json`. The Expo provider reads the local `package.json` and checks for `"expo"` in `dependencies` or `devDependencies`. If it's not there, the provider returns nil (no match).

2. **Native directories match other providers** — Every Expo project with prebuild has `ios/` containing `.xcodeproj` files (triggering the Swift provider at confidence 0.7) and `android/` containing `build.gradle` (triggering the Android provider at confidence 0.6). When the Expo provider returns nil, these become the top matches.

The result: `revyl init` detects "Swift/iOS (coming soon)" and "Android (coming soon)" instead of Expo.

**Fix**: Use `--provider expo` to bypass auto-detection:

```bash theme={null}
revyl init --provider expo
```

Or, if `expo` is genuinely missing from the local `package.json`, add it:

```bash theme={null}
npx expo install expo
```

#### Example hotreload config for a monorepo app

```yaml theme={null}
# apps/native/.revyl/config.yaml
hotreload:
  default: expo
  providers:
    expo:
      app_scheme: myapp
      port: 8081
      platform_keys:
        ios: ios-dev
        android: android-dev
```

### What to expect when the deep link opens

After `revyl dev` starts, the CLI opens a deep link to connect the dev client to your local Metro server. You may notice:

1. **A confirmation dialog** -- iOS shows "Open in \[Your App]?" with Cancel and Open buttons. This is a standard iOS security prompt for URL scheme handoffs. Tap **Open** to proceed. (On cloud simulators this currently requires a manual tap; future CLI versions will auto-accept it.)

2. **The app briefly restarts** -- The dev client may close and reopen as it disconnects from its cached state and reconnects to the tunnel URL. This looks jarring but is normal Expo dev client behavior. The app is reloading to fetch the JS bundle from your local Metro server instead of its built-in bundle.

3. **Sometimes no restart at all** -- If the dev client is already in a fresh state (first launch after install), it may connect seamlessly without closing. The behavior varies by Expo SDK version and whether the app was previously running.

Once the app is back up and showing your app's UI, hot reload is active. Edit a file locally, save, and the change appears on the device within seconds.

If the app closes and **doesn't come back**, check the Metro/Expo terminal output for JavaScript errors. A crash during bundle loading usually means a missing native module or environment variable issue -- not a Revyl problem.

### Dynamic config (app.config.js / app.config.ts)

The CLI reads the URL scheme from `app.json` at `expo.scheme`. If the project uses `app.config.js` or `app.config.ts` instead, the CLI cannot auto-detect the scheme because evaluating arbitrary JavaScript is out of scope.

The scheme is the custom URL prefix baked into the Expo dev client, such as
`myapp-dev://`. Revyl uses it to open the installed dev client on the cloud
device and connect it to Metro for hot reload.

Provide the scheme explicitly during init:

```bash theme={null}
revyl init --provider expo --hotreload-app-scheme myapp
```

Or set it manually in `.revyl/config.yaml` after init:

```yaml theme={null}
hotreload:
  providers:
    expo:
      app_scheme: myapp
```

If you add or change the scheme, rebuild the Expo dev client once. After that,
JS/TS changes can hot reload through `revyl dev` without another build.

### Custom dev server port

If Metro runs on a non-default port (common in monorepos with multiple Metro instances), set it in config or as a flag:

```yaml theme={null}
hotreload:
  providers:
    expo:
      port: 8082
```

```bash theme={null}
revyl dev --platform ios --port 8082
```
