|
| 1 | +--- |
| 2 | +slug: turn-every-pr-into-installable-preview |
| 3 | +title: "Turn Every Pull Request Into an Installable Preview" |
| 4 | +description: "Stop waiting for TestFlight processing. Capgo PR previews let QA, PMs, and stakeholders test features on real devices in under a minute." |
| 5 | +author: Martin Donadieu |
| 6 | +author_image_url: 'https://avatars.githubusercontent.com/u/4084527?v=4' |
| 7 | +author_url: 'https://x.com/martindonadieu' |
| 8 | +created_at: 2026-01-22T00:00:00.000Z |
| 9 | +updated_at: 2026-01-22T00:00:00.000Z |
| 10 | +head_image: /capgo_ci-cd-illustration.webp |
| 11 | +head_image_alt: Capgo PR preview illustration |
| 12 | +keywords: pr preview, pull request, OTA updates, capacitor, capgo, live updates, QA testing, feature preview, github actions |
| 13 | +tag: Tutorial |
| 14 | +published: true |
| 15 | +locale: en |
| 16 | +next_blog: '' |
| 17 | +--- |
| 18 | + |
| 19 | +Every mobile dev team has felt the pain: a feature is ready for review, but getting it into stakeholders' hands means navigating the TestFlight or Google Play beta review maze. What should take minutes turns into hours of waiting, installing, and managing beta builds. |
| 20 | + |
| 21 | +What if your production app could pull the latest changes from any pull request directly onto the device, without any reinstalls or app store delays? |
| 22 | + |
| 23 | +That's what **PR previews** enable. When a developer opens a pull request, a GitHub Action creates a dedicated update channel and publishes the changes. Anyone with the app installed can switch to that channel, test the feature, and switch back - all without leaving the app they already have. |
| 24 | + |
| 25 | +## The TestFlight Problem |
| 26 | + |
| 27 | +The traditional workflow for testing mobile features looks something like this: |
| 28 | + |
| 29 | +1. **Developer opens PR** - Code is ready for review |
| 30 | +2. **Wait for TestFlight** - 15-30 minutes of processing time |
| 31 | +3. **Find and install** - Testers search for the right build |
| 32 | +4. **Test and repeat** - Every change means another wait |
| 33 | + |
| 34 | +This creates a bottleneck. QA gets blocked waiting for builds. Product managers can't verify features quickly. Developers lose context while waiting for feedback. The industry estimates this costs around $340 per PR in lost productivity. |
| 35 | + |
| 36 | +## How PR Previews Work |
| 37 | + |
| 38 | +PR previews use Capgo's channel system to create per-PR update streams. Here's the flow: |
| 39 | + |
| 40 | +1. **PR opened or updated** - GitHub Action triggers |
| 41 | +2. **Bundle uploaded** - Your JS/CSS changes go to a PR-specific channel |
| 42 | +3. **Comment posted** - Testers get instructions in the PR |
| 43 | +4. **Instant testing** - Switch channels, test, switch back |
| 44 | + |
| 45 | +No new app installations. No TestFlight delays. The same production app can pull from different update channels. |
| 46 | + |
| 47 | +## Setting Up PR Previews |
| 48 | + |
| 49 | +Before you can implement PR previews, your project needs to be configured with Capgo Live Updates. Follow the [Capgo quickstart guide](/docs/getting-started/quickstart/) if you haven't already. |
| 50 | + |
| 51 | +### GitHub Actions Workflow |
| 52 | + |
| 53 | +Create `.github/workflows/pr-preview.yml`: |
| 54 | + |
| 55 | +```yaml |
| 56 | +name: PR Preview |
| 57 | +on: |
| 58 | + pull_request: |
| 59 | + types: [opened, synchronize] |
| 60 | + |
| 61 | +jobs: |
| 62 | + deploy: |
| 63 | + runs-on: ubuntu-latest |
| 64 | + steps: |
| 65 | + - uses: actions/checkout@v4 |
| 66 | + |
| 67 | + - name: Setup Bun |
| 68 | + uses: oven-sh/setup-bun@v1 |
| 69 | + |
| 70 | + - name: Install Dependencies |
| 71 | + run: bun install |
| 72 | + |
| 73 | + - name: Build |
| 74 | + run: bun run build |
| 75 | + |
| 76 | + # Create a channel named after your PR (may already exist on synchronize) |
| 77 | + - name: Create PR Channel |
| 78 | + id: create_channel |
| 79 | + continue-on-error: true |
| 80 | + run: bunx @capgo/cli@latest channel add pr-${{ github.event.pull_request.number }} --self-assign |
| 81 | + env: |
| 82 | + CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }} |
| 83 | + |
| 84 | + # Upload the build to that channel |
| 85 | + - name: Upload to Capgo |
| 86 | + run: bunx @capgo/cli@latest bundle upload --channel pr-${{ github.event.pull_request.number }} |
| 87 | + env: |
| 88 | + CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }} |
| 89 | + |
| 90 | + # Post a comment with testing instructions (only on PR open) |
| 91 | + - name: Comment on PR |
| 92 | + if: github.event.action == 'opened' |
| 93 | + uses: actions/github-script@v7 |
| 94 | + with: |
| 95 | + script: | |
| 96 | + github.rest.issues.createComment({ |
| 97 | + owner: context.repo.owner, |
| 98 | + repo: context.repo.repo, |
| 99 | + issue_number: ${{ github.event.pull_request.number }}, |
| 100 | + body: '📱 **Test this PR on device:**\n\nOpen your app and switch to channel: `pr-${{ github.event.pull_request.number }}`\n\nUse the shake menu or call `setChannel()` from your app.' |
| 101 | + }) |
| 102 | +``` |
| 103 | +
|
| 104 | +The key is the `--self-assign` flag when creating the channel. This enables testers to switch to the channel from within the app using the `setChannel()` API. |
| 105 | + |
| 106 | +### Setting Up Capgo Token |
| 107 | + |
| 108 | +1. Go to your [Capgo dashboard](https://web.capgo.app) |
| 109 | +2. Navigate to Settings > API Keys |
| 110 | +3. Generate a new key with `all` permissions |
| 111 | +4. Add it as `CAPGO_TOKEN` in your GitHub repository secrets |
| 112 | + |
| 113 | +## How Testers Switch Channels |
| 114 | + |
| 115 | +There are two ways for testers to switch to a PR channel: |
| 116 | + |
| 117 | +### Option 1: Shake Menu (Simplest) |
| 118 | + |
| 119 | +Enable the shake menu with channel selector in your Capacitor config: |
| 120 | + |
| 121 | +```typescript |
| 122 | +// capacitor.config.ts |
| 123 | +const config: CapacitorConfig = { |
| 124 | + // ... your other config |
| 125 | + plugins: { |
| 126 | + CapacitorUpdater: { |
| 127 | + shakeMenu: true, |
| 128 | + allowShakeChannelSelector: true |
| 129 | + } |
| 130 | + } |
| 131 | +}; |
| 132 | +``` |
| 133 | + |
| 134 | +Testers shake their device to open the debug menu, which shows a list of available channels with a search bar. They find their PR channel (e.g., `pr-123`), tap to select it, and the app automatically downloads and applies the update. When done testing, they shake again and switch back to production. |
| 135 | + |
| 136 | +The shake menu handles the entire flow automatically: |
| 137 | +1. Fetches all self-assignable channels via `listChannels()` |
| 138 | +2. Displays channels with search to find specific PRs |
| 139 | +3. Downloads the update after selection |
| 140 | +4. Prompts to reload with "Reload Now" / "Later" options |
| 141 | + |
| 142 | +### Option 2: Custom Channel Selector UI |
| 143 | + |
| 144 | +Build a channel switcher into your app that lists available PR channels and lets testers pick one. This uses two key APIs: |
| 145 | + |
| 146 | +- `listChannels()` - Fetches all channels with self-assignment enabled |
| 147 | +- `setChannel()` - Switches the device to the selected channel |
| 148 | + |
| 149 | +```typescript |
| 150 | +import { CapacitorUpdater } from '@capgo/capacitor-updater'; |
| 151 | +
|
| 152 | +// Get all available channels (including PR channels) |
| 153 | +async function getAvailableChannels() { |
| 154 | + const { channels } = await CapacitorUpdater.listChannels(); |
| 155 | +
|
| 156 | + // Filter to show only PR channels |
| 157 | + const prChannels = channels.filter(c => c.name.startsWith('pr-')); |
| 158 | +
|
| 159 | + return prChannels; |
| 160 | +} |
| 161 | +
|
| 162 | +// Switch to a specific PR channel |
| 163 | +async function switchToChannel(channelName: string) { |
| 164 | + await CapacitorUpdater.setChannel({ |
| 165 | + channel: channelName, |
| 166 | + triggerAutoUpdate: true // Immediately check for updates |
| 167 | + }); |
| 168 | +} |
| 169 | +
|
| 170 | +// Return to production |
| 171 | +async function switchBackToProduction() { |
| 172 | + await CapacitorUpdater.unsetChannel({}); |
| 173 | +} |
| 174 | +
|
| 175 | +// Get current channel |
| 176 | +async function getCurrentChannel() { |
| 177 | + const { channel } = await CapacitorUpdater.getChannel(); |
| 178 | + return channel; |
| 179 | +} |
| 180 | +``` |
| 181 | + |
| 182 | +With these building blocks, you can create a simple UI: |
| 183 | + |
| 184 | +```typescript |
| 185 | +// Example: List PR channels and let user select |
| 186 | +const channels = await getAvailableChannels(); |
| 187 | +const current = await getCurrentChannel(); |
| 188 | +
|
| 189 | +// Display channels in your UI |
| 190 | +channels.forEach(channel => { |
| 191 | + console.log(`${channel.name} ${channel.name === current ? '(current)' : ''}`); |
| 192 | +}); |
| 193 | + |
| 194 | +// When user selects a channel |
| 195 | +await switchToChannel('pr-123'); |
| 196 | +``` |
| 197 | + |
| 198 | +For a complete React component example, see [our channel surfing article](/blog/how-capgo-channel-switching-works/). |
| 199 | + |
| 200 | +## Cleaning Up PR Channels |
| 201 | + |
| 202 | +When a PR is merged or closed, you'll want to clean up the channel. Add another workflow: |
| 203 | + |
| 204 | +```yaml |
| 205 | +name: Cleanup PR Preview |
| 206 | +on: |
| 207 | + pull_request: |
| 208 | + types: [closed] |
| 209 | + |
| 210 | +jobs: |
| 211 | + cleanup: |
| 212 | + runs-on: ubuntu-latest |
| 213 | + steps: |
| 214 | + - name: Delete PR Channel |
| 215 | + run: bunx @capgo/cli@latest channel delete pr-${{ github.event.pull_request.number }} |
| 216 | + env: |
| 217 | + CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }} |
| 218 | +``` |
| 219 | +
|
| 220 | +This removes the channel when the PR is closed, keeping your channel list clean. |
| 221 | +
|
| 222 | +## Version Compatibility |
| 223 | +
|
| 224 | +PR previews only work when the JavaScript bundle is compatible with the installed native version. If your PR includes native code changes (new Capacitor plugins, iOS/Android modifications), testers will need a new native build. |
| 225 | +
|
| 226 | +Capgo automatically checks version compatibility. If a PR's bundle targets a different native version than what's installed, the update won't be applied. This prevents crashes from incompatible code. |
| 227 | +
|
| 228 | +For PRs that do require native changes, you'll need to distribute a new TestFlight/Play Store build. PR previews work best for JavaScript, CSS, and asset changes that don't touch native code. |
| 229 | +
|
| 230 | +## Who Benefits from PR Previews |
| 231 | +
|
| 232 | +### QA Engineers |
| 233 | +
|
| 234 | +- Test features immediately when PRs are opened |
| 235 | +- Switch between multiple PRs without reinstalling |
| 236 | +- Verify fixes and regressions on real devices |
| 237 | +- No more waiting for TestFlight processing |
| 238 | +
|
| 239 | +### Product Managers |
| 240 | +
|
| 241 | +- Review features before they're merged |
| 242 | +- Give feedback directly on the PR |
| 243 | +- Verify that implementation matches requirements |
| 244 | +- Reduce review cycle time |
| 245 | +
|
| 246 | +### Developers |
| 247 | +
|
| 248 | +- Get faster feedback on changes |
| 249 | +- Demo features to stakeholders instantly |
| 250 | +- Debug issues with specific users |
| 251 | +- Spend less time managing beta builds |
| 252 | +
|
| 253 | +## Comparison: Traditional vs PR Previews |
| 254 | +
|
| 255 | +| Aspect | TestFlight/Beta | Capgo PR Preview | |
| 256 | +|--------|----------------|------------------| |
| 257 | +| Build time | 15-30 min | <1 min | |
| 258 | +| Switching PRs | 5+ min reinstall | 10 seconds | |
| 259 | +| Setup complexity | App Store credentials | One workflow file | |
| 260 | +| Cleanup | Manual | Automatic | |
| 261 | +| Native code changes | Required | Optional (JS only) | |
| 262 | +
|
| 263 | +## Best Practices |
| 264 | +
|
| 265 | +1. **Name channels clearly**: Use `pr-{number}` convention for easy identification |
| 266 | +2. **Auto-cleanup**: Always delete channels when PRs close |
| 267 | +3. **Limit access**: Only enable shake menu in debug/staging builds |
| 268 | +4. **Document the process**: Add testing instructions to your PR template |
| 269 | +5. **Handle failures gracefully**: Check that channel creation succeeds before posting comments |
| 270 | + |
| 271 | +## When Not to Use PR Previews |
| 272 | + |
| 273 | +PR previews are for JavaScript/CSS changes. If your PR includes: |
| 274 | + |
| 275 | +- New Capacitor plugins |
| 276 | +- iOS native code changes |
| 277 | +- Android native code changes |
| 278 | +- Dependency updates that affect native builds |
| 279 | + |
| 280 | +You'll need traditional TestFlight/Play Store distribution for those changes. |
| 281 | + |
| 282 | +## Combining with Channel Surfing |
| 283 | + |
| 284 | +PR previews work best when combined with [channel surfing](/blog/how-capgo-channel-switching-works/). Your app can have: |
| 285 | + |
| 286 | +- `production` - Stable releases for all users |
| 287 | +- `beta` - Early access for opt-in users |
| 288 | +- `pr-123` - Feature previews for specific PRs |
| 289 | + |
| 290 | +Testers with production builds can switch to any PR channel, test the feature, then switch back - all with the same installed app. |
| 291 | + |
| 292 | +## Resources |
| 293 | + |
| 294 | +- [Capgo Live Updates Documentation](/docs/getting-started/quickstart/) |
| 295 | +- [Channels Documentation](/docs/live-updates/channels/) |
| 296 | +- [Channel Surfing Guide](/blog/how-capgo-channel-switching-works/) |
| 297 | +- [CLI Commands Reference](/docs/cli/commands/) |
| 298 | +- [PR Preview Solutions Page](/solutions/pr-preview/) |
| 299 | + |
| 300 | +## Conclusion |
| 301 | + |
| 302 | +PR previews transform how your team reviews and tests mobile features. Instead of waiting for TestFlight processing and managing multiple beta builds, testers can switch to any PR channel in seconds using the app they already have installed. |
| 303 | + |
| 304 | +The setup is minimal - one GitHub Actions workflow file - and the benefits compound across your team. QA stays unblocked, product managers review faster, and developers get quicker feedback. |
| 305 | + |
| 306 | +Start by adding the workflow to one repository and see how it changes your review process. |
0 commit comments