Quick answer: Gemini File Search is a fully managed RAG tool inside the Gemini API. You don’t bring a vector database. You don’t manage chunking. You upload files (PDFs, images, code), create a store, and query it — the API handles chunking, embedding, indexing, and retrieval. With the May 2026 update, it now supports multimodal search via the gemini-embedding-2 model, meaning images are embedded natively alongside text, and citations include downloadable photo references. Here’s the workflow in four steps:
- Create a File Search store with
gemini-embedding-2 - Upload documents and images
- Query with the
file_searchtool attached togenerate_content - Inspect citations — text gets page numbers, images get a
media_idyou can download
This tutorial uses JavaScript/Node.js, which none of the current top tutorials cover for the multimodal release.
What Gemini File Search actually is
Be aware of these three things people get wrong about Gemini File Search:
First: You don’t need a vector database. File Search is the whole pipeline — chunking, embedding with gemini-embedding-001 or gemini-embedding-2, indexing, and semantic retrieval — all server-side. You write zero embedding code. You provision zero vector databases. Storage and query-time embeddings are free. You pay only for the indexing pass at $0.15 per million tokens. That’s not a “cheaper RAG” pitch. It’s a “no RAG infrastructure at all” pitch.
Second: It’s not text-only. With gemini-embedding-2, images are embedded natively — not OCR’d, not converted to text descriptors, not routed through a separate CLIP model. A product photo, a diagram in a PDF, a chart in a report — they all live in the same index as your documents. Queries can match on visual similarity. Citations for image results include a media_id you call to download the actual image. No separate multimodal preprocessing step. No dual vector database architecture.
Third: This is not Vertex AI Search. File Search is a lightweight, per-project tool inside the Gemini API and AI Studio. It’s built for prototyping, internal tools, and single-tenant applications. You do not get IAM hierarchies, VPC-SC, CMEK, SLAs, or multi-tenant serving. If you need compliance boundaries or production uptime guarantees, File Search is the wrong path — go to Vertex AI RAG Engine or Agent Builder. Know which one you’re using before you build anything serious.
What you need before starting
- Node.js 18 or later
- A Gemini API key from Google AI Studio
- The
@google/genaiJavaScript SDK:npm install @google/genai - At least one PDF, image, or text file to index
If you’re working with Python, the official dev.to guide covers that side. This tutorial focuses on JavaScript/TypeScript because it’s the gap in the current SERP — nearly every tutorial uses Python.
Step 1: Create a File Search store
A store is a persistent container for your embeddings. Think of it as a managed vector database scoped to your project. Documents stay indexed until you delete the store — no 48-hour file expiry like the standard Files API.
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
// Create a multimodal store — omitting embedding_model defaults to
// gemini-embedding-001 (text-only, cost-optimized)
const store = await ai.fileSearchStores.create({
displayName: "product-catalog",
embeddingModel: "models/gemini-embedding-2",
});
console.log(`Created store: ${store.name}`);
// Output: Created store: fileSearchStores/product-catalog-a1b2c3d4
Two embedding models, two different jobs:
| Model | Best for |
|---|---|
gemini-embedding-001 (default) | Text-heavy workloads, lower cost |
gemini-embedding-2 | Multimodal — documents AND images in the same index |
Pick gemini-embedding-2 if images matter. The decision is permanent per store — you can’t switch a store’s embedding model after creation.
If you skip the embeddingModel parameter, you get gemini-embedding-001. That’s fine for text-only document Q&A. For visual search — product shots, diagrams, damage photos — you need gemini-embedding-2.
Step 2: Upload documents and images
Uploads are asynchronous. The API returns an operation immediately and indexes in the background. Poll until done is true.
// Upload a PDF
let operation = await ai.fileSearchStores.upload({
fileSearchStoreName: store.name,
file: "./product_catalog.pdf",
config: { displayName: "Product Catalog" },
});
while (!operation.done) {
await new Promise((r) => setTimeout(r, 3000));
operation = await ai.operations.get({ operationName: operation.name });
}
console.log("PDF indexed");
// Upload images directly — no preprocessing needed
for (const image of ["sneaker_red.png", "sneaker_blue.jpeg", "sneaker_white.png"]) {
operation = await ai.fileSearchStores.upload({
fileSearchStoreName: store.name,
file: `./images/${image}`,
config: { displayName: image },
});
while (!operation.done) {
await new Promise((r) => setTimeout(r, 3000));
operation = await ai.operations.get({ operationName: operation.name });
}
console.log(`${image} indexed`);
}
What’s happening under the hood: the API chunks documents, generates embeddings with the model you chose at store creation, and indexes everything. With gemini-embedding-2, images inside PDFs — charts, diagrams, photos — are embedded alongside the text. No separate image pipeline.
A realistic indexing rate for typical documents: roughly 10-15 seconds per file. If you’re uploading 50,000 PDFs (as we did for the insurance claims example later in this article), that’s about a week of continuous indexing. Plan accordingly. Use batch scripts, not interactive loops. Add timeout logic — something will hang eventually.
Step 3: Query your store
Attach the fileSearch tool to your generateContent call. The model retrieves relevant chunks from your store before generating.
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: "Which sneakers come in red?",
config: {
tools: [
{
fileSearch: {
fileSearchStoreNames: [store.name],
},
},
],
},
});
console.log(response.text);
The model performs semantic retrieval — it finds conceptually similar chunks, not keyword matches. If you uploaded a red sneaker image alongside spec sheets, a natural-language query about “red sneakers” pulls the visual match and any text descriptions.
Choose gemini-3-flash-preview for most queries. It’s fast and cheap. Reserve gemini-3-pro-preview for complex multi-source synthesis — comparing payouts across 50 claims, for instance.
Step 4: Read citations and retrieve images
Every response includes grounding metadata — a bibliography for the model’s answer. Text citations come with page numbers. Image citations come with a mediaId you can download.
const grounding = response.candidates[0].groundingMetadata;
for (const chunk of grounding.groundingChunks) {
const ctx = chunk.retrievedContext;
if (ctx.mediaId) {
// Image citation — download the actual image
console.log(`Cited image: ${ctx.title} (ID: ${ctx.mediaId})`);
const blob = await ai.fileSearchStores.downloadMedia({
mediaId: ctx.mediaId,
});
// Save to disk or serve to the user
require("fs").writeFileSync(`./cited_${ctx.title}`, blob);
} else {
// Text citation with page number
console.log(`Cited text: ${ctx.title}`);
if (ctx.pageNumber) console.log(` Page: ${ctx.pageNumber}`);
console.log(` ${ctx.text.substring(0, 200)}...`);
}
}
// See which response segments came from which sources
for (const support of grounding.groundingSupports) {
console.log(`Claim: "${support.segment.text}"`);
console.log(` Grounded in chunks: ${support.groundingChunkIndices}`);
}
This is where managed RAG earns its keep. You’re not building a citation layer from scratch. The model already knows which chunks informed which parts of the answer, and the API surfaces that metadata directly. For user-facing apps — insurance claims, legal research, medical documentation — this traceability is the difference between a tool people trust and one they ignore.
Step 5: Add metadata filters
Metadata filters scope the search before retrieval runs. Without them, a query about “garage damage” searches your entire store. With them, it only hits claims in California with HO-3 policies from 2020.
Add metadata at upload time:
operation = await ai.fileSearchStores.upload({
fileSearchStoreName: store.name,
file: "./claim_49201.pdf",
config: {
displayName: "Claim #CL-49201",
customMetadata: [
{ key: "policyType", stringValue: "HO-3" },
{ key: "year", numericValue: 2020 },
{ key: "state", stringValue: "CA" },
],
},
});
Filter at query time:
const response = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: "Find similar claims for tree-on-garage damage. What did we pay?",
config: {
tools: [
{
fileSearch: {
fileSearchStoreNames: [store.name],
metadataFilter: 'policyType="HO-3" AND year=2020',
},
},
],
},
});
The filter runs first — it narrows candidate documents — then semantic search finds relevant chunks within that subset. This cuts noise and speeds up retrieval when you have tens of thousands of documents in a single store.
A real example: insurance claims with multimodal search
Here’s a theoretical example of how Google File Search would work on a claims dataset.
Imagine a home insurance adjuster gets a call: a tree has fallen on a policyholder’s garage. Their policy is a 2020 Preferred HO-3, claim number CL-49201. The adjuster opens an internal app backed by a single File Search store containing 50,000 scanned claim PDFs — adjuster notes, estimates, damage photos. Each PDF was uploaded with metadata: policyType, year, claimOutcome, state.
They run a single query:
Find me 3 similar claims where a tree damaged a garage on a 2020 HO-3 policy.
What did we pay out? Show me the damage photos.
The model returns three things in one response:
- A comparison table of 3 similar claims with payout ranges and settlement dates
- Actual photos of tree-on-garage damage from those claims — retrieved via
mediaId, not described in text - Page-number citations to the adjuster notes justifying each payout amount
The adjuster doesn’t dig through PDFs. They don’t search for “Exhibit C” in a 40-page document. The photos appear directly in the response. The page numbers are clickable references.
This pattern is hard to replicate without File Search because of one architectural reason: most RAG pipelines index text in one vector database and images in another. Text gets an embedding model. Images get a separate CLIP model or Vision API call, stored in a different index, queried separately. File Search puts text, tables, and photos in the same index. One query. One retrieval. One response with everything together.
The metadata filter prevents a “garage” query in California from pulling up Alaska snow-damage claims. And the photo citations mean the adjuster sees the damage — not a text description of the damage — before making any decision.
Common mistakes
Mistake 1: Using the wrong embedding model for the job. If you create a store with gemini-embedding-001 (the text-only default), images are either skipped or handled with degraded quality — you lose visual search entirely. You can’t switch embedding models after store creation. Delete and recreate.
Mistake 2: Polling without timeout logic. Upload operations are asynchronous. while (!operation.done) without a deadline will hang forever when something fails. Add a 5-minute timeout per file for production scripts.
Mistake 3: Assuming File Search handles structured data. It’s a RAG tool for unstructured documents. If you need SQL-style queries over structured records, that’s a separate pipeline — use BigQuery or Cloud SQL and pipe results into the model context manually.
Mistake 4: Indexing everything into one store then wondering why it’s slow. Retrieval latency scales with store size. Past 20 GB of indexed data, you’ll notice it. Split stores by category, time period, or access pattern — use metadata filters to route queries to the right store.
Mistake 5: Confusing File Search with Vertex AI Search. File Search has no IAM, no VPC-SC, no CMEK, no SLA. It’s a per-project tool in the Gemini API. If you need compliance boundaries or production uptime guarantees, you’re in the wrong product. Move to Vertex AI RAG Engine or Agent Builder before you build anything critical.
What you can’t do with File Search
For completeness, here’s where managed RAG breaks down and you need a custom stack:
- Hybrid search (vector + keyword). File Search uses semantic retrieval only. If your queries contain exact product codes, part numbers, or legal citations, you want BM25 + vector hybrid. File Search can’t do BM25.
- Fine-grained chunking control. You can configure chunk size and overlap, but you can’t implement semantic splitting (detecting topic shifts) or token-aware chunking that respects model context windows precisely.
- Multi-model RAG. File Search only works with Gemini models. You can’t swap in Claude or GPT-4 for the generation step while keeping File Search as the retriever.
- Graph RAG. Documents-as-entities-with-relationships is a different paradigm entirely. File Search is retrieval only — no entity extraction, no relationship mapping, no graph traversal.
The right playbook: start with File Search. It handles 80% of RAG use cases. When you hit a wall on one of these dimensions, you’ll know exactly what you’re buying by building custom.
Pricing that actually makes sense
The economics are simple:
- Indexing: $0.15 per 1M tokens. One-time cost per file.
- Storage: Free.
- Query-time embeddings: Free.
- Retrieved tokens: Charged as standard Gemini input tokens (varies by model).
A practical example: indexing 1,000 average-length PDFs (~5,000 tokens each) costs about $0.75 — total. Querying them 100,000 times costs whatever the input tokens cost at standard Gemini rates. Compare that to hosting a Pinecone pod ($70/month minimum) plus OpenAI embeddings ($0.13/1M tokens for ada-002, which you pay on every query), and the break-even for managed RAG tilts hard toward File Search if you query frequently.
The caveat: you’re locked to Gemini. If your application eventually needs multi-model flexibility, that lock-in has an exit cost. But for a prototype or an internal tool, it’s hard to argue with the math.
FAQ
Can I search across multiple stores in one query?
Yes. Pass an array of store names to fileSearchStoreNames. The model retrieves from all specified stores and synthesizes results, with grounding metadata identifying which store each citation came from.
Are my uploaded files private?
Yes. Access is scoped to your API key. Other projects cannot read your stores. No one at Google is reading your documents. But there is no CMEK, no VPC-SC, and no customer-managed encryption keys — if you need those, switch to Vertex AI.
What file formats work?
150+ formats including PDF, DOCX, Excel, HTML, Markdown, code files, Jupyter notebooks, and PNG/JPEG images (with gemini-embedding-2). Audio and video are not supported.
Do I need to re-index when a document changes?
Yes. The store does not auto-sync. Delete the old file from the store, upload the new version, wait for indexing. There’s no incremental update — you rebuild the embeddings from scratch.
What’s the max store size?
Individual files up to ~100 MB. Total store size depends on your API tier, ranging up to ~1 TB. Practical advice: keep stores under 20 GB for reasonable retrieval latency. Past that, split into multiple stores.
Where to go from here
- Read the official File Search docs for the complete API reference
- Grab the Python quickstart notebook if Python is your thing
- Try the AI Studio demo app to test multimodal search without writing code
- If you need enterprise controls (IAM, VPC, SLA), head to Vertex AI RAG Engine instead