Test with preview channels
Create an expiring channel per pull request from CI, release the build to it and hand testers a QR code that points their phone at it.
A preview channel is an ordinary channel with an expiry. The pipeline creates one per pull request, releases the pull request's build to it and deletes it when the pull request closes. A tester's phone follows it after one scan of a QR code, so a preview costs no store build and no TestFlight round.
Switch channels by link owns the URL scheme and the handler that make the QR code work.
Prerequisites
- Quickstart has run, so the SDK is installed and a first release is live.
- Ship every commit sets up a pipeline that builds the app and runs the CLI.
- An API token is stored as the CI secret
HOTCODEPUSH_TOKEN.
1. Create a channel per pull request
If the pipeline already creates the channel, skip to step 2. Name the channel after the pull request and give it an expiry, so a forgotten one goes away on its own.
npx hotcodepush channel create --name pr-42 --expires-in 14d --if-not-exists --jsonWith --if-not-exists the next push creates nothing twice: the command answers with the existing channel when the name is taken. With --json it prints the channel with the id the next steps use. channel create lists the expiry forms.
2. Release the pull request's build to it
If the pipeline already releases to the channel, skip to step 3. The flag --yes answers the confirmation a pipeline cannot answer, and --bundle-version names the bundle by its commit:
npx hotcodepush release create --channel pr-42 --bundle-version "$(git rev-parse --short HEAD)" --yesThe release applies within about a minute. A pipeline that must not publish runs --dry-run, which uploads nothing and needs no key. A signed app's job also holds the signing secret.
In GitHub Actions the commands fit one workflow:
name: Preview
on:
pull_request:
types: [opened, synchronize, reopened, closed]
env:
HOTCODEPUSH_TOKEN: ${{ secrets.HOTCODEPUSH_TOKEN }}
CHANNEL_NAME: pr-${{ github.event.pull_request.number }}
jobs:
release:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build
- name: Release to the preview channel
run: |
CHANNEL_ID=$(npx hotcodepush channel create --name "$CHANNEL_NAME" --expires-in 14d --if-not-exists --json | jq -r '.id')
npx hotcodepush release create --channel "$CHANNEL_ID" --bundle-version "$(git rev-parse --short HEAD)" --yes
echo "CHANNEL_ID=$CHANNEL_ID" >> "$GITHUB_ENV"
- name: Comment the QR code
env:
GH_TOKEN: ${{ github.token }}
run: |
APP_ID=$(jq -r '.appId' hotcodepush.json)
gh pr comment "${{ github.event.pull_request.number }}" --body ""
cleanup:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx hotcodepush channel delete --channel "$CHANNEL_NAME" --ignore-not-found --yesOn React Native and Expo the CLI runs the framework's bundling itself, so the pipeline skips npm run build. On Capacitor and Cordova that line is the web build.
In GitHub Actions a pull request checkout labels the bundle with the merge commit's short SHA, not the branch head's. The Azure DevOps, Bitbucket, Bitrise, Capawesome Cloud, Codemagic, EAS Build, Gitea Actions, GitHub Actions and GitLab recipes have each provider's token and trigger.
3. Hand it to testers
If the app's link template is set and the app handles the link, skip to step 4. The QR code encodes the channel's deep link, and the app's handler turns the link into a setChannel({ id }) call. Switch channels by link sets the template, registers the scheme and adds the handler and shows the QR route the workflow embeds.
A channel id in a public repository is public
Whoever reads the comment can point a build of the app at the channel and read everything released to it. Comment it in a private repository, or hand it over from the channel's Install dialog.
Which builds take a preview depends on the framework:
- React Native and Expo: a debug build served by the development server embeds no bundle and takes no update. Hand testers a release build or a TestFlight or internal-track build.
- Capacitor and Cordova: a debug build takes over-the-air (OTA) updates by default, so the developer's build on the phone follows the preview.
Once the tester scans the QR code, the app calls setChannel({ id }) and syncs. The release applies the way the app's apply strategy says, at the next start by default. If nothing happens, see Troubleshooting.
4. Clean up
Delete the channel when the pull request closes.
npx hotcodepush channel delete --channel pr-42 --ignore-not-found --yes--ignore-not-found succeeds when the channel is already gone, so a re-run deletes nothing twice.
A deleted or expired channel serves no index: a device on it keeps the bundle it runs and its channel, and every check fails with channel_unknown. The handler in Switch channels by link returns the tester's phone to the build's channel, and so does Return to configured channel on the SDK's debug screen.
Next steps
- Switch channels by link has the URL scheme, the handler per framework and the
setChannelcall behind the QR code. - GitHub Actions has the release workflow and its CLI step.
- Set up environments and tenants models a channel per environment or tenant.
Last updated on
Set up environments and tenants
Model staging, production and per-tenant builds as channels, promote releases between them, pause one in an incident and see what the public index exposes.
Switch channels by link
Register a URL scheme, handle the link in React Native, Expo, Capacitor or Cordova and call setChannel with the channel id it carries.