Kitchen-sink Expo tutorial
Step-by-step build of a to-do list Expo app that exercises the whole Amba SDK surface — auth, content, streaks, remote config, and push.
This tutorial walks an AI agent (or a human) through building a real Expo to-do app that uses Amba end-to-end:
- Email signup / login.
- Todos stored as content-library items (reads and writes from the app).
- A daily "completed a todo" streak.
- A remote-config feature flag (
weekly_goal_count). - Push notifications on both platforms.
When you're done you'll have a TestFlight / Play-internal-ready app that demonstrates every major SDK module.
If you get stuck because this doc is ambiguous, the doc is wrong — file an issue.
1. Prerequisites
- Node.js 20 or later (
node -v). Node 22 is fine too. - pnpm 10 or later (
pnpm -v). The Amba monorepo uses pnpm workspaces; Path A below requires pnpm.npm/yarn/bunonly work for Path B once the packages ship on npm. - Expo CLI — we invoke via
npx, no global install needed. - iOS only: Xcode 15+ for local dev builds, or an Expo account for EAS Build.
- Android only: Android Studio with an emulator, or a physical device with USB debugging.
If you don't have an Apple Developer account ($99/yr), you can still build and run the app — you just won't be able to test iOS push or submit to TestFlight. Skip the iOS push section when you get there.
Push notifications require a development build on iOS (Expo Go does not support custom entitlements). Call this out up-front so you don't waste time trying to test push in Expo Go.
2. Install the SDK (two paths)
There are two supported install paths. Path A is the reality today. Path B is the published-to-npm story and is not yet working because @amba/cli, @amba/client, and @amba/expo have not been published.
Path A — clone the monorepo (recommended right now)
The monorepo already contains a ready-to-run kitchen-sink example at examples/expo-todo-kitchen-sink. examples/* is part of the pnpm workspace, so the demo consumes the SDKs via the workspace:* protocol — pnpm symlinks the live source, and any edit to packages/client/src/** shows up in the app immediately.
Want to start from scratch instead? Create a new Expo app inside examples/ so it's part of the pnpm workspace automatically:
Then run the CLI from the monorepo root to create an Amba project + mint an API key:
The root package.json exposes an "amba" script ("amba": "pnpm --filter amba exec node dist/index.js"), so pnpm amba <args> runs the CLI against the workspace build. You do not need to install anything globally.
What pnpm amba init does
- Authenticate — opens a browser for login, or reuses
~/.amba/credentials.json. - Store credentials — writes
~/.amba/credentials.json(chmod 0600). - Select or create a project — pick existing, or type a name for a new project.
- Generate API keys — mints a development client API key (
amb_dev_ck_...). - Write environment file — writes
.env.localwithAMBA_PROJECT_ID,AMBA_API_KEY, andAMBA_API_URLin the directory you ran the command from. - Detect framework — writes
AMBA.md+.cursor/rules/amba.mdcso your AI agent has project context.
Re-run from inside examples/my-app if you want those files placed next to the app (recommended):
Path B — npm install (coming soon)
Once @amba/cli, @amba/client, and @amba/expo are published to npm, this will be the preferred path:
Until then, use Path A. The rest of this tutorial assumes Path A — anywhere it says pnpm amba ..., substitute npx amba ... once the packages ship.
3. Environment variables
Expo only exposes env vars that start with EXPO_PUBLIC_ to client-side code. The CLI writes un-prefixed names (AMBA_PROJECT_ID) because the same file may be used by server-side tooling. For the Expo app, rewrite .env.local so every client-side value has the prefix:
There are no server-side env vars required for the mobile app itself. If you later add an edge function or a server that calls the Admin API, it will need un-prefixed developer credentials (AMBA_DEVELOPER_TOKEN=...) — never ship those to the client.
.gitignore in the Expo template already excludes .env*.local. Confirm before committing.
4. Configure the Expo plugin
The @amba/expo config plugin auto-wires push entitlements, Apple Sign In capability, and URL schemes so you don't have to hand-edit Info.plist / AndroidManifest.xml / app.json. Register the plugin and pass any options you want:
What the plugin adds for you:
- iOS —
aps-environmententitlement (defaultdevelopment, EAS flips toproductionat build time),applinks:associated domains, andCFBundleURLTypesfor deep-link URL schemes. Apple Sign In capability is enabled via the peerexpo-apple-authenticationplugin. - Android — intent filters on
.MainActivityfor each scheme/host/pathPrefix you pass.autoVerifyis added automatically when the scheme ishttps.
You never need to hand-edit Info.plist or AndroidManifest.xml for basic push + deep-link wiring. Rebuild native after changing app.json:
Pick your own bundleIdentifier / package — you'll need them later for APNs / FCM.
5. Initialize the SDK
Edit (or create) app/_layout.tsx:
If you're starting from the create-expo-app --template default template, keep the existing ThemeProvider + <Stack> wrapper around this — merge the ready-check into the template's root layout rather than replacing it wholesale.
Amba.init() does five things for you:
- Builds an
AmbaClientwith theasyncStorageAdapterstorage adapter. - Restores any persisted session (
auth.restore()). - Ensures an
anonymous_idexists. - Restores cached remote config (
config.restore()) and kicks off a backgroundconfig.refresh(). - Prompts for push permission and registers the device token (unless
autoRegisterPushToken: false).
6. Auth: email signup + login
Create app/(auth)/login.tsx:
Now gate the home tab on having a session. With the default template, (tabs)/index.tsx is the home route; drop the auth gate in there (don't create a new app/index.tsx, or it will race with (tabs)/index.tsx):
Amba.auth.logout() calls notify(null) internally, so the onAuthStateChange subscription fires and redirects the user to the login screen automatically.
Full AuthModule surface (all exposed on Amba.auth):
For the Expo one-liner social flows use Amba.signInWithApple() and Amba.signInWithGoogle() — they wrap the native prompt and then call loginWithApple/Google for you. See Authentication for details.
Run npx expo start and verify you can sign up and log out.
7. Todos: store them as content-library items
Each todo is a content_items row. The ContentModule ships read and write endpoints, so the app can create, edit, and delete todos directly — you don't need a server in front.
Create the library (admin-side, one time)
The library itself is admin-scoped (it defines the collection, not the items). Use an MCP-enabled agent like Cursor or Claude Code with the Amba MCP server configured (see MCP):
Or via the Admin API directly:
Capture the returned library_id and put it in .env.local (it's not a secret, but env-var-ification keeps it out of source):
Read, write, update, and delete todos in the app
The client SDK's ContentModule exposes:
The server stamps ownership from the current session — the caller cannot set owner_app_user_id. updateItem and deleteItem return 404 when the target item isn't owned by the current user.
Create app/todos.tsx (or add it as a new tab in (tabs)/ if you want it inside the tab bar — with the default template it's easier to add to the existing tabs group):
To make the screen reachable from the tab bar, add a Link to (tabs)/index.tsx or create a new tab file in app/(tabs)/ that re-exports this screen.
8. Streaks: track "completed a todo today"
Define the streak (admin-side)
Via MCP:
Capture the streak_definition_id — you'll only need it if you plan to call qualify() manually. If you're letting track('todo_completed') drive qualification (as the todo screen above does), no client-side id is required.
Fire the event when a todo is completed
The track() call in Step 7's toggle handler is all you need — the server matches the event name against every streak definition and qualifies whichever ones match:
You can also force-qualify without firing an event:
Read the current streak
Import it into (tabs)/index.tsx (or todos.tsx) wherever you want it rendered.
9. Remote config: weekly_goal_count
Set the flag via the CLI (from the monorepo root):
Or via MCP:
Read it in the app. The Expo wrapper exposes the config module as configModule (config is renamed to avoid colliding with React prop/state naming):
Because Amba.init() already calls restore() and kicks off a refresh() in the background, this read is fast and offline-safe.
Per-segment override
Set the value to 10 for users in a power_users segment:
10. Push notifications
Expo plugin handles the native wiring
You already registered @amba/expo as an Expo config plugin in Step 4. That means aps-environment, Apple Sign In capability, URL schemes, and Android intent filters are all added automatically when you prebuild or run expo run:ios / eas build.
You do not need to hand-edit:
ios/*.entitlementsios/Info.plist(deep-link schemes)android/app/src/main/AndroidManifest.xml(intent filters)
Push on iOS still requires a development build — Expo Go cannot add custom entitlements. npx expo run:ios or eas build --profile development both produce a dev build.
Configure APNs (iOS)
Via MCP:
If you don't have an Apple Developer account, skip this and rely on Android for push testing.
Configure FCM (Android)
Verify the app registered its token
After a successful sign-in on a real device (push requires a physical device — simulators don't get tokens), confirm via MCP or the Admin API:
If you want to defer the permission prompt (e.g. show your own soft-ask screen first), pass autoRegisterPushToken: false to Amba.init({...}) and call Amba.registerPushToken() yourself later.
Send a test push
Via MCP:
Or the CLI:
pnpm amba push test sends to every registered device for the project.
Send a scheduled campaign
The API rejects scheduled_at values in the past — use a future timestamp.
11. Test on iOS and Android
Expo Go
- Works for: auth, content, streaks, remote config.
- Does not support iOS push (custom entitlements unavailable).
- Supports Android push only with the Expo push service — for Amba-owned FCM delivery you need a development build.
Development build
A development build lets you test push on both platforms and matches your production native config.
Export without running
If you just want to confirm the app bundles, run:
Both should produce a clean bundle with no Metro errors.
12. Deploy to TestFlight / Play internal
Use EAS Build (the standard Expo CI):
Then submit:
Full EAS docs at https://docs.expo.dev/build/setup/.
What you just built
- Expo app with file-based routing.
@amba/expoinitialized in the root layout — session, anonymous id, and remote config restored on launch.- Email signup + login backed by bcrypt on your project's dedicated database.
- Todos rendered from a content library, with full create / update / delete from the app.
- Daily streak driven by
Amba.track('todo_completed', ...). - Remote-config flag (
weekly_goal_count) read in the UI. - Device push tokens registered automatically on both platforms.
- Test push + scheduled campaign delivered via Amba.
Every module you just used is covered in depth in the dedicated docs: