--- name: integrating-convex-expo description: >- Sets up and evolves Convex backends for Expo / React Native apps: create deployments, configure EXPO_PUBLIC_CONVEX_URL (and EAS env vars), wire up ConvexProvider + ConvexReactClient, use generated api types, and implement queries/mutations/actions, schemas/indexes, auth, and file uploads. Use when the user mentions Convex with Expo/React Native, create-expo-app, expo-router, useQuery/useMutation, ConvexReactClient, convex dev, deployment URL, EAS builds, or asks for a realtime database/backend in an Expo app. compatibility: >- Intended for Expo (React Native) apps (including expo-router) using Node.js and the Convex CLI via npx. Assumes you can run shell commands and edit project files. metadata: version: 1.0.0 category: mobile-backend tags: [convex, expo, react-native, realtime, typescript] --- # Integrating Convex with Expo (React Native) ## Why this skill exists Convex setup in React Native is straightforward, but *small* details (Expo environment variables, file locations, keeping `convex dev` running) regularly cause broken builds or “it returns undefined” confusion. This skill standardises a reliable, end-to-end workflow. ## Non-negotiables (do these every time) - **Keep `npx convex dev` running** while developing. It syncs functions + generates the `convex/_generated` API/types. - **Use Expo public env vars**: the client should read `process.env.EXPO_PUBLIC_CONVEX_URL`. - **Create exactly one Convex client** per app, and wrap the whole tree in `ConvexProvider`. - **React Native:** set `unsavedChangesWarning: false` on `ConvexReactClient` (the default warning is web-centric). - **Don’t hand-type API names:** always import from `convex/_generated/api` and call `api.someFile.someExport`. ## First decision: what kind of Expo project is this? 1. **expo-router project** (common with `create-expo-app`): - root layout is usually `app/_layout.tsx` or `src/app/_layout.tsx` 2. **classic App.tsx entrypoint**: - wrap `App.tsx` with `ConvexProvider` When unsure, search for `expo-router` in `package.json` and look for an `app/` folder. --- # Workflow A — Add Convex to an existing Expo app (recommended default) ## A1) Install Convex From the Expo app root: ```bash npx expo install convex ``` (If you aren’t using Expo’s installer, `npm i convex` also works.) ## A2) Create a Convex dev deployment + backend folder ```bash npx convex dev ``` What you should see / get: - prompts to log in and create a project - a new `convex/` folder for backend functions - `.env.local` containing `EXPO_PUBLIC_CONVEX_URL=...` - the command keeps running to sync changes ## A3) Ensure `EXPO_PUBLIC_CONVEX_URL` is available in development *and* builds - **Dev (Expo CLI / Metro):** `npx convex dev` writes `.env.local` with `EXPO_PUBLIC_CONVEX_URL`. - **EAS builds:** copy that value into EAS env vars. See: [references/eas-env.md](references/eas-env.md) ## A4) Wire up the Convex client + provider (expo-router) Edit `app/_layout.tsx` *or* `src/app/_layout.tsx`: ```ts import { ConvexProvider, ConvexReactClient } from "convex/react"; import { Stack } from "expo-router"; const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, { unsavedChangesWarning: false, }); export default function RootLayout() { return ( ); } ``` ### If the project does not use expo-router Wrap your top-level component (usually `App.tsx`) similarly: ```ts import { ConvexProvider, ConvexReactClient } from "convex/react"; const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, { unsavedChangesWarning: false, }); export default function App() { return ( {/* your existing navigation / UI */} ); } ``` ## A5) Create a first query + show it in the app Use the canonical “tasks” example: - backend: `convex/tasks.ts` - seed: `sampleData.jsonl` - import: `npx convex import --table tasks sampleData.jsonl` - UI: `useQuery(api.tasks.get)` See: [references/tasks-example.md](references/tasks-example.md) ✅ Quality gate: when the app loads, `useQuery` should initially be `undefined` (loading) and then become an array of tasks. --- # Workflow B — Add a new feature end-to-end (schema → function → UI) Use this loop for each feature (it keeps you “in sync” across app/backend/db): 1. **Data model first (schema + indexes)** - define/extend tables in `convex/schema.ts` - add indexes you’ll query by (before you have 1000+ docs) - see: [references/schema-and-indexes.md](references/schema-and-indexes.md) 2. **Backend API (query/mutation/action)** - prefer **query** for reads, **mutation** for writes, **action** when you need Node APIs or third-party fetches that don’t fit the normal runtime - always validate args with `v.*` - see: [references/functions.md](references/functions.md) 3. **Frontend integration** - `useQuery(api.file.export)` - `useMutation(api.file.export)` - consider loading/empty/error states explicitly - see: [references/frontend-patterns.md](references/frontend-patterns.md) 4. **Run/verify** - keep `npx convex dev` running - restart Metro if env vars changed - run the validation script: `python scripts/validate_project.py` --- # Workflow C — Authentication (pick one and stick to it) Convex supports multiple approaches; the right choice depends on your app’s needs: - **Convex Auth**: simplest end-to-end if you’re all-in on Convex. - **Clerk**: common in Expo; Convex provides `` for integration. - **Other OIDC/JWT providers**: generally work by passing JWTs and enforcing access control in functions. See: [references/auth.md](references/auth.md) --- # Workflow D — File uploads from Expo / React Native Use Convex File Storage upload URLs: 1. mutation generates a short-lived upload URL 2. client `fetch(putUrl, { method: "POST", ... })` 3. store the returned `storageId` in your table See: [references/file-uploads.md](references/file-uploads.md) --- # Troubleshooting (fast path) If something breaks, do this in order: 1. **Is `npx convex dev` running?** If not, start it. 2. **Is `process.env.EXPO_PUBLIC_CONVEX_URL` defined at runtime?** - if you added/changed `.env*`, restart Metro with cache cleared. 3. **Does the app wrap the entire tree with `ConvexProvider`?** 4. **Are you importing `api` from `convex/_generated/api` and calling `useQuery(api.x.y)`?** 5. Run: `python scripts/validate_project.py` More cases + fixes: [references/troubleshooting.md](references/troubleshooting.md) --- # Scripts included - `scripts/validate_project.py` — checks package.json, env vars, Convex folder, and provider wiring. - `scripts/scaffold_tasks_example.py` — generates the “tasks” example files (dry-run by default). --- # References (load only when needed) - [references/eas-env.md](references/eas-env.md) — `.env.local` vs EAS env vars, Expo gotchas - [references/tasks-example.md](references/tasks-example.md) — seed + query + UI in Expo - [references/schema-and-indexes.md](references/schema-and-indexes.md) — schemas, validators, indexes, query perf - [references/functions.md](references/functions.md) — queries, mutations, actions patterns - [references/frontend-patterns.md](references/frontend-patterns.md) — hooks patterns, loading/error handling, navigation - [references/auth.md](references/auth.md) — Convex Auth vs Clerk vs others, access control patterns - [references/file-uploads.md](references/file-uploads.md) — RN/Expo file URIs → Blob → Convex storage - [references/troubleshooting.md](references/troubleshooting.md) — common issues and exact fixes - [references/sources.md](references/sources.md) — upstream docs used to build this skill