# Expo OTA updates with Pakta

Configure the root layout, native channels and a Release build, then publish an update.

## Choose your Expo update workflow {#update-workflow}

This guide uses Pakta's `rn-update` client and `pakta` CLI for Expo OTA updates. If you are comparing `expo-updates` and EAS Update, first read [Expo Updates vs Pakta](/docs/expo-updates-vs-pakta). Moving from another OTA client requires a new native build with one system responsible for loading the bundle.

## Prepare {#before}

Use a native Expo app, not Expo Go. This project's iOS automatic integration requires Expo 50 or later. Keep the existing compatible Expo/RN combination.

Install and select an application from the project root:

```bash
npx expo install rn-update
npm install -g rn-update-cli
pakta login
pakta selectApp --platform android
```

Create an application in the console if needed, or run `pakta createApp --platform android --name ExpoDemo`. Select the matching `ios` application separately.

## Wrap the root layout {#layout}

For Expo Router, add the Provider to the existing `app/_layout.tsx`. This minimal Stack example must be merged with your theme, fonts and authentication setup:

```tsx title="app/_layout.tsx"
import { Stack } from 'expo-router';
import { Platform } from 'react-native';
import { Pakta, UpdateProvider } from 'rn-update';
import updateConfig from '../update.json';

const configs = updateConfig as Record<string, { appKey: string }>;
const appKey = configs[Platform.OS]?.appKey;
if (!appKey) throw new Error(`Missing appKey for ${Platform.OS}`);

const client = new Pakta({ appKey, updateStrategy: 'silentAndLater' });

export default function RootLayout() {
  return (
    <UpdateProvider client={client}>
      <Stack />
    </UpdateProvider>
  );
}
```

For `src/app/_layout.tsx`, import `../../update.json`. Without Expo Router, follow the [standard root component setup](/docs/integration#basic).

## Optional: set a channel {#channel}

Skip this section for `default`. [Create the custom channel in the console](/docs/channels#manage) first. `rn-update` includes the Config Plugin; do not create a local plugin.

Add this entry to the existing `expo.plugins` array in `app.json`:

```json title="app.json · merge into existing configuration"
{
  "expo": {
    "plugins": [
      ["rn-update", { "channel": "staging" }]
    ]
  }
}
```

The `channel` is written to Android manifest metadata and iOS `Info.plist`. Use separate build configurations when Android and iOS need different channels. No plugin entry is needed for `default`.

## Build and inspect {#build}

Run `npx expo prebuild` and verify `channel` in the generated Android manifest or iOS plist. EAS Build runs the same plugin.

```bash title="Android"
npx expo run:android --variant release
```

```bash title="iOS · macOS"
npx expo run:ios --configuration Release
```

An existing EAS production workflow also works. Verify hot updates in the Release app.

## Upload and publish {#publish}

1. Retain the exact APK / IPA installed on the device and register it with `uploadApk` / `uploadIpa`; see [native registration](/docs/publish#register-native).
2. Change a screen label and build an update:

```bash
pakta bundle --platform android --expo --output .pakta/output/android.ppk --no-interactive
```

3. Follow [first release](/docs/publish#publish), using the channel and version parsed from the native installer.
4. Check in the installed Release app, wait for download, fully close and reopen, then verify the changed label.

For iOS use `--platform ios` and a separate output path. Native plugins, channels and native SDK changes require a new installer, not just a `.ppk`.
