Build a mobile app with Expo
Generate app.json, the iOS privacy manifest and the Play Data safety form from main.yaml, keep secrets in Keychain, attest the device and send push through notify.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
You will point an Expo app at MainSDK, let mainsdk sync write the native files, and add the device-side rules. It takes about 30 minutes.
You need
- A MainSDK app and an Expo app. Push and attestation take a real device.
1. Declare the mobile target
targets:
mobile:
framework: expo
platforms: [ios, android]
bundleId: dev.acme.orders
signing:
teamId: ABCDE12345
androidSha256: ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
notify:
channels: [push]Run mainsdk sync. It writes app.json, mobile/PrivacyInfo.xcprivacy, mobile/play-data-safety.csv and the two association files under public/.well-known/. mainsdk explain config --target mobile shows where each app.json value came from.
2. Keep secrets in Keychain and Keystore
import { DeviceVault, type SecureStorePort } from "mainsdk/expo";
// In the app: import * as SecureStore from "expo-secure-store"; it has the three calls SecureStorePort names.
declare const SecureStore: SecureStorePort;
declare const token: string; // the refresh token from sign-in
export const vault = new DeviceVault(SecureStore);
await vault.saveRefreshToken(token);Wrap AsyncStorage with guardedPlainStore so a write whose key names a secret fails with MAIN-MOBILE-0002. mainsdk check raises MAIN-MOBILE-0003 for a secret written to AsyncStorage and MAIN-MOBILE-0004 for a raw fetch.
3. Attest the device and decide consent on it
On the server, build an Attestation with a verifier for App Attest and Play Integrity, call check before an action marked attest: required, and a request with no valid token fails with MAIN-AUTH-0100. On the phone, a DeviceGate decides analytics.track and storage.write with the same policy engine as the server, so an EU device sends nothing until the person agrees.
4. Send push through notify
registerPush asks permission and stores the token only when the person says yes. The expoPush, fcmPush and apnsPush drivers carry ids and no message text, and guardPush drops any field your labels mark as personal. Quiet hours and the stop action are notify's.
Check that it works
mainsdk checkpasses, and fails withMAIN-NET-0050whenInfo.plistsetsNSAllowsArbitraryLoads.- Send a push at 23:00 in the person's time zone: it is held until 07:00.
- The Expo, FCM and APNs request shapes follow each provider's documentation; send one push through each sandbox before launch.
Next
Was this page helpful?