Models
NativeExpress connects to OpenAI, Anthropic and Google models through OpenRouter . Add a model by editing the shared registry and redeploying the functions that use it.
@openrouter/ai-sdk-provider is imported inside the edge functions as a Deno npm:
specifier. It is a server dependency and does not appear in the app’s package.json.
config.js and the server read the same file
The model list is defined in supabase/functions/_utils/ai.config.json.
config.js reads it for the app’s picker, and the edge functions import it too:
ai: require('./supabase/functions/_utils/ai.config.json'),"models": [
{ "id": "openai/gpt-5.6-sol", "provider": "openai", "label": "GPT-5.6 Sol", "hostedTier": true, "vision": true },
{ "id": "openai/gpt-5.6-luna", "provider": "openai", "label": "GPT-5.6 Luna", "hostedTier": true, "vision": true },
{ "id": "anthropic/claude-sonnet-5", "provider": "anthropic", "label": "Claude Sonnet 5", "hostedTier": true, "vision": true },
{ "id": "anthropic/claude-opus-4.8", "provider": "anthropic", "label": "Claude Opus 4.8", "hostedTier": false, "vision": true },
{ "id": "google/gemini-3-flash-preview", "provider": "google", "label": "Gemini 3 Flash", "hostedTier": true, "vision": true },
{ "id": "google/gemini-3.1-flash-image", "provider": "google", "label": "Gemini 3.1 Flash (Image)", "hostedTier": false, "vision": true, "imageOutput": true }
]| Field | Meaning |
|---|---|
id | The OpenRouter slug, sent to the provider as-is. |
provider | Display grouping in the picker only, not the routing. |
label | What the user sees. |
hostedTier | false locks the model in the picker for free users. Tapping it opens the paywall. This flag controls the picker, not server authorization. |
vision | The model accepts image input, so the attach button is available. |
imageOutput | The model can return images. Always Pro-gated. |
The client only ever sends a model id. The chat function checks it against MODELS,
built from this same file, so a request for a slug that was never in the picker is
rejected before it reaches OpenRouter:
const caps = MODELS.get(model);
if (!caps) throw new RequestError('invalid_model', 400);Redeploy chat after editing the model list. Until then, the app’s picker can
offer a model that the running function rejects with invalid_model.
Adding a model
Find the slug
Browse openrouter.ai/models and copy the exact slug. Check the model’s input and output modalities on that page, which is where vision and imageOutput come from.
Add it to ai.config.json
{ "id": "vendor/model-slug", "provider": "vendor", "label": "Model Name", "hostedTier": true, "vision": true }Set hostedTier: false to lock it in the picker for free users. For a model with
imageOutput: true, use hostedTier: false so the picker reflects the server’s
Pro requirement.
Redeploy
supabase functions deploy chatVerify
Check that the model appears in the picker and streams a reply. If you receive
invalid_model, confirm the ID matches the registry and redeploy chat.
Gating rules
The chat function applies these in order, before anything is sent to a provider:
- Unknown slug →
400 invalid_model. - Image attached to a non-vision model →
400 model_not_vision. - An
imageOutputmodel requested by a non-Pro user →403 pro_required. - Free quota exhausted and user is not Pro →
402 quota_exceeded.
hostedTier: false does not add a server-side Pro check for text models.
If you need to restrict those models, add an entitlement check in
supabase/functions/chat/index.ts before the provider call. Image-output models
already have this check.
The server checks Pro status through RevenueCat using REVENUECAT_SECRET_KEY and
the entitlement named by PRO_ENTITLEMENT_ID. If the key is unset, users cannot
access Pro-only features. See Quota and paywall.
Using a different provider
The functions build their provider in supabase/functions/_utils/provider.ts:
export function requireOpenRouter() {
const apiKey = Deno.env.get('OPENROUTER_API_KEY');
if (!apiKey) throw new RequestError('provider_not_configured', 500);
return createOpenRouter({ apiKey });
}To use a direct provider, replace the provider factory and its secret lookup, then
update its imports and model calls in chat, generate and identify. Update the
model IDs in ai.config.json to match that provider.
Check image input, structured output and image generation separately. The functions
currently use OpenRouter’s .chat() method and IMAGE_MODALITY_OPTIONS; another
provider may need different methods or options. Follow the chosen provider’s
AI SDK integration guide , then test
each feature before deploying it.