Guide

How to update app strings without an App Store release

Updated

You can change the text in a shipped app without a new App Store or Google Play release by loading strings at runtime instead of compiling them into the binary. The app ships an SDK plus a signed fallback copy of its strings; when you publish a change, devices download the new string bundle, verify its signature and show the new copy, no build or review cycle involved.

This guide covers how over-the-air (OTA) strings work, what Apple’s rules say about them, why the content should be signed, and when you should not use this approach at all.

How over-the-air strings work

  • Strings live outside the binary. Each piece of copy is a key such as onboarding.title with a value per locale, in plain text or ICU MessageFormat for plurals and selects.
  • Publishing compiles a bundle. One bundle per locale and environment, signed with Ed25519 and uploaded to a CDN.
  • The SDK fetches and verifies. On launch and refresh, the SDK downloads the bundle, checks the signature against public keys built into your app, caches it on disk and serves strings from it.
  • Offline is handled. With no network, the SDK serves the last verified cached bundle. On a first launch it can use signed bundles you commit into the app with airstrings bundles pull. Only when neither exists does it show key names.

What changes over the air is data, not code. Your screens, logic and features are exactly what went through review; only the words inside them change.

Reading remote strings in your app

These snippets use the AirStrings SDKs. Replace the ids and public key with the values from your project.

iOS (SwiftUI)

import AirStrings

@main
struct MyApp: App {
    @State private var airStrings = AirStrings(configuration: .init(
        organizationId: "org_xxx",
        projectId: "proj_xxx",
        environmentId: "env_xxx",
        publicKeys: ["BASE64_ED25519_PUBLIC_KEY"]
    ))

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.airStrings, airStrings)
        }
    }
}

struct ContentView: View {
    @Environment(\.airStrings) var strings

    var body: some View {
        Text(strings["onboarding.welcome_title"])
    }
}

Android (Jetpack Compose)

val airStrings = AirStrings.create(context, AirStringsConfiguration(
    organizationId = "org_xxx",
    projectId = "proj_xxx",
    environmentId = "env_xxx",
    publicKeys = listOf("BASE64_ED25519_PUBLIC_KEY"),
))

CompositionLocalProvider(LocalAirStrings provides airStrings) {
    MyApp()
}

@Composable
fun WelcomeScreen() {
    val strings by LocalAirStrings.current.strings.collectAsStateWithLifecycle()
    Text(strings["onboarding.title"] ?: "")
}

React Native

import { useSyncExternalStore } from 'react'
import { Text } from 'react-native'
import { AirStrings } from '@airstrings/react-native'

const airstrings = new AirStrings({
  organizationId: 'org_xxx',
  projectId: 'proj_xxx',
  environmentId: 'env_xxx',
  publicKeys: ['BASE64_ED25519_PUBLIC_KEY'],
  locale: 'en-US',
  seed: [require('./airstrings/bundles/en-US.json')],
})

function Welcome() {
  useSyncExternalStore((cb) => airstrings.on('strings:updated', cb), () => airstrings.strings)
  return <Text>{airstrings.t('onboarding.title')}</Text>
}

Web

import { AirStrings } from '@airstrings/web'

const airstrings = new AirStrings({
  organizationId: 'org_xxx',
  projectId: 'proj_xxx',
  environmentId: 'env_xxx',
  publicKeys: ['BASE64_ED25519_PUBLIC_KEY'],
  locale: navigator.language,
})

airstrings.on('strings:updated', () => {
  document.getElementById('title')!.textContent = airstrings.t('onboarding.title')
})

Publishing a change

npm install -g @airstrings/cli
airstrings strings set onboarding.title en="Welcome back" --format text --push
airstrings publish

Devices pick up the new copy on their next refresh. No build, no store submission.

Is this allowed on the App Store?

Apple’s App Review Guideline 2.5.2 says apps may not “download, install, or execute code which introduces or changes features or functionality of the app”. The Apple Developer Program License Agreement adds a narrower clause for interpreted code (long cited as section 3.3.2): downloaded interpreted code must not change the app’s primary purpose, create a storefront for other code, or bypass the OS’s signing and sandbox protections. These rules are what code-push tools for React Native have to live within.

