Skip to Content
AIImage Generation

Image Generation

The Create tab is a single-shot image studio: type a prompt, pick a style and an aspect ratio, get an image. It is backed by the generate edge function.

Add a style preset to the Create tab in this project. Add it to stylePresets in supabase/functions/_utils/ai.config.json, then redeploy generate.
The Create tab showing a prompt field, style preset chips, aspect ratio chips, a Generate button, and the Pro-required upgrade state below

Change presets, ratios or the model

To changeEditThen
Style presetsstylePresets in ai.config.jsonRedeploy generate
Aspect ratiosaspectRatios in ai.config.jsonRedeploy generate
The modelgenerateModel in ai.config.jsonRedeploy generate

All three are defined in one file, supabase/functions/_utils/ai.config.json, which config.js also reads. The client sends only an id. The function looks up what that id means in the same file, so it never trusts wording the device sends. An id the function doesn’t recognise contributes an empty suffix rather than failing, which is why a missed redeploy shows up as unstyled images rather than an error.

Adding a style preset

Add it to ai.config.json

supabase/functions/_utils/ai.config.json
{ "id": "noir", "label": "Noir", "promptSuffix": ", black and white film noir, hard shadows, high contrast" }

Redeploy

supabase functions deploy generate

Verify

The chip appears in the Create tab and its images look styled. A chip that appears but produces unstyled images means the redeploy was missed.

Changing the model

Change the image generation model in this project. Update generateModel in supabase/functions/_utils/ai.config.json, then redeploy generate.

Pick a slug from openrouter.ai/models  whose output modalities include image, then redeploy:

supabase/functions/_utils/ai.config.json
"generateModel": "google/gemini-3.1-flash-image"

Gotchas

With no REVENUECAT_SECRET_KEY, generation is refused for everyone. The entitlement check fails closed, so an unset key reads as “nobody is Pro”. See Secrets.

Aspect ratios are approximate. The image model exposes no width or height parameter through OpenRouter, so the ratio is written into the prompt as text (, wide 16:9 cinematic landscape composition, much wider than tall).

Generations are never metered, because they are Pro-only. See Quota & Paywall.

Error codes

CodeHTTPMeaning
invalid_prompt400Empty prompt.
pro_required403Not a Pro user, or REVENUECAT_SECRET_KEY is unset.
no_image502The model replied without an image. Usually a prompt the provider refused.
provider_not_configured500OPENROUTER_API_KEY isn’t set.

Files

PieceWhere
Screensrc/app/(protected)/(tabs)/create.tsx
Componentssrc/components/create/
Client callsrc/lib/ai/generateImage.ts
Gallery hooksrc/hooks/generations.ts
Functionsupabase/functions/generate/index.ts
Tablegenerations

How it works

Generation is not streamed. Unlike chat, it’s a plain fetch that returns the finished image in one response.

The gallery is owner-scoped by row-level security. Images are stored as paths rather than signed URLs, because signed URLs expire, and get signed again on demand when the gallery renders.

Last updated on