OTA Compatibility
This page explains the rules for determining when OTA updates are safe and when a new store build is required.
The Core Rule
OTA updates can safely replace JavaScript code, but not native code.
If the native components of your app change, devices need a new binary from the app store.
What Can Be Updated via OTA
✅ JavaScript/TypeScript
All your app code:
// src/services/api.ts
export async function fetchData() {
// This can be updated via OTA
return await fetch('/api/data');
}✅ CSS/SCSS Styles
All styling:
/* app.css */
.button {
/* Can be updated via OTA */
background-color: blue;
}✅ Assets
Images, fonts, and other assets in your source:
src/
├── assets/
│ ├── images/
│ ├── fonts/
│ └── icons/✅ Non-Native npm Packages
Pure JavaScript packages:
{
"dependencies": {
"lodash": "4.17.21", // ✅ Safe to update
"axios": "1.4.0", // ✅ Safe to update
"date-fns": "2.30.0" // ✅ Safe to update
}
}✅ Node Modules (JavaScript Parts)
JavaScript code from any package, even native plugins:
node_modules/
└── nativescript-camera/
├── index.js // ✅ JS can update
└── platforms/
└── ios/ // ❌ Native cannotWhat Requires a Store Build
❌ NativeScript Platform Versions
{
"devDependencies": {
"@nativescript/ios": "8.6.0", // Change = store build
"@nativescript/android": "8.6.0" // Change = store build
}
}❌ Native Plugin Versions
Any plugin with native code:
{
"dependencies": {
"nativescript-camera": "4.5.0", // Change = store build
"@nativescript/imagepicker": "1.0.0", // Change = store build
"nativescript-geolocation": "8.0.0" // Change = store build
}
}❌ App_Resources Contents
App_Resources/
├── iOS/
│ ├── Info.plist // Change = store build
│ ├── Assets.xcassets/ // Change = store build
│ └── LaunchScreen.storyboard // Change = store build
└── Android/
├── AndroidManifest.xml // Change = store build
└── res/ // Change = store build❌ Custom Native Code
native-src/
├── ios/
│ └── MyBridge.swift // Change = store build
└── android/
└── MyBridge.java // Change = store buildCompatibility Checking Flow
1. Build Time
When you create a store build:
Project → Compute Fingerprint → Store with Build Record2. Update Time
When you publish an OTA update:
Project → Compute Fingerprint → Store with Update Record3. Check Time
When a device checks for updates:
Device Fingerprint ─┐
├─→ Compare ─┐
Update Fingerprint ─┘ │
▼
┌─────────────────────────────────┐
│ Match? │
│ │
│ Yes → Download OTA │
│ No → requiresStoreUpdate: true │
└─────────────────────────────────┘Client Behavior
Default Behavior
When requiresStoreUpdate is true:
- SDK receives the response
- Update is ignored
- App continues with current version
- Status:
UPDATE_IGNORED
Override Behavior
With allowStoreUpdateOverride: true:
initNorrix({
updateUrl: 'https://norrix.net',
allowStoreUpdateOverride: true, // ⚠️ Use with caution
});- SDK receives the response
- Update is downloaded despite incompatibility
- Update is applied
- Risk: App may crash if native code is missing
Warning: Only use this if you’re certain the native changes are backward-compatible.
Example Scenarios
Scenario 1: Bug Fix
Before: Version 1.0.0, Build 42
Change: Fix API endpoint in src/api.ts
After: Version 1.0.1, Build 43
→ ✅ OTA Compatible (JS only)Scenario 2: Plugin Update
Before: [email protected]
Change: npm update [email protected]
After: [email protected]
→ ❌ Store Build Required (native code changed)Scenario 3: New Icon
Before: AppIcon.png (original)
Change: Update App_Resources/iOS/Assets.xcassets
After: AppIcon.png (new design)
→ ❌ Store Build Required (App_Resources changed)Scenario 4: Mixed Changes
Changes:
- Fix bug in src/utils.ts ✅ JS
- Update @nativescript/core ❌ Native
→ ❌ Store Build Required (any native change triggers)App Store Policy Compliance
Beyond technical compatibility, OTA updates must comply with Apple’s and Google’s store policies. Both platforms restrict how apps can update themselves outside their official distribution channels.
Apple: Guideline 2.5.2
“Apps should be self-contained in their bundles, and may not read or write data outside the designated container area, nor may they download, install, or execute code which introduces or changes features or functionality of the app.”
Apple doesn’t provide an explicit exemption for JavaScript interpreters or virtual machines. In practice, Apple has generally tolerated JS-only OTA updates (as used by Expo, CodePush, and similar services) when they’re limited to bug fixes and minor improvements that don’t change the app’s core features or functionality.
Google Play: Device and Network Abuse
“An app distributed via Google Play may not modify, replace, or update itself using any method other than Google Play’s update mechanism. Likewise, an app may not download executable code (such as dex, JAR, .so files) from a source other than Google Play. This restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser).”
Google’s exemption is specifically for code that provides indirect access to Android APIs, such as JavaScript in a webview. NativeScript provides direct access to all native APIs from JavaScript, which means NativeScript apps may not be covered by this exemption.
What This Means for NativeScript OTA Updates
OTA updates are widely used across the mobile ecosystem (Expo, CodePush, Capacitor, and others), but NativeScript’s direct native API access puts it in a different position than frameworks that use a bridge or webview for native interaction.
To stay within policy:
- Do use OTA for bug fixes, copy changes, and minor UI adjustments.
- Do use OTA for patches that keep the app within its approved scope.
- Don’t use OTA to introduce new features or change the app’s core functionality.
- Don’t use OTA to bypass app store review for changes that should be reviewed.
When in doubt, submit a new store build.
Best Practices
Plan Updates Carefully
- Patch releases (1.0.1): Usually OTA-compatible
- Minor releases (1.1.0): Check for dependency changes
- Major releases (2.0.0): Often require store builds
Check Before Publishing
Always verify compatibility:
norrix fingerprint compare --from <last-build-id>Targeting Specific Version Series
When maintaining multiple production versions, use --version to target specific release series:
# Hotfix for 1.1.x users (compares against latest 1.1.0 build)
norrix update ios --version 1.1.0
# Hotfix for 2.0.x users (compares against latest 2.0.0 build)
norrix update ios --version 2.0.0This is essential when:
- You have users on older versions who can’t update due to device constraints
- A critical bug affects multiple release branches
- You need to maintain LTS (Long Term Support) versions
Separate Concerns
- Keep native changes in dedicated releases
- Batch OTA-compatible changes together
- Communicate store updates to users
Version Strategy
1.0.0 (Store) → 1.0.1 (OTA) → 1.0.2 (OTA) → 1.1.0 (Store)