Expo build prompt
Canonical /goal prompt for building a full Expo app with Amba as the only backend. Paste into Claude Code, Cursor, or Windsurf. Also available as the /amba-build skill.
Last reviewed: 2026-05-17. The canonical version of this page lives at docs.amba.dev/prompts/expo-build. If you're reading an inlined snapshot from your
.claude/skills/amba-build/SKILL.md, check the URL above for updates.
This is the prompt an AI coding agent runs to build a full Expo app
where Amba is the only backend. It's structured as a single /goal
directive — paste it, replace <DESIGN_HASH> with whatever describes
your design (a URL, a description, a Figma link), and let the agent
execute.
Quick setup
The CLI handles signup, project provisioning, env-file writes, and MCP client config wiring in one command:
That's the entire setup. The CLI:
- Signs up an agent-mode developer account (no browser, no email verification needed for sandbox).
- Creates an Amba project and mints a client key + admin PAT.
- Writes
.env.local(AMBA_PROJECT_ID,AMBA_CLIENT_KEY,AMBA_API_URL). - Writes
AMBA.md(project-scoped context for the agent). - Auto-wires
mcpServers.ambainto every MCP client config it detects on disk — Claude Code, Cursor, Windsurf. - Prints a per-client restart instruction.
After restarting your MCP client, the Amba MCP toolset is available
(amba_* tools — ~130 of them) and the agent can drive the backend
end-to-end.
If you have the /amba-build skill installed (via
npx @layers/amba init --sandbox), invoke it directly:
Otherwise paste the prompt below.
Current known gotchas
Three remaining wrinkles you may hit. Everything else from the 2026-05 DX cascade is fixed.
- Web CORS — the public API does not currently send
Access-Control-Allow-Originfor browser-origin requests. Use the agent's circuit-break-on-second-failure rule for web targets; for Expo (iOS + Android) you'll never see this. - React Native bundle size — the React Native SDK adds ~4 MB to the JS bundle today. Functional, just heavier than the long-term goal. Tracked separately.
- Sandbox MAU cap (50) — the agent-mode sandbox tier caps at 50
monthly active users. If you blow through it during testing, call
amba_users_reset_sandboxto clear the counter — that tool exists specifically for this. Upgrade to the Free tier (claim the email) to lift the cap to 1,000.
How to read the design
If <DESIGN_HASH> is a URL to a packaged design (e.g. a download
link from your design tool of choice), unpack it before you start:
Read the README first — it should tell you what each subdirectory
holds. The chats/ directory typically contains conversation logs
that capture the design intent in dialog form; treat them as the
authoritative source for tone and feature priorities. The project/
directory holds the structured asset graph (screens, components,
styles).
If <DESIGN_HASH> is a freeform description (not a URL), skip the
unpacking and treat the description text as the design brief.
Use Amba for everything
The rule: any feature that touches data, identity, scheduling, notifications, content, or social — use Amba. Don't reach for AsyncStorage-as-database, don't bring in Firebase / Supabase / your own server, don't roll a custom auth layer. The point of this build is that Amba covers it all.
Specifically: every feature in the design that needs a backend maps to an Amba primitive. If you can't find a fit, the rule is escalate in the gaps log (see the verification gate), not "ship without Amba." Skipping a primitive needs a written justification — the verification gate enforces this.
Feature → Amba primitive map
| App feature | Amba primitive | MCP tools |
|---|---|---|
| User accounts (anonymous + Apple + Google + email) | Auth | amba_developer_signup (one-time bootstrap), Amba.signIn() SDK calls |
| Profile data (name, avatar, prefs) | App users | amba_users_list, amba_users_get, amba_users_bulk_update |
| Daily content (tips, lessons, quotes) | Content libraries + schedules | amba_content_libraries_create, amba_content_items_add, amba_content_schedules_create, amba_content_list_libraries, amba_content_list_items, amba_content_list_schedules |
| Push notifications | Push campaigns | amba_push_campaigns_create, amba_push_campaigns_send, amba_push_send_test, amba_push_list_campaigns |
| User segments (e.g. inactive 7d, premium) | Segments | amba_segments_create, amba_segments_list, amba_segments_evaluate |
| Daily streaks | Streaks | amba_streaks_create, amba_streaks_list (call streaks.qualify() from the SDK to record activity) |
| XP and levels | XP rules | amba_xp_rules_create, amba_xp_rules_list, amba_users_get_xp |
| Achievements / badges | Achievements | amba_achievements_create, amba_achievements_list, amba_achievements_get |
| Challenges (time-limited goals) | Challenges | amba_challenges_create, amba_challenges_list, amba_challenges_list_participants |
| Leaderboards | Leaderboards | amba_leaderboards_create, amba_leaderboards_list, amba_leaderboards_get |
| In-app currency / virtual goods | Economy (currencies + catalog + stores) | amba_currencies_create, amba_catalog_items_create, amba_stores_create, amba_currencies_grant, amba_users_get_inventory |
| Social (friends, groups, feed, DMs) | Social primitives | amba_create_group, amba_groups_list, amba_groups_update_member, amba_friendships_list (feeds + messaging via SDK: Amba.feeds.*, Amba.messaging.*) |
| Remote feature flags / config | Configs | amba_configs_create, amba_configs_list, amba_configs_update |
| Entitlements (premium / paywall) | RevenueCat / Superwall integration | amba_integrations_configure, amba_integrations_test |
| Custom data (anything not above) | Collections | amba_collections_create, amba_collections_alter, amba_collections_list, amba_admin_insert_row, amba_admin_list_rows, plus client-side Amba.collections.* |
| Analytics / event tracking | Events | Amba.events.track(...) from the SDK; query with amba_analytics_get, amba_users_list_events |
Every primitive above has list / read MCP tools you can use to verify seed data after creation — the verification gate uses these to catch "fake implementation" failure modes (where the app code thinks a thing was created but nothing actually landed in the backend).
Seed data
Before writing app code, seed the backend with enough data that every screen in the design has something realistic to render. Order:
- Configs — feature flags + tunable constants the app reads at
boot (
amba_configs_create). - Segments — at least one (e.g. "new_user", first 7 days) so targeting works downstream.
- Content libraries + schedules — daily content for any tips/quotes/lessons screen. Seed ≥30 items so the carousel / day-stepper doesn't loop visibly.
- Streaks — define the streak shape (daily / weekly, grace window, freeze policy).
- XP rules — events → XP-award rules so XP accrues from real gameplay.
- Achievements — unlock criteria for badges.
- Challenges — at least one active challenge with rewards.
- Leaderboards — XP, streaks, or any custom metric.
- Currencies + catalog + stores — virtual currency, catalog items, store listings (only if the design has an economy screen).
- Collections — schemas + sample rows for any custom data the app needs (e.g. user-generated content, journal entries, custom list items).
- Push campaigns — at least one welcome push + one re-engagement push targeting your "new_user" segment.
After seeding, the verification gate (below) confirms each primitive
exists by calling the matching amba_*_list MCP tool. Empty list →
failure.
Engineering rules
These are non-negotiable. Violating any one of them fails the build gate.
- Expo Router with typed routes. Use
expo-routerand enableexperiments.typedRoutesinapp.json. Every screen is a filesystem route; no manual navigation stacks. - TypeScript strict mode.
strict: trueintsconfig.json. Zeroany. Zero@ts-ignore.tsc --noEmitmust pass. - React Native primitives only.
View,Text,Pressable,ScrollView,FlatList,Image. Nodiv, nospan, no DOM-only libs. The build target is iOS + Android + Web — every screen has to render on all three. - Fonts via expo-font. Don't ship system-font-only screens; load
the design's typography via
useFontsand gate the splash screen on load. - Persistence via AsyncStorage. Anything you cache client-side (theme choice, last-viewed-item, dismissed banners) goes in AsyncStorage. Never sprinkle direct file I/O.
- Theme system. A single
theme.tsexports light + dark token maps; consume via auseTheme()hook. The verification gate toggles light ↔ dark and screenshots; if any screen has hardcoded colors that don't flip, the gate fails. - Circuit-break on second failure. If two consecutive Amba API calls fail with the same error, stop retrying and surface a clean empty-state to the user. Don't loop forever; don't crash. The web CORS issue (above) is the most likely trigger.
- Deterministic offline fallback. When
fetchfails (airplane mode, network drop), the app renders deterministic placeholder content — same content peruserId + day— never random. Real data swaps in when the network returns. - Three-platform bundle gate.
expo export --platform web,expo export --platform ios, andexpo export --platform androidmust all succeed. If any one fails, the build fails. No "shipped iOS-only, web is broken" — the rule is parity. - Don't name a tab
settings.tsx. Useaccount.tsxorpreferences.tsxinstead. Expo Router's static web export generatessettings.htmlcorrectly but does not resolve direct URL navigation to/settings— the client-side router shows an unmatched-route error while other tab names work fine. (Observed in dogfood; upstream behavior, not an Amba issue.)
Verification gate
Before declaring the build done, run every check in this list. Any failure means the build is not done — fix and re-run.
Then, from inside the agent (use the Amba MCP tools):
amba_analytics_get→ at least one event tracked end-to-end throughAmba.events.track()from the app.amba_users_list→ at least one user exists (the agent's own anonymous signin counts).- For every primitive the seed step created, call the matching
amba_*_listand assert non-empty:amba_configs_listamba_segments_listamba_content_list_libraries,amba_content_list_items,amba_content_list_schedulesamba_streaks_listamba_xp_rules_listamba_achievements_listamba_challenges_listamba_leaderboards_listamba_currencies_list(if economy seeded)amba_catalog_list(if catalog seeded)amba_collections_list+amba_admin_list_rowsper collectionamba_push_list_campaigns
- Empty list for any seeded primitive → the implementation is fake (UI exists but never wrote to the backend). Failure.
- Manually walk every route in the browser (
expo start --web), screenshot each, and confirm:- Light theme renders cleanly.
- Dark theme renders cleanly (toggle and re-screenshot every route).
- Empty states render when collections are empty (fresh-install simulation: wipe AsyncStorage, reload).
amba_users_reset_sandboxto confirm you can recover from the MAU cap if you blew past 50 during testing.
Skipping any primitive's seed step requires a one-line written justification in the gaps log (next section). "We don't need streaks" is fine; silence is not.
Final output
When done, write a final report to BUILD_REPORT.md in the project
root. Required sections:
- Start timestamp (when the agent started).
- End timestamp (when the verification gate last passed).
- MCP call inventory — every
amba_*tool you invoked, with a count. Lets a human reviewer audit "did this agent actually use Amba for X" at a glance. - Primitive coverage table — one row per primitive from the Feature → Amba primitive map. Mark each ✅ (used), ⚠️ (used with caveats — explain), or ⏭ (skipped — justify in one line).
- Gaps log — every primitive you skipped, every feature you couldn't fit cleanly to an Amba primitive, every workaround. One line per gap, no marketing language.
seed-report.json— machine-readable seed summary:{ "primitive": "<name>", "created": <count>, "listed": <count> }for every primitive. Thelistedcount comes from theamba_*_listcall in the verification gate.created === listedfor every row is the success condition.
If BUILD_REPORT.md is missing any required section, or
seed-report.json is missing, the build is not done.