Strings are a different category. A button label, an onboarding headline or a translated error message is content, the same kind of data as articles, prices or configuration that apps routinely load from a server. Changing that content does not introduce or change features.

The nuance that matters:

  • Apple reviews the whole app and decides on its own terms. Nothing here is legal advice, and no tool can promise a review outcome.
  • The content you push still has to follow the guidelines. Do not use remote strings to reveal hidden features, change what the app does, or show content that would not pass review. Guideline 2.3.1(a) prohibits hidden, dormant or undocumented features.
  • Google Play has a parallel rule. Its Device and Network Abuse policy bars apps from downloading executable code (such as dex, JAR or .so files) from outside Google Play. String data is not executable code.

Why sign string content

Strings render as trusted UI, so whoever controls the bundle controls what your users read. HTTPS protects the connection, not the storage behind it. A compromised bucket, a misconfigured cache or a leaked publish credential could put a fake support number, a phishing link or a wrong price on every device at once.

AirStrings closes that gap with signatures checked on the device:

  • Every bundle is signed with Ed25519 before it leaves the platform. There is no unsigned delivery path.
  • The SDK verifies before rendering. A bad signature, an unknown key or an unsupported format means the bundle is rejected and the previous verified bundle stays in use.
  • The signature covers project, locale, revision, format version and timestamp, so a bundle cannot be swapped into another project or quietly rolled back to an older revision.
  • The disk cache is re-verified on every load. A tampered cache is discarded.

When not to use this

  • You need to change behavior, layout or add screens. That is code. Ship a release, or use a code-push tool for React Native JavaScript where that fits your risk tolerance.
  • Your copy rarely changes and you support one language. Compiled Localizable.strings, String Catalogs or strings.xml are simpler and need no SDK.
  • Every wording change must pass a per-release legal or compliance review. OTA updates skip the build where that review usually happens. If you adopt OTA anyway, gate production with a protected environment and promote reviewed changes from staging.
  • You want to ship images, video or other large assets. AirStrings delivers strings only.

Getting started

The AirStrings free plan includes signed over-the-air delivery: 1 project, 1,000 keys, 3 locales and 2 environments, no credit card. SDKs exist for iOS, Android, React Native and Web, and the documentation walks through setup end to end. Moving off CodePush? See CodePush for strings. Platform deep dives: React Native OTA translations and Remote localization in SwiftUI. Weighing other tools? See AirStrings vs Lokalise OTA, AirStrings vs Tolgee and Firebase Remote Config for translations.

FAQ

Can I change the text in my iOS app without submitting a new build?

Yes, if the text is loaded at runtime instead of compiled into the binary. An SDK fetches string bundles from a server or CDN, so publishing a new bundle changes the copy on devices without a new App Store submission. Text compiled into Localizable.strings or a String Catalog still needs a release.

Does Apple allow over-the-air string updates?

Apple’s guidelines restrict downloading code that introduces or changes features or functionality (guideline 2.5.2). Display text is content rather than executable code, the same category as articles or prices an app loads from a server. Apple decides every review on its own terms, so use remote strings to change wording, not to unlock hidden features or change what the app does.

What happens if the device is offline?

The SDK serves the last verified bundle from its on-disk cache. On a first launch with no network, it can use signed bundles you ship inside the app. Only if neither exists does it fall back to showing key names.

Why do string bundles need to be signed?

Strings render as trusted UI. Anyone who can alter a bundle in storage, a cache or a misconfigured CDN could show a fake support number or phishing message on every device. AirStrings signs each bundle with Ed25519 and the SDK rejects any bundle whose signature does not verify against the public keys built into your app.

Is over-the-air string delivery included in the free plan?

Yes. The AirStrings free plan includes signed over-the-air delivery with 1 project, 1,000 keys, 3 locales and 2 environments.