> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/xprem/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/xprem/eoas/publish-an-update.md).

# Publish an update

Publishing is one command. Two things decide whether it reaches users: the runtime version and authentication. Everything else is flags.

### Runtime version

The runtime version connects an update to a compatible native build. It must match exactly. Otherwise, the update is not delivered.

Neither side reports an error. This makes mismatches easy to miss.

[Expo's runtime versions guide](https://docs.expo.dev/eas-update/runtime-versions/) covers the concept in full.

#### Recommended: a fixed version

Set a fixed runtime version in `app.config.ts`:

```ts
runtimeVersion: '1.0.0',
```

Bump it before every build that changes native code. This includes native dependencies, config plugins, and files under `ios/` or `android/`.

JavaScript-only changes do not need a new runtime version. They keep matching installed builds, so you can publish them as updates.

#### The fingerprint policy

Expo also offers a fingerprint policy. It derives the runtime version from a hash of your native configuration:

```ts
runtimeVersion: { policy: 'fingerprint' },
```

The hash stays identical while native properties remain unchanged. You never need to bump a version manually.

{% hint style="warning" %}
We do not recommend the fingerprint policy unless you fully understand it. CI and local environments can produce different fingerprints for one commit. The only symptom is an update that never arrives.

Use it only when you can diagnose and control fingerprint mismatches.
{% endhint %}

Official documentation: <https://docs.expo.dev/versions/latest/sdk/fingerprint/>

### Authentication

EOAS authenticates with `EOO_TOKEN`, the app-scoped API key created in the dashboard. Set it in the environment that runs the publish command; see [EOAS overview](/xprem/eoas/overview.md) for how keys are created and scoped.

{% hint style="warning" %}
Without `EOO_TOKEN`, EOAS silently falls back to Expo authentication and the server rejects the request. A missing token therefore shows up as a rejected publish, not as a clear "token not set" error.
{% endhint %}

### Publish an update

Run this in your Expo project:

```bash
npx eoas publish --branch <branch-name>
```

EOAS resolves the runtime version, exports the bundle with `expo export`, hashes every file, uploads only the ones the server does not hold yet, and points the branch at the new update.

{% hint style="warning" %}
Pin `eoas` to your server version. A server on v3.2.0 or later refuses a publish from an older CLI, which sends neither file hashes nor file roles, with `unknown role` or `expected exactly one launch asset`.
{% endhint %}

#### Flags

| Flag                             | Default                   | Purpose                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| -------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--branch <name>`                | Required                  | Branch that receives the update                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `--platform <ios\|android\|all>` | `all`                     | Platforms to export and publish. `all` publishes the iOS and Android entries of `platforms` in the resolved app config, or both when `platforms` is unset. With `all`, a `platforms` list with neither `ios` nor `android` fails with `No iOS or Android platform is enabled in the Expo config.`                                                                                                                                                                                                                                                                                                       |
| `--message`, `-m <text>`         | Latest Git commit message | Description shown in the dashboard                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `--rollout-percentage <1-99>`    | None                      | Stages the update as a progressive rollout served to that share of devices, the rest keep the previous update                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `--outputDir <dir>`              | `dist`                    | Directory for the export                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `--packageRunner <bin>`          | Auto-detected             | Runner for `expo export` and `expo config`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--nonInteractive`               | `false`                   | Skips prompts; required in CI                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `--dumpSourcemap`                | `true`                    | Emits Hermes source maps alongside the bundle (passes `--dump-sourcemap` to `expo export`). Without a source map, Hermes bakes a random temporary path into the bytecode, so two exports of identical code never hash the same and the server cannot detect an unchanged update. A server in control-plane mode with `UPLOAD_SOURCEMAPS=true` stores each map (see [Source maps and symbolication](/xprem/observe/source-maps-and-symbolication.md)). The maps also stay in the output directory, where tools such as Sentry or PostHog can use them to symbolicate. Disable with `--no-dumpSourcemap`. |
| `--emitMetadata`                 | `false`                   | Writes `xprem-update-metadata.json` to the output directory, with the update ID of each published platform. The ID is the value `Updates.updateId` returns on the device. Requires the server on v3.2.4 or later.                                                                                                                                                                                                                                                                                                                                                                                       |

To stage an update on a fraction of your devices before delivering it to everyone, pass `--rollout-percentage`. How devices are selected, and how you progress, finish or revert the rollout from the dashboard, is covered in [Progressive rollouts](/xprem/eoas/progressive-rollouts.md).

#### Unchanged code is not republished

Before uploading anything, EOAS sends the hash of every file to the server. If the bundle and every asset match the latest update of the branch and runtime version, the server answers `You have already uploaded this update, no changes detected` and nothing is published. Exporting the same commit twice therefore never creates a duplicate update.

#### Environment variables

| Variable                          | Purpose                                                                                                                                                                                                                                     |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EOO_TOKEN`                       | App-scoped API key created in the dashboard. Required.                                                                                                                                                                                      |
| `RELEASE_CHANNEL`                 | Passed to your app config and bundle export.                                                                                                                                                                                                |
| `EOAS_PACKAGE_RUNNER`             | Overrides the package runner.                                                                                                                                                                                                               |
| `DEBUG`                           | Enables verbose logging.                                                                                                                                                                                                                    |
| `EOAS_ALLOW_INSECURE_UPLOAD_URLS` | The CLI accepts upload URLs over HTTPS only, and allows plain HTTP on loopback addresses. Set it to `1` or `true` to accept a non-loopback plain-HTTP endpoint, such as a self-hosted MinIO. Your update artifacts then travel unencrypted. |

{% hint style="warning" %}
EOAS does not load `.env` files. It runs `expo export` with `EXPO_NO_DOTENV=1`, disabling Expo CLI dotenv loading.

Every variable must exist in the process environment.
{% endhint %}

```bash
EOO_TOKEN=your_token RELEASE_CHANNEL=staging \
  npx eoas publish --branch staging
```

Or load a file explicitly with `dotenv`:

```bash
dotenv -e .env.local -- npx eoas publish --branch staging
```

#### Why set `RELEASE_CHANNEL`?

`RELEASE_CHANNEL` does not select the branch. Only `--branch` does.

EOAS builds your JavaScript bundle before publishing it. Set `RELEASE_CHANNEL` to build the app for the intended release channel.

This selects the channel-specific environment variables, configuration, and screens used in the bundle.

### Update message

Attach a short description with `--message` or `-m`:

```bash
npx eoas publish --branch production -m "Fix login crash on Android"
```

If omitted, EOAS uses the latest Git commit message. This matches EAS Update. The message appears in the dashboard updates table.

### Package runner

EOAS uses `npx` by default to run Expo CLI commands. These include `expo export` and `expo config`. Override it for another package manager.

Resolution priority:

1. `--packageRunner` CLI flag
2. `EOAS_PACKAGE_RUNNER` environment variable
3. `packageManager` in the nearest `package.json`
4. `npx`

| `packageManager` value | Resolved runner |
| ---------------------- | --------------- |
| `bun@x.x.x`            | `bunx`          |
| `pnpm@x.x.x`           | `pnpm exec`     |
| `yarn@x.x.x`           | `npx`           |
| `npm@x.x.x`            | `npx`           |

```bash
# Auto-detected from package.json
npx eoas publish --branch production

# Explicit flag
npx eoas publish --branch production --packageRunner bunx

# Environment variable
EOAS_PACKAGE_RUNNER=bunx eoas publish --branch production
```

### CI/CD

Automate publishing with `npx eoas publish --nonInteractive`.

Set `EOO_TOKEN` as a CI secret and keep `--nonInteractive` so the command never waits for a prompt.

```bash
EOO_TOKEN=$EOO_TOKEN RELEASE_CHANNEL=production \
  npx eoas publish --branch production --nonInteractive
```

#### A clean working tree is required

`eoas publish` refuses to run when the git working tree is dirty, and untracked files count as changes. With `--nonInteractive` it prints the files that still need to be committed and aborts with `Commit all changes. Aborting...`. Run interactively, it offers to commit everything for you before continuing.

Build environments that generate files as part of the pipeline have two escape hatches. Pass `--disableRepositoryCheck` to skip the check for that run, or set the `EAS_NO_VCS` environment variable to run the command without version control at all.

### Troubleshooting

When the server cannot finalize the update of a platform, EOAS prints `Failed to mark the update as finished for platform <platform>` followed by the server's answer:

* `invalid update: invalid expoConfig.json: <detail>`: the uploaded `expoConfig.json` is empty, is not valid JSON, or has content after the JSON document.
* `invalid update: missing sourcemap <hash> in update`: the source map recorded for the update is not in the source-map destination.
* `Internal server error`: a storage or database call failed on the server. The server log has the cause. EOAS retries the request up to four times before failing.

On an `invalid update` answer, the server discards the update before any device receives it.
