Skip to Content
Platform GuidesMulti-Version OTA Updates

Multi-Version OTA Updates

Learn how to maintain and deliver OTA updates to multiple app version series running in production simultaneously.

The Challenge

Real-world apps often have multiple versions in production:

Production Users: ├── v1.1.0 (Legacy) - 30% of users ├── v1.2.0 (Stable) - 50% of users └── v2.0.0 (Latest) - 20% of users

When a critical bug is discovered, you may need to fix it across all versions without forcing users to upgrade their native app.

How Norrix Handles This

Build Selection by Version

When you run norrix update with a specific --version, the CLI:

  1. Filters builds to only those matching the specified version
  2. Selects the newest build within that version (by timestamp)
  3. Compares fingerprints against that specific build
  4. Publishes the update targeted at that version series

This allows OTA updates to flow to the correct devices:

┌─────────────────────────────────────────────────────────────────┐ │ Norrix Update Server │ ├─────────────────────────────────────────────────────────────────┤ │ Update v1.1.0-hotfix → Fingerprint: abc123... │ │ Update v2.0.0-hotfix → Fingerprint: xyz789... │ └─────────────────────────────────────────────────────────────────┘ ┌────────────────────┴────────────────────┐ │ │ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ Device: v1.1.0 │ │ Device: v2.0.0 │ │ FP: abc123... │ │ FP: xyz789... │ │ │ │ │ │ ✓ Gets v1.1.0 │ │ ✓ Gets v2.0.0 │ │ hotfix │ │ hotfix │ └─────────────────┘ └─────────────────┘

Workflow Example

Scenario: Critical Bug in Shared Code

A bug is found in src/services/api.ts that affects all versions.

Step 1: Fix the Bug

# Fix in your codebase git checkout main # ... make the fix ... git commit -m "fix: critical API bug"

Step 2: Push OTA to v1.1.0 Users

# Target v1.1.0 specifically norrix update ios --version 1.1.0 --app-id com.example.myapp # CLI output: # Fingerprint comparison # ---------------------- # From: build build-xxxxx (v1.1.0) # Hash: abc123... # To: local project # Hash: abc123... # # ✓ Fingerprints match - OTA compatible

Step 3: Push OTA to v2.0.0 Users

# Target v2.0.0 specifically norrix update ios --version 2.0.0 --app-id com.example.myapp # CLI output: # Fingerprint comparison # ---------------------- # From: build build-yyyyy (v2.0.0) # Hash: xyz789... # To: local project # Hash: xyz789... # # ✓ Fingerprints match - OTA compatible

Version Series Strategy

Use semantic versioning to organize your releases:

MAJOR.MINOR.PATCH │ │ └── OTA updates (bug fixes, small changes) │ └──────── Feature releases (may be OTA if no native changes) └────────────── Breaking changes (always require store update)

Example Release Timeline

v1.0.0 (Store) ─┬─ v1.0.1 (OTA) ─ v1.0.2 (OTA) └─ v1.1.0 (Store) ─┬─ v1.1.1 (OTA) ─ v1.1.2 (OTA) └─ v2.0.0 (Store) ─ v2.0.1 (OTA)

Each version series maintains its own fingerprint, enabling targeted OTA updates.

CI/CD Integration

GitHub Actions Example

name: Hotfix All Versions on: workflow_dispatch: inputs: versions: description: 'Comma-separated versions to update' required: true default: '1.1.0,2.0.0' jobs: hotfix: runs-on: ubuntu-latest strategy: matrix: version: ${{ fromJson(format('["{0}"]', replace(github.event.inputs.versions, ',', '","'))) }} steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Push OTA for ${{ matrix.version }} run: | npx norrix update ios \ --version-name ${{ matrix.version }} \ --app-id ${{ secrets.APP_ID }} \ --non-interactive env: NORRIX_API_KEY: ${{ secrets.NORRIX_API_KEY }}

Best Practices

1. Track Active Versions

Maintain a list of versions that are still in active use:

// versions.json { "active": ["1.1.0", "2.0.0"], "deprecated": ["1.0.0"], "eol": ["0.9.0"] }

2. Test Across Versions

Before pushing hotfixes, verify compatibility:

# Check fingerprint compatibility for each version for version in 1.1.0 2.0.0; do echo "Checking $version..." norrix fingerprint compare --from "build-id-for-$version" done

3. Communicate with Users

If a version can’t receive an OTA (fingerprint mismatch), communicate clearly:

initNorrix({ updateUrl: 'https://norrix.net', statusCallback(status, data) { if (data.requiresStoreUpdate) { // Show user-friendly message showUpdateDialog({ title: 'Update Required', message: 'A new version is available in the App Store with important security fixes.', action: 'Open App Store' }); } } });

4. Set Deprecation Timelines

Plan when to stop supporting older versions:

v1.0.0 - EOL: 2025-06-01 (no more OTA updates) v1.1.0 - Deprecated: 2025-09-01 (security fixes only) v2.0.0 - Active (full OTA support)

Troubleshooting

Wrong Build Selected

If the CLI selects an unexpected build:

# Use verbose mode to see build selection norrix update ios --version 1.1.0 --verbose

The output will show which builds are being considered and which is selected.

No Matching Builds

If no builds match your version:

# List all builds to verify versions norrix build-status <build-id>

Ensure the version in your Info.plist or app.gradle matches exactly.

Fingerprint Mismatch

If fingerprints don’t match within a version series, you may have:

  • Updated a native plugin in that version
  • Changed App_Resources contents
  • Modified native source files

In this case, you’ll need a new store build for that version series.