> 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/concepts/branch-surfing.md).

# Branch surfing

Branch surfing lets a device ask the update server for a branch other than the one its channel maps to. It is meant for testing: one TestFlight or Play build can receive updates from any compatible branch, so several people can test several branches in parallel without building the app several times.

The feature is opt-in and scoped to a channel. It is disabled by default, and a channel where it stays disabled behaves exactly as described in [Branches and release channels](/xprem/concepts/branches-and-release-channels.md).

### How it works

A device on a surfing-enabled channel can send an extra request header, `xprem-branch`, with the name of the branch it wants. When the server receives an update check with that header, it serves the requested branch instead of the channel's mapped branch, as long as the channel allows it and the branch has something to serve.

In practice you do not send that header by hand. The `@xprem/control-center` package ships an in-app panel that lists the branches available to the build and applies the choice through `expo-updates`. The package is plain JavaScript, so it can itself be delivered over the air to builds already installed on testers' devices.

```mermaid
sequenceDiagram
    participant App as Tester's build
    participant Server as xprem server
    App->>Server: GET /branch_lists
    Server-->>App: branches compatible with this build
    App->>Server: update check with xprem-branch: feature-checkout
    Server-->>App: latest update from feature-checkout
```

Only compatible branches are offered. A branch appears in the list when it has at least one published update for the build's exact runtime version and platform. A branch that changed native code has no update for the old runtime version, so it is simply not listed rather than failing after the download.

### Enabling it on a channel

Branch surfing is configured per channel, from the dashboard's **Channels** page. Each channel has a **Branch surfing** section with two settings that are always saved together:

* a switch that turns the feature on or off for the channel, and
* a pattern that says which branches the channel exposes.

The pattern is a branch name where `*` matches any run of characters. `pr-*` exposes every branch starting with `pr-`, `*-eu` every branch ending in `-eu`, and `*` every branch of the app. A pattern without `*` exposes exactly one branch. An empty pattern matches nothing, which is why the switch cannot be turned on until a pattern is set.

{% hint style="warning" %}
The `*` pattern exposes every branch of the app, including branches created later. Devices read branch names from an unauthenticated endpoint, so on a production channel a narrow pattern is the safer choice, and leaving the feature off is safer still.
{% endhint %}

Changing the setting requires the `channel:branch-surfing` permission, which falls back to admin-only when unset. Every change is recorded in the audit log as `channel_branch_surfing.updated`.

### What the app needs

The in-app panel comes from the `@xprem/control-center` package. It needs:

* Expo SDK 54 or newer, because it relies on `setUpdateRequestHeadersOverride` from `expo-updates`.
* Three headers declared in `updates.requestHeaders` at build time: `expo-channel-name`, `expo-app-id` and `xprem-branch`.

`eoas init` writes the three headers for you. They matter because `expo-updates` only accepts runtime overrides for headers that existed when the app was built. Switching branches replaces the whole header set, so a build missing one of them would drop that header from every subsequent update check, and the server answers such requests with a 400. No update could then reach the device to repair it. The panel checks for the three headers first and refuses to appear if one is missing, logging which one.

For the same reason, declare `expo-channel-name` as a literal string rather than `process.env.SOMETHING`. The app config is evaluated when the JS bundle is exported, so an unset variable silently removes the key. Only the key needs to be declared; at runtime the device always sends its build's real channel.

### How the server decides

When an update check arrives, the server resolves the branch in this order:

1. If the request names a branch, the channel has surfing enabled, and the name matches the channel's pattern, that branch is served.
2. Otherwise, if the channel has an active [progressive rollout](/xprem/eoas/progressive-rollouts.md), the rollout draw decides.
3. Otherwise, the channel's mapped branch is served.

Two details of this order are worth knowing:

* A surfing device skips the rollout draw. Its explicit choice wins over the percentage split.
* Choosing the channel's own mapped branch does not count as surfing. The request falls through to the normal path, so those devices keep taking part in the rollout.

If the surfed branch has no update for the device's runtime version and platform, the server falls back to the channel's mapped branch instead of leaving the device without updates.

### When a surfed update crashes

A surfed branch can contain broken work in progress. If an update fails to launch, `expo-updates` rolls the device back to the bundle embedded in the binary, so the build cannot be bricked. On the next update check the device reports the failed update, and the server stops serving that exact update to that device. The device falls back to the channel's mapped branch, and the panel shows which branch was refused.

The block is per update, not per branch. To get the device moving again, in order of preference:

1. Publish a fix to the branch being tested. The new update is served normally.
2. Turn branch surfing off for the channel, which returns every device to the mapped branch.
3. Reinstall the app, which clears the stored choice.

### Notes and limits

* Branch surfing needs a [control-plane server](/xprem/stateless-mode/overview.md). In stateless mode the feature is unavailable and the branch list endpoint answers 404.
* The branch list is served by `GET /branch_lists`, an unauthenticated endpoint that lives next to the manifest endpoint. It only answers for channels where surfing is enabled, and it answers 404 identically for unknown channels and for channels where surfing is off, so it cannot be used to discover channel names. Responses are sent with `cache-control: private, max-age=0` and must not be cached by a CDN.
* Settings are cached briefly on the server (30 seconds for the channel setting, 15 seconds for the branch list), so a change can take a few seconds to reach every replica.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mercure-technologies.gitbook.io/xprem/concepts/branch-surfing.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
