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-previewshut down March 9, 2026 — usegemini-3.1-pro-previewinstead.
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 |
// SDK pattern
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: prompt,
config: {
thinkingConfig: {
thinkingLevel: "high" // For complex analysis
}
}
});
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;
// Check retrieval status for each URL
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: {} }]
}
});
// Access grounding metadata (G5: DON'T discard this!)
const { webSearchQueries, groundingChunks, groundingSupports } =
response.candidates[0].groundingMetadata;
Extracting Citations (G5):
// groundingChunks contains source URLs and titles
const citations = groundingChunks?.map(chunk => ({
url: chunk.web?.uri,
title: chunk.web?.title
})) ?? [];
// groundingSupports maps text segments to sources
const supports = groundingSupports?.map(support => ({
text: support.segment?.text,
sources: support.groundingChunkIndices // indexes into groundingChunks
})) ?? [];
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) // G1: REQUIRED for valid JSON
}
});
const data = schema.parse(JSON.parse(response.text));
Why responseJsonSchema matters:
responseMimeTypealone = 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";
// G4: Use x-goog-api-key header, NOT ?key= query param
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, // G4: Header, not query param
},
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: {
responseMimeType: "application/json",
responseJsonSchema: schema, // G1: Include schema
// G2: Do NOT set temperature below 1.0
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);
// G5: Also extract grounding citations
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") });
// Enrichment with URL Context + Google Search + Structured Output
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), // G1+G3
thinkingConfig: {
thinkingLevel: "medium"
}
// G2: No temperature override — keeps default 1.0
}
});
// G5: Extract citations
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
};
}
// Analysis with high thinking
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), // G1
thinkingConfig: {
thinkingLevel: "high" // Deep reasoning for scoring
}
}
});
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 }
});
// Handle function call
const functionCall = response.functionCalls?.[0];
if (functionCall) {
// Execute your function, then send result back
}
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:
// Model response includes thoughtSignature
const response1 = await ai.models.generateContent({...});
const signature = response1.candidates[0].content.parts[0].thoughtSignature;
// Return signature in next request
const response2 = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [
{ role: "user", parts: [{ text: "Initial prompt" }] },
{
role: "model",
parts: [{
functionCall: {...},
thoughtSignature: signature // REQUIRED for function calling
}]
},
{ 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
functionCallhas 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: {} }], // Optional: ground in real data
imageConfig: {
aspectRatio: "16:9",
imageSize: "4K"
}
}
});
// Extract image
const imagePart = response.candidates[0].content.parts
.find(p => p.inlineData);
const imageData = imagePart.inlineData.data; // base64
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:
// Inline (small docs)
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [
{ text: "Summarize this document" },
{ inlineData: { mimeType: "application/pdf", data: base64Data } }
]
});
// Files API (large docs, recommended)
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.
// WRONG — causes issues on Gemini 3
config: { temperature: 0.4 }
// RIGHT — use default (omit temperature)
config: { /* no temperature setting */ }
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; // Don't retry 400/403/404
}
}
throw new Error("Max retries exceeded");
}
Token Counting
const { thoughtsTokenCount, candidatesTokenCount, totalTokenCount } =
response.usageMetadata;
// For URL Context
const { toolUsePromptTokenCount } = response.usageMetadata;
Common Patterns
Extraction with Fallback
try {
// Try URL Context first
const result = await enrichWithUrlContext(url);
} catch (error) {
// Fallback to Google Search
const result = await enrichWithGoogleSearch(companyName);
}
Founder Lookup (Google Search Only)
// DON'T use URL Context for LinkedIn (private profiles)
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: `Find info about founder: "${name}" at "${company}"`,
config: {
tools: [{ googleSearch: {} }], // Not urlContext!
responseMimeType: "application/json",
responseJsonSchema: founderSchema // G1: Always include schema
}
});
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:
- Use
npm:@google/genai@^1.0.0(not esm.sh, not legacy SDK) - Set
GEMINI_API_KEYviaDeno.env.get()in Edge Functions - G1: Use BOTH
responseMimeTypeANDresponseJsonSchemafor JSON output - G2: Do NOT set temperature below 1.0 on Gemini 3 models
- G3: Combine
responseJsonSchemawithgoogleSearchwhen using both - G4: Pass API key via
x-goog-api-keyheader (not query param) - G5: Extract
groundingChunkscitations from Google Search responses - Choose appropriate thinking level for task complexity
- Handle URL Context failures with Google Search fallback
- Don't use URL Context for private pages (LinkedIn)
- Return thought signatures for function calling and image editing
- Implement retry with exponential backoff for 429/5xx errors