Version Targeting
This guide explains how to automatically deliver the latest compatible bundle to users based on their native app version, similar to Ionic AppFlow’s approach. This ensures simplified update management and faster rollouts while preventing compatibility issues.
Overview
Section titled “Overview”Capgo’s version targeting system allows you to:
- Automatically deliver compatible updates to users based on their native app version
- Prevent breaking changes from reaching incompatible app versions
- Manage multiple app versions simultaneously without complex logic
- Seamlessly roll out updates to specific user segments
Why Version Targeting Matters (Especially for AppFlow Users)
Section titled “Why Version Targeting Matters (Especially for AppFlow Users)”If you’re familiar with Ionic AppFlow, you know how critical it is to ensure users receive only compatible updates. AppFlow automatically matched live update bundles to native app versions, preventing incompatible JavaScript from being delivered to older native code.
Capgo provides the same safety guarantees, with additional features:
- More granular control over version matching
- Multiple strategies (channels, semver, native constraints)
- Better visibility into version distribution
- API and CLI control alongside dashboard management
This approach is particularly useful when:
- You have users on different major versions of your app (e.g., v1.x, v2.x, v3.x)
- You need to maintain backward compatibility while rolling out breaking changes
- You want to prevent newer bundles from breaking older native code
- You’re migrating users gradually from one version to another
- You’re migrating from AppFlow and want to maintain the same update safety
How It Works
Section titled “How It Works”Capgo uses a multi-layered approach to match users with compatible updates:
- Native Version Constraints: Prevent bundles from being delivered to incompatible native versions
- Channel-Based Routing: Route different app versions to different update channels
- Semantic Versioning Controls: Automatically block updates across major/minor/patch boundaries
- Device-Level Overrides: Target specific devices or user groups
Version Matching Flow
Section titled “Version Matching Flow”graph TD A[User Opens App] --> B{Check Device Override} B -->|Override Set| C[Use Override Channel] B -->|No Override| D{Check defaultChannel in App} D -->|Has defaultChannel| E[Use App's defaultChannel] D -->|No defaultChannel| F[Use Cloud Default Channel] C --> G{Check Version Constraints} E --> G F --> G G -->|Compatible| H[Deliver Update] G -->|Incompatible| I[Skip Update]Strategy 1: Channel-Based Version Routing
Section titled “Strategy 1: Channel-Based Version Routing”This is the recommended approach for managing breaking changes and major version updates. It’s similar to AppFlow’s delivery model.
Example Scenario
Section titled “Example Scenario”- App v1.x (100,000 users) →
productionchannel - App v2.x (50,000 users with breaking changes) →
v2channel - App v3.x (10,000 beta users) →
v3channel
Implementation
Section titled “Implementation”Step 1: Configure Channels for Each Major Version
Section titled “Step 1: Configure Channels for Each Major Version”// capacitor.config.ts for version 1.x buildsimport { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = { appId: 'com.example.app', appName: 'Example App', plugins: { CapacitorUpdater: { autoUpdate: true, defaultChannel: 'production', // or omit for default } }};
export default config;// capacitor.config.ts for version 2.x buildsconst config: CapacitorConfig = { appId: 'com.example.app', appName: 'Example App', plugins: { CapacitorUpdater: { autoUpdate: true, defaultChannel: 'v2', // Routes v2 users automatically } }};// capacitor.config.ts for version 3.x buildsconst config: CapacitorConfig = { appId: 'com.example.app', appName: 'Example App', plugins: { CapacitorUpdater: { autoUpdate: true, defaultChannel: 'v3', // Routes v3 users automatically } }};Step 2: Create Channels
Section titled “Step 2: Create Channels”# Create channels for each major versionnpx @capgo/cli channel create productionnpx @capgo/cli channel create v2npx @capgo/cli channel create v3
# Enable self-assignment so apps can switch channelsnpx @capgo/cli channel set production --self-assignnpx @capgo/cli channel set v2 --self-assignnpx @capgo/cli channel set v3 --self-assignStep 3: Upload Version-Specific Bundles
Section titled “Step 3: Upload Version-Specific Bundles”# For v1.x users (from v1-maintenance branch)git checkout v1-maintenancenpm run buildnpx @capgo/cli bundle upload --channel production
# For v2.x users (from v2-maintenance or main branch)git checkout mainnpm run buildnpx @capgo/cli bundle upload --channel v2
# For v3.x users (from beta/v3 branch)git checkout betanpm run buildnpx @capgo/cli bundle upload --channel v3Benefits
Section titled “Benefits”- Zero code changes - Channel routing happens automatically
- Clear separation - Each version has its own update pipeline
- Flexible targeting - Push updates to specific version groups
- Safe rollouts - Breaking changes never reach incompatible versions
Strategy 2: Semantic Versioning Controls
Section titled “Strategy 2: Semantic Versioning Controls”Use Capgo’s built-in semantic versioning controls to prevent updates across version boundaries.
Disable Auto-Update Across Major Versions
Section titled “Disable Auto-Update Across Major Versions”# Create a channel that blocks major version updatesnpx @capgo/cli channel create stable --disable-auto-update majorThis configuration means:
- Users on app version 1.2.3 will receive updates up to 1.9.9
- Users will NOT receive version 2.0.0 automatically
- Prevents breaking changes from reaching older native code
Granular Control Options
Section titled “Granular Control Options”# Block minor version updates (1.2.x won't get 1.3.0)npx @capgo/cli channel set stable --disable-auto-update minor
# Block patch updates (1.2.3 won't get 1.2.4)npx @capgo/cli channel set stable --disable-auto-update patch
# Allow all updatesnpx @capgo/cli channel set stable --disable-auto-update noneStrategy 3: Native Version Constraints
Section titled “Strategy 3: Native Version Constraints”Specify minimum native version requirements for bundles to prevent delivery to incompatible devices.
Using nativeVersion Delay Condition
Section titled “Using nativeVersion Delay Condition”When uploading a bundle, you can specify a minimum native version:
# This bundle requires native version 2.0.0 or highernpx @capgo/cli bundle upload \ --channel production \ --native-version "2.0.0"Use Cases
Section titled “Use Cases”-
New Native Plugin Required
Terminal window # Bundle needs Camera plugin added in v2.0.0npx @capgo/cli bundle upload --native-version "2.0.0" -
Breaking Native API Changes
Terminal window # Bundle uses new Capacitor 6 APIsnpx @capgo/cli bundle upload --native-version "3.0.0" -
Gradual Migration
Terminal window # Test bundle only on latest native versionnpx @capgo/cli bundle upload \--channel beta \--native-version "2.5.0"
Strategy 4: Auto-Downgrade Prevention
Section titled “Strategy 4: Auto-Downgrade Prevention”Prevent users from receiving bundles older than their current native version.
Enable in Channel Settings
Section titled “Enable in Channel Settings”In the Capgo dashboard:
- Go to Channels → Select your channel
- Enable “Disable auto downgrade under native”
- Save changes
Or via CLI:
npx @capgo/cli channel set production --disable-downgradeExample
Section titled “Example”- User’s device: Native version 1.2.5
- Channel bundle: Version 1.2.3
- Result: Update is blocked (would be a downgrade)
This is useful when:
- Users manually installed a newer version from the app store
- You need to ensure users always have the latest security patches
- You want to prevent regression bugs
Strategy 5: Device-Level Targeting
Section titled “Strategy 5: Device-Level Targeting”Override channel assignment for specific devices or user groups.
Force Specific Version for Testing
Section titled “Force Specific Version for Testing”import { CapacitorUpdater } from '@capgo/capacitor-updater'
// Force beta testers to use v3 channelasync function assignBetaTesters() { const deviceId = await CapacitorUpdater.getDeviceId()
// Check if user is beta tester if (isBetaTester(userId)) { await CapacitorUpdater.setChannel({ channel: 'v3' }) }}Dashboard Device Override
Section titled “Dashboard Device Override”In the Capgo dashboard:
- Go to Devices → Find device
- Click Set Channel or Set Version
- Override with specific channel or bundle version
- Device will receive updates from overridden source
Complete AppFlow-Style Workflow
Section titled “Complete AppFlow-Style Workflow”Here’s a complete example combining all strategies:
1. Initial Setup (App v1.0.0)
Section titled “1. Initial Setup (App v1.0.0)”# Create production channel with semver controlsnpx @capgo/cli channel create production \ --disable-auto-update major \ --disable-downgradeconst config: CapacitorConfig = { plugins: { CapacitorUpdater: { autoUpdate: true, defaultChannel: 'production', } }};2. Release Breaking Change (App v2.0.0)
Section titled “2. Release Breaking Change (App v2.0.0)”# Create v2 channel for new versionnpx @capgo/cli channel create v2 \ --disable-auto-update major \ --disable-downgrade \ --self-assign
# Create git branch for v1 maintenancegit checkout -b v1-maintenancegit push origin v1-maintenance// capacitor.config.ts for v2.0.0const config: CapacitorConfig = { plugins: { CapacitorUpdater: { autoUpdate: true, defaultChannel: 'v2', // New users get v2 channel } }};3. Push Updates to Both Versions
Section titled “3. Push Updates to Both Versions”# Update v1.x users (bug fix)git checkout v1-maintenance# Make changesnpx @capgo/cli bundle upload \ --channel production \ --native-version "1.0.0"
# Update v2.x users (new feature)git checkout main# Make changesnpx @capgo/cli bundle upload \ --channel v2 \ --native-version "2.0.0"4. Monitor Version Distribution
Section titled “4. Monitor Version Distribution”Use the Capgo dashboard to track:
- How many users are on v1 vs v2
- Bundle adoption rates per version
- Errors or crashes per version
5. Deprecate Old Version
Section titled “5. Deprecate Old Version”Once v1 usage drops below threshold:
# Stop uploading to production channel# Optional: Delete v1 maintenance branchgit branch -d v1-maintenance
# Move all remaining users to default# (They'll need to update via app store)Channel Precedence
Section titled “Channel Precedence”When multiple channel configurations exist, Capgo uses this precedence order:
- Device Override (Dashboard or API) - Highest priority
- Cloud Override via
setChannel()call - defaultChannel in capacitor.config.ts
- Default Channel (Cloud setting) - Lowest priority
Best Practices
Section titled “Best Practices”1. Always Set defaultChannel for Major Versions
Section titled “1. Always Set defaultChannel for Major Versions”// ✅ Good: Each major version has explicit channel// v1.x → production// v2.x → v2// v3.x → v3
// ❌ Bad: Relying on dynamic channel switching// All versions → production, switch manually2. Use Semantic Versioning
Section titled “2. Use Semantic Versioning”# ✅ Good1.0.0 → 1.0.1 → 1.1.0 → 2.0.0
# ❌ Bad1.0 → 1.1 → 2 → 2.53. Maintain Separate Branches
Section titled “3. Maintain Separate Branches”# ✅ Good: Separate branches per major versionmain (v3.x)v2-maintenance (v2.x)v1-maintenance (v1.x)
# ❌ Bad: Single branch for all versions4. Test Before Rollout
Section titled “4. Test Before Rollout”# Test on beta channel firstnpx @capgo/cli bundle upload --channel beta
# Monitor for issues, then promote to productionnpx @capgo/cli bundle upload --channel production5. Monitor Version Distribution
Section titled “5. Monitor Version Distribution”Regularly check your dashboard:
- Are users upgrading to newer native versions?
- Are old versions still getting high traffic?
- Should you deprecate old channels?
Comparison with Ionic AppFlow
Section titled “Comparison with Ionic AppFlow”For teams migrating from Ionic AppFlow, here’s how Capgo’s version targeting compares:
| Feature | Ionic AppFlow | Capgo |
|---|---|---|
| Version-based routing | Automatic based on native version | Automatic via defaultChannel + multiple strategies |
| Semantic versioning | Basic support | Advanced with --disable-auto-update (major/minor/patch) |
| Native version constraints | Manual configuration in AppFlow dashboard | Built-in --native-version flag in CLI |
| Channel management | Web UI + CLI | Web UI + CLI + API |
| Device overrides | Limited device-level control | Full control via Dashboard/API |
| Auto-downgrade prevention | Yes | Yes via --disable-downgrade |
| Multi-version maintenance | Manual branch/channel management | Automated with channel precedence |
| Self-hosting | No | Yes (full control) |
| Version analytics | Basic | Detailed per-version metrics |
Troubleshooting
Section titled “Troubleshooting”Users Not Receiving Updates
Section titled “Users Not Receiving Updates”Check the following:
-
Channel Assignment: Verify device is on correct channel
const channel = await CapacitorUpdater.getChannel()console.log('Current channel:', channel) -
Version Constraints: Check if bundle has native version requirements
- Dashboard → Bundles → Check “Native Version” column
-
Semver Settings: Verify channel’s
disable-auto-updatesettingTerminal window npx @capgo/cli channel list -
Device Override: Check if device has manual override
- Dashboard → Devices → Search for device → Check channel/version
Bundle Delivered to Wrong Version
Section titled “Bundle Delivered to Wrong Version”- Review defaultChannel: Ensure correct channel in
capacitor.config.ts - Check Bundle Upload: Verify bundle was uploaded to intended channel
- Inspect Native Version: Confirm
--native-versionflag was used correctly
Breaking Changes Affecting Old Versions
Section titled “Breaking Changes Affecting Old Versions”- Immediate Fix: Override affected devices to safe bundle
- Dashboard → Devices → Bulk select → Set Version
- Long-term Fix: Create versioned channels and maintain separate branches
- Prevention: Always test updates on representative devices before rollout
Migration from Ionic AppFlow
Section titled “Migration from Ionic AppFlow”If you’re migrating from Ionic AppFlow, version targeting works very similarly in Capgo, with improved flexibility:
Concept Mapping
Section titled “Concept Mapping”| AppFlow Concept | Capgo Equivalent | Notes |
|---|---|---|
| Deploy Channel | Capgo Channel | Same concept, more powerful |
| Native Version Lock | --native-version flag | More granular control |
| Channel Priority | Channel precedence (override → cloud → default) | More transparent precedence |
| Deployment Target | Channel + semver controls | Multiple strategies available |
| Production Channel | production channel (or any name) | Flexible naming |
| Git-based deployment | CLI bundle upload from branch | Same workflow |
| Automatic version matching | defaultChannel + version constraints | Enhanced with multiple strategies |
Key Differences for AppFlow Users
Section titled “Key Differences for AppFlow Users”- More Control: Capgo gives you multiple strategies (channels, semver, native version) that can be combined
- Better Visibility: Dashboard shows version distribution and compatibility issues
- API Access: Full programmatic control over version targeting
- Self-Hosting: Option to run your own update server with same version logic
Migration Steps
Section titled “Migration Steps”- Map your AppFlow channels to Capgo channels (usually 1:1)
- Set
defaultChannelincapacitor.config.tsfor each major version - Configure semver rules if you want automatic blocking at version boundaries
- Upload version-specific bundles using
--native-versionflag - Monitor version distribution in Capgo dashboard
Advanced Patterns
Section titled “Advanced Patterns”Gradual Rollout by Version
Section titled “Gradual Rollout by Version”// Gradually migrate v1 users to v2async function migrateUsers() { const deviceId = await CapacitorUpdater.getDeviceId() const rolloutPercentage = 10 // Start with 10%
// Hash device ID to get deterministic percentage const hash = hashCode(deviceId) % 100
if (hash < rolloutPercentage) { // User is in rollout group - migrate to v2 await CapacitorUpdater.setChannel({ channel: 'v2' }) }}Feature Flags by Version
Section titled “Feature Flags by Version”// Enable features based on native versionasync function checkFeatureAvailability() { const info = await CapacitorUpdater.getDeviceId() const nativeVersion = info.nativeVersion
if (compareVersions(nativeVersion, '2.0.0') >= 0) { // Enable features requiring v2.0.0+ enableNewCameraFeature() }}A/B Testing Across Versions
Section titled “A/B Testing Across Versions”// Run A/B tests within same native versionasync function assignABTest() { const nativeVersion = await getNativeVersion()
if (nativeVersion.startsWith('2.')) { // Only A/B test on v2 users const variant = Math.random() < 0.5 ? 'v2-test-a' : 'v2-test-b' await CapacitorUpdater.setChannel({ channel: variant }) }}Summary
Section titled “Summary”Capgo provides multiple strategies for version-specific update delivery:
- Channel-Based Routing: Automatic version separation via
defaultChannel - Semantic Versioning: Prevent updates across major/minor/patch boundaries
- Native Version Constraints: Require minimum native version for bundles
- Auto-Downgrade Prevention: Never deliver older bundles to newer native versions
- Device Overrides: Manual control for testing and targeting
By combining these strategies, you can achieve AppFlow-style automatic update delivery with even more flexibility and control. Choose the approach that best fits your app’s versioning and deployment workflow.
For more details on specific features:
- Breaking Changes Guide - Detailed channel versioning strategy
- Channel Management - Complete channel configuration reference
- Update Behavior - Native version delays and conditions