AI
NativeExpress includes three AI features: a streaming chat, an image generation studio, and an object scanner. All three talk to OpenRouter through Supabase Edge Functions, using the Vercel AI SDK .
Prerequisites
Complete the Supabase project setup, set your edge function secrets, and deploy the functions.
Set OPENROUTER_API_KEY as a Supabase function secret before using AI features.
A missing key returns provider_not_configured when a request reaches the provider setup.
How it works
Every AI feature is built the same way:
The provider key is kept only in Supabase function secrets, never in your app bundle or on the device. The client sends a Supabase access token, and the function derives the user from it rather than trusting a user id in the request body.
Before a function sends anything to OpenRouter, it checks that the user has allowed it,
and refuses with ai_consent_required otherwise. The App Store requires that
permission. See AI consent.
| Feature | Function | Client entry point |
|---|---|---|
| Chat | supabase/functions/chat | src/features/chat/chatTransport.ts |
| Image generation | supabase/functions/generate | src/features/create/generateImage.ts |
| Scan | supabase/functions/identify | src/features/scan/hooks/scans.ts |
Each feature is an independent directory under src/features/, with its own screens,
components, hooks and repository. Remove the ones your app does not need with
yarn remove:feature; see
Removing features and integrations.
Where data is stored
| Table | Holds |
|---|---|
threads | One row per conversation: owner, model, title, updated_at |
messages | One row per message: thread_id, role, and parts as JSON |
generations | One row per generated image: prompt, model, style, aspect ratio, image_path |
scans | One row per scan: image_path and the structured result JSON |
All four have owner-scoped row-level security: a user can only read and write their own rows.
Images are stored in a private Supabase Storage bucket called chat-media, under {userId}/…, and its policies scope each signed-in user to objects under their own prefix. Rows store the stable object path rather than a signed URL, because signed URLs expire, and the app signs the path on demand for display.
Configuration
Each feature’s models and presets are set in its own edge function’s config.json, and
the app reads the same file through src/features/<feature>/config.ts:
| Feature | File | Holds |
|---|---|---|
| Chat | supabase/functions/chat/config.json | The model list for the picker. See Models |
| Image generation | supabase/functions/generate/config.json | generateModel, style presets and aspect ratios |
| Scan | supabase/functions/identify/config.json | identifyModel |
The two settings every AI function shares, freeMessagesPerMonth and the
AI consent version consentVersion, are in
supabase/functions/_utils/ai.config.json. config.js exposes them to the app:
ai: require('./supabase/functions/_utils/ai.config.json'),Redeploy the affected functions after editing any of these files. The
AI_FREE_MESSAGES secret overrides freeMessagesPerMonth on the server, so keep
the two in sync. See Models and Quota and paywall.