| name | gemini |
| description | Use when integrating Gemini AI models in Supabase Edge Functions or Deno. Covers Gemini 3 models (Pro, Flash, Pro-Image), thinking levels, URL Context, Google Search grounding, structured output, thought signatures, function calling, image generation, document processing, deep search, error handling, and REST API patterns. Use for AI enrichment, data extraction, analysis tasks, and image generation. |
Gemini AI Integration
Build AI features using Google's Gemini 3 models with URL Context, Google Search grounding, structured outputs, thinking levels, function calling, and image generation.
CRITICAL RULES (Audit Findings G1-G6)
These rules MUST be followed in every Gemini integration. Violating them causes crashes, degraded output, or security issues.
| ID | Rule | Why |
|---|
| G1 | Always use responseJsonSchema with responseMimeType: "application/json" | responseMimeType alone just asks the model to try JSON. Adding responseJsonSchema guarantees syntactically valid JSON matching the schema. Without it, JSON parse crashes are likely. |
| G2 | Keep temperature at default 1.0 for Gemini 3 | Setting temperature below 1.0 causes looping, repetitive output, or degraded performance on Gemini 3 models. The docs explicitly warn against this. |
| G3 | Combine structured output with Google Search | Gemini 3 supports responseJsonSchema + googleSearch together. Use both to get valid JSON even with search grounding. Without the schema, search metadata can corrupt JSON output. |
| G4 | Pass API key via x-goog-api-key header, NOT query param | ?key= exposes the key in server logs and referrer headers. Every official REST example uses the header. |
| G5 | Extract groundingChunks citations from Google Search | The response includes groundingChunks (source URLs/titles) and groundingSupports (inline citation mapping). Don't discard this data — use it for citations. |
| G6 | Both googleSearch and google_search casing work | The API accepts both camelCase and snake_case via protobuf. Use camelCase for SDK consistency. |
Quick Reference
| Model | Use Case | Context | Pricing |
|---|
gemini-3.1-pro-preview | Latest Pro — complex reasoning, agentic, vibe coding | 1M / 64k | $2-4 / $12-18 |
gemini-3-flash-preview | Fast responses, extraction, chat | 1M / 64k | $0.50 / $3 |
gemini-3.1-flash-lite-preview | Cost-efficient, fastest for high-frequency tasks | 1M / 64k | $0.50 / $3 |
gemini-3.1-flash-image-preview | Fast image generation + editing | 131k / 32k | $2 / $0.134 |
gemini-3-pro-image-preview | Pro image generation (Nano Banana Pro) | 131k / 32k | $2 / $0.134 |
⚠️ DEPRECATED: gemini-3-pro-preview shut down March 9, 2026 — use gemini-3.1-pro-preview instead.
All Gemini 3 models have a knowledge cutoff of January 2025.
SDK Setup (Deno Edge Functions)
import { GoogleGenAI } from "npm:@google/genai@^1.0.0";
const ai = new GoogleGenAI({ apiKey: Deno.env.get("GEMINI_API_KEY") });
IMPORTANT: Use npm:@google/genai@^1.0.0 (not esm.sh, not legacy @google/generativeai).
Thinking Levels
Control reasoning depth with thinkingLevel (Gemini 3):
| Level | Models | Use Case | Latency |
|---|
minimal | Flash only | Matches "no thinking" for most queries | Lowest |
low | Pro + Flash | Simple instruction following | Low |
medium | Flash only | Balanced tasks | Medium |
high | Pro + Flash | Complex analysis, scoring (default) | Higher |
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: prompt,
config: {
thinkingConfig: {
thinkingLevel: "high"
}
}
});
Best Practice: Use high for scoring, readiness calculations, and complex analysis. Use low or minimal for simple extraction and chat.
WARNING: Cannot use both thinkingLevel and legacy thinkingBudget in the same request (400 error).
Built-in Tools
1. URL Context
Extract data from web URLs directly:
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `Extract company info from ${url}`,
config: {
tools: [{ urlContext: {} }]
}
});
Limitations:
- Up to 20 URLs per request
- Max 34MB per URL
- Cannot access: paywalled content, YouTube, Google Workspace, localhost
- Supported: HTML, JSON, PDF, images (PNG, JPEG)
Metadata access:
const metadata = response.candidates[0].urlContextMetadata;
2. Google Search Grounding
Connect to real-time web information:
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: "Who won the 2024 Euro championship?",
config: {
tools: [{ googleSearch: {} }]
}
});
const { webSearchQueries, groundingChunks, groundingSupports } =
response.candidates[0].groundingMetadata;
Extracting Citations (G5):
const citations = groundingChunks?.map(chunk => ({
url: chunk.web?.uri,
title: chunk.web?.title
})) ?? [];
const supports = groundingSupports?.map(support => ({
text: support.segment?.text,
sources: support.groundingChunkIndices
})) ?? [];
Combine Tools:
config: {
tools: [
{ urlContext: {} },
{ googleSearch: {} }
]
}
Structured Output
Force JSON responses matching a schema. G1: Always use responseJsonSchema — this guarantees valid JSON.
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
const schema = z.object({
company_name: z.string(),
industry: z.array(z.string()),
description: z.string(),
founded_year: z.number().optional()
});
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `Extract company info from: ${url}`,
config: {
tools: [{ urlContext: {} }],
responseMimeType: "application/json",
responseJsonSchema: zodToJsonSchema(schema)
}
});
const data = schema.parse(JSON.parse(response.text));
Why responseJsonSchema matters:
responseMimeType alone = model tries to output JSON (can fail)
responseMimeType + responseJsonSchema = guaranteed syntactically valid JSON matching the schema
- Works with Google Search, URL Context, and Code Execution tools (G3)
Supported types: string, number, integer, boolean, object, array, null
REST API Patterns
For raw fetch() calls in Edge Functions (instead of SDK):
Basic Request
const GEMINI_API_KEY = Deno.env.get("GEMINI_API_KEY");
const model = "gemini-3-flash-preview";
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": GEMINI_API_KEY,
},
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: {
responseMimeType: "application/json",
responseJsonSchema: schema,
thinkingConfig: {
thinkingLevel: "high"
}
}
})
});
const data = await response.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
With Google Search + Structured Output (G3)
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": GEMINI_API_KEY,
},
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
tools: [{ googleSearch: {} }],
generationConfig: {
responseMimeType: "application/json",
responseJsonSchema: {
type: "object",
properties: {
summary: { type: "string" },
sources: {
type: "array",
items: {
type: "object",
properties: {
url: { type: "string" },
title: { type: "string" }
}
}
}
},
required: ["summary", "sources"]
}
}
})
});
const data = await response.json();
const result = JSON.parse(data.candidates[0].content.parts[0].text);
const groundingChunks = data.candidates[0].groundingMetadata?.groundingChunks ?? [];
Streaming (REST)
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": GEMINI_API_KEY,
},
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }]
})
});
Complete Edge Function Pattern
import { GoogleGenAI } from "npm:@google/genai@^1.0.0";
import { z } from "npm:zod@^3.23.0";
import { zodToJsonSchema } from "npm:zod-to-json-schema@^3.23.0";
const ai = new GoogleGenAI({ apiKey: Deno.env.get("GEMINI_API_KEY") });
async function enrichUrl(url: string) {
const schema = z.object({
company_name: z.string(),
description: z.string(),
industry: z.array(z.string()),
competitors: z.array(z.object({
name: z.string(),
differentiator: z.string()
}))
});
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `Analyze this company: ${url}. Find competitors.`,
config: {
tools: [
{ urlContext: {} },
{ googleSearch: {} }
],
responseMimeType: "application/json",
responseJsonSchema: zodToJsonSchema(schema),
thinkingConfig: {
thinkingLevel: "medium"
}
}
});
const citations = response.candidates[0]?.groundingMetadata
?.groundingChunks?.map(c => ({ url: c.web?.uri, title: c.web?.title })) ?? [];
return {
data: schema.parse(JSON.parse(response.text)),
citations
};
}
async function calculateReadiness(data: object) {
const schema = z.object({
overall_score: z.number().min(0).max(100),
category_scores: z.object({
product: z.number(),
market: z.number(),
team: z.number(),
clarity: z.number()
}),
recommendations: z.array(z.string())
});
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `Calculate investor readiness score: ${JSON.stringify(data)}`,
config: {
responseMimeType: "application/json",
responseJsonSchema: zodToJsonSchema(schema),
thinkingConfig: {
thinkingLevel: "high"
}
}
});
return schema.parse(JSON.parse(response.text));
}
Function Calling
Declare tools the model can call:
const tools = [{
functionDeclarations: [{
name: "get_weather",
description: "Get weather for a city",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "City name" }
},
required: ["city"]
}
}]
}];
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: "What's the weather in Paris?",
config: { tools }
});
const functionCall = response.functionCalls?.[0];
if (functionCall) {
}
Modes: AUTO (default), ANY (force tool use), NONE (disable), VALIDATED
NOTE: Combining built-in tools (Google Search, URL Context) with function calling is NOT supported in Gemini 3.
Thought Signatures (Multi-turn)
For function calling and image editing workflows, preserve thought context:
const response1 = await ai.models.generateContent({...});
const signature = response1.candidates[0].content.parts[0].thoughtSignature;
const response2 = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [
{ role: "user", parts: [{ text: "Initial prompt" }] },
{
role: "model",
parts: [{
functionCall: {...},
thoughtSignature: signature
}]
},
{ role: "user", parts: [{ functionResponse: {...} }] }
]
});
Rules:
- Required for function calling (400 error without it)
- Required for multi-turn image editing conversations (single-turn generation works without them)
- Recommended for text/chat (improves reasoning continuity)
- SDKs handle this automatically
- SDKs manage thought signatures automatically; preserve all accumulated signatures in multi-turn
- Parallel calls: only the first
functionCall has the signature
- Multi-step: ALL accumulated signatures must be preserved
Image Generation
const response = await ai.models.generateContent({
model: "gemini-3-pro-image-preview",
contents: "Generate a logo for a tech startup",
config: {
tools: [{ googleSearch: {} }],
imageConfig: {
aspectRatio: "16:9",
imageSize: "4K"
}
}
});
const imagePart = response.candidates[0].content.parts
.find(p => p.inlineData);
const imageData = imagePart.inlineData.data;
Key capabilities:
- 4K resolution with text rendering
- Grounded generation via Google Search
- Conversational editing (multi-turn, requires thought signatures)
- SynthID watermarks on all generated images
Document Processing
Process PDFs and documents directly:
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [
{ text: "Summarize this document" },
{ inlineData: { mimeType: "application/pdf", data: base64Data } }
]
});
const file = await ai.files.upload({ file: "document.pdf" });
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [
{ text: "Extract key findings" },
{ fileData: { fileUri: file.uri, mimeType: file.mimeType } }
]
});
- Supports up to 1000 pages
- Use
mediaResolution: "media_resolution_medium" for standard PDFs
- Combine with structured output for typed extraction
System Instructions
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: "Hello there",
config: {
systemInstruction: "You are a startup advisor. Be concise and data-driven.",
}
});
Temperature
G2: Always use default temperature (1.0) for Gemini 3. Lower values cause looping or degraded performance, especially for reasoning and math tasks.
config: { temperature: 0.4 }
config: { }
Error Handling
| Code | Error | Cause | Fix |
|---|
| 400 | INVALID_ARGUMENT | Bad request params, missing thought signature | Check schema, ensure thought signatures present |
| 403 | PERMISSION_DENIED | Invalid API key or restricted | Check key, ensure API enabled |
| 404 | NOT_FOUND | Model not found | Verify model ID (e.g., gemini-3-flash-preview) |
| 429 | RESOURCE_EXHAUSTED | Rate limit hit | Implement exponential backoff |
| 500 | INTERNAL | Server error | Retry with backoff |
| 503 | UNAVAILABLE | Service temporarily down | Retry with backoff |
Retry pattern:
async function callGeminiWithRetry(fn: () => Promise<any>, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.status === 429 || error.status >= 500) {
await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
continue;
}
throw error;
}
}
throw new Error("Max retries exceeded");
}
Token Counting
const { thoughtsTokenCount, candidatesTokenCount, totalTokenCount } =
response.usageMetadata;
const { toolUsePromptTokenCount } = response.usageMetadata;
Common Patterns
Extraction with Fallback
try {
const result = await enrichWithUrlContext(url);
} catch (error) {
const result = await enrichWithGoogleSearch(companyName);
}
Founder Lookup (Google Search Only)
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `Find info about founder: "${name}" at "${company}"`,
config: {
tools: [{ googleSearch: {} }],
responseMimeType: "application/json",
responseJsonSchema: founderSchema
}
});
Resources
- references/gemini-3.md - Full Gemini 3 documentation
- references/text-generation.md - Text gen, system instructions, temperature
- references/structured-output.md - JSON schema output, Zod patterns
- references/google-search.md - Google Search grounding, citations
- references/function-calling.md - Tool declarations, multi-turn
- references/thinking.md - Thinking levels, budgets
- references/thought-signatures.md - Multi-turn state, function calling
- references/url-context.md - URL extraction, limitations
- references/image-generation.md - Nano Banana, 4K, SynthID
- references/document-processing.md - PDF, inline data, Files API
- references/deep-search.md - Deep Research Agent, Interactions API
- references/troubleshooting.md - Error codes, temperature warnings
- references/api-setup.md - API keys, SDKs, quickstart
Checklist
When implementing Gemini AI: