Skip to Content

Expo SDK

@appspacer/expo — Asset OTA updates and crash reporting for Expo managed workflow apps.

Update model: Because Expo managed workflow does not allow custom native modules or JS bundle hot-swapping, AppSpacer for Expo delivers asset OTA updates — images, JSON configs, string files, and any other data your app fetches at runtime. Your app reads from the OTA cache first, falling back to bundled assets when nothing is cached yet.


Requirements

Minimum
Expo SDK>= 50
React Native>= 0.73
expo-file-system>= 17
expo-crypto>= 13
expo-network>= 6

Installation

npx expo install @appspacer/expo expo-file-system expo-crypto expo-network expo-application

Initialization

Call AppSpacer.init() once in your root component or app/_layout.tsx before anything else:

import { AppSpacer } from '@appspacer/expo'; export default function RootLayout() { useEffect(() => { AppSpacer.init({ apiKey: 'your_api_key' }); }, []); return <Slot />; }

Configuration Options

OptionTypeDefaultDescription
apiKeystringRequiredYour AppSpacer API key
appVersionstringApplication.nativeApplicationVersionOverride the app version
updateStrategy'onInit' | 'background' | 'manual''onInit'When to check for updates
debugLoggingbooleanfalsePrint verbose logs
crashReportingEnabledbooleantrueCapture JS crashes automatically
maxBreadcrumbsnumber50Max breadcrumbs stored per session

Serving OTA Assets

After init, read cached assets anywhere in your app. Always provide a fallback to the bundled asset:

import { AppSpacer } from '@appspacer/expo'; import { Image } from 'expo-image'; export function HeroBanner() { const [src, setSrc] = useState(require('./assets/hero.png')); useEffect(() => { AppSpacer.readAssetBytes('images/hero.png').then(bytes => { if (bytes) { const base64 = Buffer.from(bytes).toString('base64'); setSrc({ uri: `data:image/png;base64,${base64}` }); } }); }, []); return <Image source={src} style={{ width: '100%', height: 200 }} />; }

Reading JSON config

const flags = await AppSpacer.readAssetJson('config/feature-flags.json'); const showNewCheckout = flags?.new_checkout ?? false;

Manual Update Check

const result = await AppSpacer.checkForUpdates(); if (result.status === OtaUpdateStatus.updated) { console.log(`Updated ${result.updatedCount} asset(s). Bundle: ${result.newBundleId}`); }

API Reference

MethodReturnsDescription
AppSpacer.init({ apiKey })Promise<void>Initialize — call once at app startup
AppSpacer.checkForUpdates()Promise<OtaUpdateResult>Check and download new assets
AppSpacer.readAssetBytes(key)Promise<Uint8Array | null>Read cached asset as raw bytes
AppSpacer.readAssetString(key)Promise<string | null>Read cached asset as UTF-8 text
AppSpacer.readAssetJson(key)Promise<Record | null>Read and parse cached JSON asset
AppSpacer.clearAssetCache()Promise<void>Delete all cached assets
AppSpacer.cacheSize()Promise<number>Total cached bytes
AppSpacer.activeBundleInfo()Promise<Record | null>Metadata about the active bundle
AppSpacer.reportError(ex)Promise<void>Report a handled error
AppSpacer.addBreadcrumb(msg)voidAdd a breadcrumb for crash context
AppSpacer.sessionIdstringCurrent session ID

Backend Integration

GET /api/sdk/expo/manifest Headers: x-api-key: <your_api_key> Query: app_version, current_bundle_id, install_id POST /api/sdk/expo/report-status Body: { release_id, device_id, app_version, status, error_message }

Send install_id (a stable per-device ID) on every manifest call — it is required for staged-rollout targeting and monthly active-user metering. Report installed/failed back to report-status so egress is billed once per device and failed installs can trigger auto-rollback.

Returns 204 No Content when up to date, or a manifest JSON when an update is available.

Upload your asset bundles from the AppSpacer dashboard or CLI and set platform: expo on the release.

Last updated on