Advanced features for customizing update behavior including extra parameters for server communication, experimental request overrides, and developer utilities for testing.
Extra parameters are sent as structured headers to update servers and can be used for custom update selection logic or A/B testing scenarios.
/**
* Retrieves the current extra params.
* Cannot be used in Expo Go or development mode.
* @returns Promise that resolves to current extra parameters as key-value pairs
* @throws Error when expo-updates is not enabled or in development mode
*/
function getExtraParamsAsync(): Promise<Record<string, string>>;
/**
* Sets an extra param if value is non-null, otherwise unsets the param.
* Extra params are sent as an Expo Structured Field Value Dictionary
* in the Expo-Extra-Params header of update requests. A compliant update server
* may use these params when selecting an update to serve.
* Cannot be used in Expo Go or development mode.
* @param key Parameter key to set
* @param value Parameter value to set, or null/undefined to unset
* @returns Promise that resolves when parameter is set/unset
* @throws Error when expo-updates is not enabled or in development mode
*/
function setExtraParamAsync(
key: string,
value: string | null | undefined
): Promise<void>;Usage Examples:
import * as Updates from "expo-updates";
// Get current extra parameters
const currentParams = await Updates.getExtraParamsAsync();
console.log("Current extra params:", currentParams);
// Set extra parameters for custom server logic
await Updates.setExtraParamAsync("environment", "staging");
await Updates.setExtraParamAsync("feature_flags", "dark_mode,new_ui");
await Updates.setExtraParamAsync("user_tier", "premium");
// Remove a parameter
await Updates.setExtraParamAsync("old_param", null);
// A/B testing with extra params
async function setupABTestParams(userId: string) {
const abGroup = userId.charCodeAt(0) % 2 === 0 ? "A" : "B";
await Updates.setExtraParamAsync("ab_test_group", abGroup);
await Updates.setExtraParamAsync("user_id", userId);
}
// Conditional updates based on device characteristics
async function setDeviceParams() {
const { width, height } = Dimensions.get('window');
const isTablet = width > 768;
await Updates.setExtraParamAsync("device_type", isTablet ? "tablet" : "phone");
await Updates.setExtraParamAsync("screen_size", `${width}x${height}`);
await Updates.setExtraParamAsync("platform", Platform.OS);
}
// Complete parameter management
class UpdateParamsManager {
async getAllParams(): Promise<Record<string, string>> {
return await Updates.getExtraParamsAsync();
}
async setParams(params: Record<string, string | null>): Promise<void> {
for (const [key, value] of Object.entries(params)) {
await Updates.setExtraParamAsync(key, value);
}
}
async clearAllParams(): Promise<void> {
const currentParams = await Updates.getExtraParamsAsync();
for (const key of Object.keys(currentParams)) {
await Updates.setExtraParamAsync(key, null);
}
}
async updateUserContext(user: User): Promise<void> {
await this.setParams({
user_id: user.id,
subscription_type: user.subscriptionType,
locale: user.locale,
timezone: user.timezone
});
}
}
// Usage with update checking
async function checkForUpdatesWithParams() {
// Set context before checking
await Updates.setExtraParamAsync("check_timestamp", Date.now().toString());
await Updates.setExtraParamAsync("app_version", "1.2.3");
// Now check for updates - server will receive these params
const result = await Updates.checkForUpdateAsync();
return result;
}Warning: These methods are experimental and should be used with caution as they may cause unexpected behavior. They require disableAntiBrickingMeasures to be set to true in app.json.
/**
* Overrides updates URL and request headers in runtime from build time.
* This method allows you to load specific updates from a URL that you provide.
* Use this method at your own risk, as it may cause unexpected behavior.
* Requires disableAntiBrickingMeasures to be set to true in the app.json file.
* @experimental
* @param configOverride Configuration override with URL and headers, or null to reset
*/
function setUpdateURLAndRequestHeadersOverride(
configOverride: {
updateUrl: string;
requestHeaders: Record<string, string>
} | null
): void;
/**
* Overrides updates request headers in runtime from build time.
* This method allows you to load specific updates with custom request headers.
* Use this method at your own risk, as it may cause unexpected behavior.
* @experimental
* @param requestHeaders Custom request headers, or null to reset
*/
function setUpdateRequestHeadersOverride(
requestHeaders: Record<string, string> | null
): void;Usage Examples:
import * as Updates from "expo-updates";
// Override both URL and headers for custom update server
Updates.setUpdateURLAndRequestHeadersOverride({
updateUrl: "https://custom-update-server.com/api/updates",
requestHeaders: {
"Authorization": "Bearer custom-token",
"X-Custom-Header": "custom-value",
"X-Environment": "production"
}
});
// Override only headers while keeping existing URL
Updates.setUpdateRequestHeadersOverride({
"Authorization": "Bearer new-token",
"X-User-ID": "12345",
"X-Feature-Flags": "experimental-ui"
});
// Reset overrides to use original configuration
Updates.setUpdateURLAndRequestHeadersOverride(null);
Updates.setUpdateRequestHeadersOverride(null);
// Advanced usage - dynamic configuration based on environment
function configureUpdateServer(environment: string, authToken: string) {
const serverUrls = {
development: "https://dev-updates.company.com/api",
staging: "https://staging-updates.company.com/api",
production: "https://prod-updates.company.com/api"
};
const baseHeaders = {
"Authorization": `Bearer ${authToken}`,
"X-Environment": environment,
"X-Client-Version": "1.0.0"
};
if (environment !== "production") {
// Use custom server for non-production
Updates.setUpdateURLAndRequestHeadersOverride({
updateUrl: serverUrls[environment],
requestHeaders: {
...baseHeaders,
"X-Debug": "true"
}
});
} else {
// Only override headers for production
Updates.setUpdateRequestHeadersOverride(baseHeaders);
}
}
// Enterprise usage - custom authentication
class EnterpriseUpdateConfig {
private authToken: string | null = null;
async authenticate(credentials: LoginCredentials): Promise<void> {
const response = await fetch("/api/auth", {
method: "POST",
body: JSON.stringify(credentials)
});
const { token } = await response.json();
this.authToken = token;
// Set authenticated headers
Updates.setUpdateRequestHeadersOverride({
"Authorization": `Bearer ${token}`,
"X-Enterprise-Client": "true"
});
}
async configureCustomServer(serverUrl: string): Promise<void> {
if (!this.authToken) {
throw new Error("Must authenticate before configuring custom server");
}
Updates.setUpdateURLAndRequestHeadersOverride({
updateUrl: serverUrl,
requestHeaders: {
"Authorization": `Bearer ${this.authToken}`,
"X-Enterprise-Client": "true",
"X-Custom-Server": "true"
}
});
}
resetToDefault(): void {
Updates.setUpdateURLAndRequestHeadersOverride(null);
this.authToken = null;
}
}Hidden methods primarily used for testing and debugging the reload screen functionality.
/**
* Shows the reload screen with customizable appearance. This is primarily useful for testing
* how the reload screen will appear to users during app reloads and is only available in
* debug builds of the app. The reload screen can be hidden by calling hideReloadScreen().
* @hidden exposed for testing
* @param options Configuration options for customizing the reload screen appearance
* @returns Promise that resolves when the reload screen is shown
*/
function showReloadScreen(options?: {
reloadScreenOptions?: ReloadScreenOptions
}): Promise<void>;
/**
* Hides the reload screen.
* @hidden exposed for testing
* @returns Promise that resolves when the reload screen is hidden
*/
function hideReloadScreen(): Promise<void>;Usage Examples:
import * as Updates from "expo-updates";
// Test reload screen appearance (debug builds only)
async function testReloadScreen() {
// Show default reload screen
await Updates.showReloadScreen();
// Wait 3 seconds
await new Promise(resolve => setTimeout(resolve, 3000));
// Hide it
await Updates.hideReloadScreen();
}
// Test custom styling
async function testCustomReloadScreen() {
await Updates.showReloadScreen({
reloadScreenOptions: {
backgroundColor: '#1a1a1a',
spinner: {
color: '#ffffff',
size: 'large'
}
}
});
setTimeout(async () => {
await Updates.hideReloadScreen();
}, 5000);
}
// Test with custom image
async function testImageReloadScreen() {
await Updates.showReloadScreen({
reloadScreenOptions: {
backgroundColor: '#ffffff',
image: require('./assets/loading.png'),
imageResizeMode: 'contain',
fade: true
}
});
// Auto hide after delay
setTimeout(() => Updates.hideReloadScreen(), 3000);
}
// Testing suite for reload screen
class ReloadScreenTester {
async testAllVariations(): Promise<void> {
const variations = [
// Default
{},
// Dark theme
{
reloadScreenOptions: {
backgroundColor: '#000000',
spinner: { color: '#ffffff' }
}
},
// Light theme with image
{
reloadScreenOptions: {
backgroundColor: '#ffffff',
image: 'https://example.com/logo.png',
spinner: { enabled: false }
}
},
// Full screen image
{
reloadScreenOptions: {
image: require('./assets/splash.png'),
imageFullScreen: true,
fade: true
}
}
];
for (const [index, options] of variations.entries()) {
console.log(`Testing variation ${index + 1}`);
await Updates.showReloadScreen(options);
await new Promise(resolve => setTimeout(resolve, 2000));
await Updates.hideReloadScreen();
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}// Never hardcode sensitive tokens
// ❌ Bad
Updates.setExtraParamAsync("api_key", "hardcoded-secret-key");
// ✅ Good - use secure storage or environment variables
const apiKey = await SecureStore.getItemAsync("api_key");
if (apiKey) {
Updates.setExtraParamAsync("api_key", apiKey);
}
// Be careful with experimental overrides
if (__DEV__ || isTestBuild) {
// Only use overrides in development/testing
Updates.setUpdateURLAndRequestHeadersOverride({
updateUrl: DEV_UPDATE_SERVER,
requestHeaders: { "X-Debug": "true" }
});
}// Create a centralized parameter manager
class UpdateConfigManager {
private readonly MAX_PARAM_LENGTH = 1000;
async safeSetParam(key: string, value: string | null): Promise<boolean> {
try {
if (value && value.length > this.MAX_PARAM_LENGTH) {
console.warn(`Parameter ${key} too long, truncating`);
value = value.substring(0, this.MAX_PARAM_LENGTH);
}
await Updates.setExtraParamAsync(key, value);
return true;
} catch (error) {
console.error(`Failed to set parameter ${key}:`, error);
return false;
}
}
async syncUserPreferences(preferences: UserPreferences): Promise<void> {
const params = {
theme: preferences.theme,
language: preferences.language,
notifications: preferences.notificationsEnabled ? "true" : "false"
};
for (const [key, value] of Object.entries(params)) {
await this.safeSetParam(`pref_${key}`, value);
}
}
}