Configuration
Every hotcodepush.json key the Expo SDK reads, with its type and default, the strategies, the readiness gate and how a change reaches a build.
hotcodepush.json in the project root, next to package.json, configures the Expo SDK: the app id, the channel by name and the options below. Every option has a default that is the recommended production setup, so a file with appId alone is complete. The app config, app.json, carries only the plugin entry, never an option.
A debug build, the development build among them, embeds no bundle, so it takes no updates whatever isEnabledInDebugBuilds says. Test an update in a release build. The same file with every option written at its default:
{
"appId": "4f2b7c9e-8d31-4a6e-9b0f-2c5d7e8a1b34",
"channel": "production",
"checkStrategy": "auto",
"checkIntervalSeconds": 900,
"downloadStrategy": "auto",
"applyStrategy": "next_start",
"mandatoryApplyStrategy": "immediate",
"nextResumeMinimumBackgroundSeconds": 300,
"readySignal": "render",
"readyTimeoutSeconds": 10,
"isEnabledInDebugBuilds": true,
"publicKeys": [],
"extraFingerprintPaths": []
}Options
This table is every key with its type and default. Which apply strategy fits your app is the territory of Choose an apply strategy. Every duration is in seconds.
| Key | Type | Default | What it does |
|---|---|---|---|
appId | string | required | Names the app by its id, shown on the app's page in the console. The id is public. |
applyStrategy | next_start | immediate | next_resume | manual | next_start | Decides when a downloaded update is applied: at the next start, as soon as it is verified, on the first resume after enough time in the background, or only through applyUpdate(). The moment is decided at download time and stored with the release. |
channel | string | production | Names the channel this build follows. The build step resolves it to the channel's id, and HOTCODEPUSH_CHANNEL in the build's environment overrides it for one build, see binary. |
checkIntervalSeconds | number | 900 | Sets the least time between two automatic checks: a cold start always checks, a resume checks when the last check is older, and an app in the foreground checks when the interval elapses. At least 60. |
checkStrategy | auto | manual | auto | auto checks at start, on resume and while the app stays in the foreground, never more often than checkIntervalSeconds, and manual leaves every check to sync() or checkForUpdate(). |
downloadStrategy | auto | unmetered | manual | auto | Decides when a found update is downloaded. auto downloads on any connection. unmetered downloads only off metered and constrained connections (cellular, a personal hotspot and Low Data Mode on iOS, mobile data and a network marked metered on Android). manual never downloads on its own, the cycle stopping at available for downloadUpdate(). |
extraFingerprintPaths | string[] | [] | Lists the paths beyond the lockfile the native fingerprint hashes, relative to the project. It is the CLI's key, stripped from the resource file. |
isEnabledInDebugBuilds | boolean | true | Decides whether a debug build checks and applies updates. false makes every cycle in one answer skipped with build_debug. |
mandatoryApplyStrategy | immediate | manual | immediate | Decides when a mandatory update is applied, whatever applyStrategy says: at once, or by the app through applyUpdate(). |
nextResumeMinimumBackgroundSeconds | number | 300 | Sets, for next_resume, the least time in the background before the update applies on a resume. At least 0. |
publicKeys | string[] | [] | Lists the signing keys this build trusts, each rsa-v1_5-sha256: and the base64 of the key's SPKI DER. A non-empty list refuses an unsigned or wrongly signed manifest, see Sign your bundles. |
readySignal | render | manual | render | Names what ends the readiness gate: the app's first render, or an explicit notifyReady(). |
readyTimeoutSeconds | number | 10 | Sets the gate: a release that has not signalled readiness within this window is rolled back. At least 1, with no off switch. |
The reader on the device ignores a key it does not know. Keep checkIntervalSeconds at 900 or above: a lower value spends battery and data on empty checks, and a device that checks too often hits the per-device limit.
Strategies
Every stage has one hand-over value, manual, and a method that takes over: checkStrategy: manual with sync() or checkForUpdate(), downloadStrategy: manual with downloadUpdate(), applyStrategy: manual with applyUpdate(), and readySignal: manual with notifyReady(). Both sync() and downloadUpdate() take the strategies as options for one cycle. The apply moment is decided at download time and stored with the release, so a later start or resume reads the release, not the file.
A mandatory release follows mandatoryApplyStrategy instead of applyStrategy. A skipped mandatory release between the device's release and the chosen one makes the chosen one mandatory. The methods are on the API page. What a cycle does between the stages is How an update reaches a device.
Readiness
The readiness gate runs for readyTimeoutSeconds after a release starts. Under render the SDK observes the first render natively, so no call of yours is needed. On iOS that is the first frame after the root view renders, on Android the first frame that leaves a view inside a React root.
Under manual the gate waits for notifyReady(). The first render still tells the SDK the app is up, which a held reload waits for. A release that clears the gate becomes the fallback. One that does not, or whose run ends before it, is rolled back at the next start and reported, and that start fires updateRolledBack.
The timer runs in the foreground alone: the background stops it and a resume starts the full window again.
Changing the file
A change reaches the device through a native build, never through an over-the-air (OTA) update. The build step reads the file on every build, so a rebuild is enough, and a store build creates the binary with the new values.
The reader refuses checkIntervalSeconds below 60, readyTimeoutSeconds below 1, a negative duration or a duration written as a string, and never clamps. Every method then rejects with the reader's reason until the app is rebuilt, as in a build whose step never ran. Errors describes the error.
Last updated on