Skip to Content
AIScan

Scan

The Scan tab identifies the main subject of a photo and returns its name, category, confidence, description and a few facts. It uses the identify edge function.

The Scan tab empty state, with a viewfinder illustration, the heading Identify anything, and a Start scanning button

Change the prompt or model

Change the Scan prompt or model in this project. Edit INSTRUCTION in supabase/functions/identify/index.ts or identifyModel in supabase/functions/_utils/ai.config.json, then redeploy identify and test a photo.

The prompt is a local constant at the top of supabase/functions/identify/index.ts:

supabase/functions/identify/index.ts
const INSTRUCTION = 'Identify the single main subject of this image. Give its common name, a broad ' + 'category (e.g. Animal, Plant, Food, Vehicle, Landmark, Everyday Object), your ' + 'confidence as a number between 0 and 1, a one or two sentence description, and ' + '2 to 4 short fun facts.';

IDENTIFY_MODEL is imported from _utils/registry.ts, which reads supabase/functions/_utils/ai.config.json. Set identifyModel to a model that accepts image input, then redeploy:

supabase/functions/_utils/ai.config.json
"identifyModel": "google/gemini-3-flash-preview"
supabase functions deploy identify

Repurposing it

Repurpose the Scan feature in this project for a different subject. Update INSTRUCTION and IdentifySchema in the identify edge function, IdentifyResult in src/features/scan/repository.ts and ScanResultCard.tsx together. Plan how to handle existing scan results before changing their shape.

To turn Scan into something else, such as a plant-disease checker or a wine-label reader, change INSTRUCTION and IdentifySchema together. Update the IdentifyResult type in src/features/scan/repository.ts and the display in src/features/scan/components/ScanResultCard.tsx, then redeploy. Existing rows keep their previous shape, so migrate those results or handle both shapes when rendering.

The result shape

The function validates successful results against this Zod schema:

supabase/functions/identify/index.ts
const IdentifySchema = z.object({ name: z.string(), category: z.string(), confidence: z.number().min(0).max(1), description: z.string(), funFacts: z.array(z.string()), });

It is stored as jsonb in scans.result.

The request takes imagePath, an uploaded object’s path within chat-media, such as {userId}/scans/{uuid}.jpg. The useIdentifyImage() hook uploads the selected photo and sends this path. The response contains:

FieldValue
idThe new scan row’s ID.
pathThe uploaded image’s storage path.
resultThe validated identification result.
createdAtThe scan row’s creation timestamp.

Gotchas

Categories are free text. The prompt suggests Animal, Plant, Food, Vehicle, Landmark, Everyday Object, but the model can return other labels. The category chips filter on those labels. For a fixed set, update INSTRUCTION and change category to a Zod enum.

Scan opens the system camera or photo library through expo-image-picker. The viewfinder graphic in the modal is not a live camera preview.

Photos are resized to a maximum long edge of 1024px before upload. Smaller images keep their dimensions.

Scans count toward the free monthly allowance alongside chat messages. Reaching the allowance returns 402 quota_exceeded and opens the upgrade paywall. See Quota and paywall.

Error codes

CodeHTTPMeaning
invalid_request400The request body is missing imagePath or has the wrong shape.
invalid_image400The path is invalid or is not under the caller’s user ID.
image_unavailable400The object couldn’t be downloaded server-side.
unauthorized401The Supabase access token is missing or invalid.
quota_exceeded402Free allowance used up.
provider_not_configured500OPENROUTER_API_KEY isn’t set.
internal_error500An unexpected server or provider error. Check the function logs.

Files

      • scan.tsx - Scan route
      • ScanScreen.tsx - Scan screen
      • repository.ts - Scan queries and the IdentifyResult type
        • ScanResultCard.tsx - Result display
        • ScanCameraModal.tsx - Photo selection
        • scans.ts - Scan requests and history
      • chatMedia.ts - Image uploads
    • index.ts - Prompt, schema and handler

How it works

Structured output. The function tries generateObject first. If that request fails, it asks for JSON through generateText and parses the response with the same schema. The function logs when it uses this fallback.

History. Row-level security limits useScans() to the signed-in user’s results, so the query needs no user_id filter.

Last updated on