Install
$ agentstack add skill-kgeminic-claude-skills-1-google-gemini-file-search ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Google Gemini File Search Setup
Overview
Google Gemini File Search is a fully managed RAG system. Upload documents (100+ formats: PDF, Word, Excel, code) and query with natural language—automatic chunking, embeddings, semantic search, and citations.
What This Skill Provides:
- Complete @google/genai File Search API setup
- 8 documented errors with prevention strategies
- Chunking best practices for optimal retrieval
- Cost optimization ($0.15/1M tokens indexing, 3x storage multiplier)
- Cloudflare Workers + Next.js integration templates
Prerequisites
1. Google AI API Key
Create an API key at https://aistudio.google.com/apikey
Free Tier Limits:
- 1 GB storage (total across all file search stores)
- 1,500 requests per day
- 1 million tokens per minute
Paid Tier Pricing:
- Indexing: $0.15 per 1M input tokens (one-time)
- Storage: Free (Tier 1: 10 GB, Tier 2: 100 GB, Tier 3: 1 TB)
- Query-time embeddings: Free (retrieved context counts as input tokens)
2. Node.js Environment
Minimum Version: Node.js 18+ (v20+ recommended)
node --version # Should be >=18.0.0
3. Install @google/genai SDK
npm install @google/genai
# or
pnpm add @google/genai
# or
yarn add @google/genai
Current Stable Version: 1.30.0+ (verify with npm view @google/genai version)
⚠️ Important: File Search API requires @google/genai v1.29.0 or later. Earlier versions do not support File Search. The API was added in v1.29.0 (November 5, 2025).
4. TypeScript Configuration (Optional but Recommended)
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
}
}
Common Errors Prevented
This skill prevents 12 common errors encountered when implementing File Search:
Error 1: Document Immutability
Symptom:
Error: Documents cannot be modified after indexing
Cause: Documents are immutable once indexed. There is no PATCH or UPDATE operation.
Prevention: Use the delete+re-upload pattern for updates:
// ❌ WRONG: Trying to update document (no such API)
await ai.fileSearchStores.documents.update({
name: documentName,
customMetadata: { version: '2.0' }
})
// ✅ CORRECT: Delete then re-upload
const docs = await ai.fileSearchStores.documents.list({
parent: fileStore.name
})
const oldDoc = docs.documents.find(d => d.displayName === 'manual.pdf')
if (oldDoc) {
await ai.fileSearchStores.documents.delete({
name: oldDoc.name,
force: true
})
}
await ai.fileSearchStores.uploadToFileSearchStore({
name: fileStore.name,
file: fs.createReadStream('manual-v2.pdf'),
config: { displayName: 'manual.pdf' }
})
Source: https://ai.google.dev/api/file-search/documents
Error 2: Storage Quota Exceeded
Symptom:
Error: Quota exceeded. Expected 1GB limit, but 3.2GB used.
Cause: Storage calculation includes input files + embeddings + metadata. Total storage ≈ 3x input size.
Prevention: Calculate storage before upload:
// ❌ WRONG: Assuming storage = file size
const fileSize = fs.statSync('data.pdf').size // 500 MB
// Expect 500 MB usage → WRONG
// ✅ CORRECT: Account for 3x multiplier
const fileSize = fs.statSync('data.pdf').size // 500 MB
const estimatedStorage = fileSize * 3 // 1.5 GB (embeddings + metadata)
console.log(`Estimated storage: ${estimatedStorage / 1e9} GB`)
// Check if within quota before upload
if (estimatedStorage > 1e9) {
console.warn('⚠️ File may exceed free tier 1 GB limit')
}
Source: https://blog.google/technology/developers/file-search-gemini-api/
Error 3: Incorrect Chunking Configuration
Symptom: Poor retrieval quality, irrelevant results, or context cutoff mid-sentence.
Cause: Default chunking may not be optimal for your content type.
Prevention: Use recommended chunking strategy:
// ❌ WRONG: Using defaults without testing
await ai.fileSearchStores.uploadToFileSearchStore({
name: fileStore.name,
file: fs.createReadStream('docs.pdf')
// Default chunking may be too large or too small
})
// ✅ CORRECT: Configure chunking for precision
await ai.fileSearchStores.uploadToFileSearchStore({
name: fileStore.name,
file: fs.createReadStream('docs.pdf'),
config: {
chunkingConfig: {
whiteSpaceConfig: {
maxTokensPerChunk: 500, // Smaller chunks = more precise retrieval
maxOverlapTokens: 50 // 10% overlap prevents context loss
}
}
}
})
Chunking Guidelines:
- Technical docs/code: 500 tokens/chunk, 50 overlap
- Prose/articles: 800 tokens/chunk, 80 overlap
- Legal/contracts: 300 tokens/chunk, 30 overlap (high precision)
Source: https://www.philschmid.de/gemini-file-search-javascript
Error 4: Metadata Limits Exceeded
Symptom:
Error: Maximum 20 custom metadata key-value pairs allowed
Cause: Each document can have at most 20 metadata fields.
Prevention: Design compact metadata schema:
// ❌ WRONG: Too many metadata fields
await ai.fileSearchStores.uploadToFileSearchStore({
name: fileStore.name,
file: fs.createReadStream('doc.pdf'),
config: {
customMetadata: {
doc_type: 'manual',
version: '1.0',
author: 'John Doe',
department: 'Engineering',
created_date: '2025-01-01',
// ... 18 more fields → Error!
}
}
})
// ✅ CORRECT: Use hierarchical keys or JSON strings
await ai.fileSearchStores.uploadToFileSearchStore({
name: fileStore.name,
file: fs.createReadStream('doc.pdf'),
config: {
customMetadata: {
doc_type: 'manual',
version: '1.0',
author_dept: 'John Doe|Engineering', // Combine related fields
dates: JSON.stringify({ // Or use JSON for complex data
created: '2025-01-01',
updated: '2025-01-15'
})
}
}
})
Source: https://ai.google.dev/api/file-search/documents
Error 5: Indexing Cost Surprises
Symptom: Unexpected bill for $375 after uploading 10 GB of documents.
Cause: Indexing costs are one-time but calculated per input token ($0.15/1M tokens).
Prevention: Estimate costs before indexing:
// ❌ WRONG: No cost estimation
await uploadAllDocuments(fileStore.name, './data') // 10 GB uploaded → $375 surprise
// ✅ CORRECT: Calculate costs upfront
const totalSize = getTotalDirectorySize('./data') // 10 GB
const estimatedTokens = (totalSize / 4) // Rough estimate: 1 token ≈ 4 bytes
const indexingCost = (estimatedTokens / 1e6) * 0.15
console.log(`Estimated indexing cost: $${indexingCost.toFixed(2)}`)
console.log(`Estimated storage: ${(totalSize * 3) / 1e9} GB`)
// Confirm before proceeding
const proceed = await confirm(`Proceed with indexing? Cost: $${indexingCost.toFixed(2)}`)
if (proceed) {
await uploadAllDocuments(fileStore.name, './data')
}
Cost Examples:
- 1 GB text ≈ 250M tokens = $37.50 indexing
- 100 MB PDF ≈ 25M tokens = $3.75 indexing
- 10 MB code ≈ 2.5M tokens = $0.38 indexing
Source: https://ai.google.dev/pricing
Error 6: Not Polling Operation Status
Symptom: Query returns no results immediately after upload, or incomplete indexing.
Cause: File uploads are processed asynchronously. Must poll operation until done: true.
Prevention: Always poll operation status with timeout and fallback:
// ❌ WRONG: Assuming upload is instant
const operation = await ai.fileSearchStores.uploadToFileSearchStore({
name: fileStore.name,
file: fs.createReadStream('large.pdf')
})
// Immediately query → No results!
// ✅ CORRECT: Poll until indexing complete with timeout
const operation = await ai.fileSearchStores.uploadToFileSearchStore({
name: fileStore.name,
file: fs.createReadStream('large.pdf')
})
// Poll with timeout and fallback
const MAX_POLL_TIME = 60000 // 60 seconds
const POLL_INTERVAL = 1000
let elapsed = 0
while (!operation.done && elapsed setTimeout(resolve, POLL_INTERVAL))
elapsed += POLL_INTERVAL
try {
operation = await ai.operations.get({ name: operation.name })
console.log(`Indexing progress: ${operation.metadata?.progress || 'processing...'}`)
} catch (error) {
console.warn('Polling failed, assuming complete:', error)
break
}
}
if (operation.error) {
throw new Error(`Indexing failed: ${operation.error.message}`)
}
// ⚠️ Warning: operations.get() can be unreliable for large files
// If timeout reached, verify document exists manually
if (elapsed >= MAX_POLL_TIME) {
console.warn('Polling timeout - verifying document manually')
const docs = await ai.fileSearchStores.documents.list({ parent: fileStore.name })
const uploaded = docs.documents?.find(d => d.displayName === 'large.pdf')
if (uploaded) {
console.log('✅ Document found despite polling timeout')
} else {
throw new Error('Upload failed - document not found')
}
}
console.log('✅ Indexing complete:', operation.response?.displayName)
Source: https://ai.google.dev/api/file-search/file-search-stores#uploadtofilesearchstore, GitHub Issue #1211
Error 7: Forgetting Force Delete
Symptom:
Error: Cannot delete store with documents. Set force=true.
Cause: Stores with documents require force: true to delete (prevents accidental deletion).
Prevention: Always use force: true when deleting non-empty stores:
// ❌ WRONG: Trying to delete store with documents
await ai.fileSearchStores.delete({
name: fileStore.name
})
// Error: Cannot delete store with documents
// ✅ CORRECT: Use force delete
await ai.fileSearchStores.delete({
name: fileStore.name,
force: true // Deletes store AND all documents
})
// Alternative: Delete documents first
const docs = await ai.fileSearchStores.documents.list({ parent: fileStore.name })
for (const doc of docs.documents || []) {
await ai.fileSearchStores.documents.delete({
name: doc.name,
force: true
})
}
await ai.fileSearchStores.delete({ name: fileStore.name })
Source: https://ai.google.dev/api/file-search/file-search-stores#delete
Error 8: Using Unsupported Models
Symptom:
Error: File Search is only supported for Gemini 3 Pro and Flash models
Cause: File Search requires Gemini 3 Pro or Gemini 3 Flash. Gemini 2.x and 1.5 models are not supported.
Prevention: Always use Gemini 3 models:
// ❌ WRONG: Using Gemini 1.5 model
const response = await ai.models.generateContent({
model: 'gemini-1.5-pro', // Not supported!
contents: 'What is the installation procedure?',
config: {
tools: [{
fileSearch: { fileSearchStoreNames: [fileStore.name] }
}]
}
})
// ✅ CORRECT: Use Gemini 3 models
const response = await ai.models.generateContent({
model: 'gemini-3-flash', // ✅ Supported (fast, cost-effective)
// OR
// model: 'gemini-3-pro', // ✅ Supported (higher quality)
contents: 'What is the installation procedure?',
config: {
tools: [{
fileSearch: { fileSearchStoreNames: [fileStore.name] }
}]
}
})
Source: https://ai.google.dev/gemini-api/docs/file-search
Error 9: displayName Not Preserved for Blob Sources (Fixed v1.34.0+)
Symptom:
groundingChunks[0].title === null // No document source shown
Cause: In @google/genai versions prior to v1.34.0, when uploading files as Blob objects (not file paths), the SDK dropped the displayName and customMetadata configuration fields.
Prevention:
// ✅ CORRECT: Upgrade to v1.34.0+ for automatic fix
npm install @google/genai@latest // v1.34.0+
await ai.fileSearchStores.uploadToFileSearchStore({
name: storeName,
file: new Blob([arrayBuffer], { type: 'application/pdf' }),
config: {
displayName: 'Safety Manual.pdf', // ✅ Now preserved
customMetadata: { version: '1.0' } // ✅ Now preserved
}
})
// ⚠️ WORKAROUND for v1.33.0 and earlier: Use resumable upload
const uploadUrl = `https://generativelanguage.googleapis.com/upload/v1beta/${storeName}:uploadToFileSearchStore?key=${API_KEY}`
// Step 1: Initiate with displayName in body
const initResponse = await fetch(uploadUrl, {
method: 'POST',
headers: {
'X-Goog-Upload-Protocol': 'resumable',
'X-Goog-Upload-Command': 'start',
'X-Goog-Upload-Header-Content-Length': numBytes.toString(),
'X-Goog-Upload-Header-Content-Type': 'application/pdf',
'Content-Type': 'application/json'
},
body: JSON.stringify({
displayName: 'Safety Manual.pdf' // ✅ Works with resumable upload
})
})
// Step 2: Upload file bytes
const uploadUrl2 = initResponse.headers.get('X-Goog-Upload-URL')
await fetch(uploadUrl2, {
method: 'PUT',
headers: {
'Content-Length': numBytes.toString(),
'X-Goog-Upload-Offset': '0',
'X-Goog-Upload-Command': 'upload, finalize',
'Content-Type': 'application/pdf'
},
body: fileBytes
})
Source: GitHub Issue #1078
Error 10: Grounding Metadata Ignored with JSON Response Mode
Symptom:
response.candidates[0].groundingMetadata === undefined
// Even though fileSearch tool is configured
Cause: When using responseMimeType: 'application/json' for structured output, the API ignores the fileSearch tool and returns no grounding metadata, even with Gemini 3 models.
Prevention:
// ❌ WRONG: Structured output overrides grounding
const response = await ai.models.generateContent({
model: 'gemini-3-flash',
contents: 'Summarize guidelines',
config: {
responseMimeType: 'application/json', // Loses grounding
tools: [{ fileSearch: { fileSearchStoreNames: [storeName] } }]
}
})
// ✅ CORRECT: Two-step approach
// Step 1: Get grounded text response
const textResponse = await ai.models.generateContent({
model: 'gemini-3-flash',
contents: 'Summarize guidelines',
config: {
tools: [{ fileSearch: { fileSearchStoreNames: [storeName] } }]
}
})
const grounding = textResponse.candidates[0].groundingMetadata
// Step 2: Convert to structured format in prompt
const jsonResponse = await ai.models.generateContent({
model: 'gemini-3-flash',
contents: `Convert to JSON: ${textResponse.text}
Format:
{
"summary": "...",
"key_points": ["..."]
}`,
config: {
responseMimeType: 'application/json',
responseSchema: {
type: 'object',
properties: {
summary: { type: 'string' },
key_points: { type: 'array', items: { type: 'string' } }
}
}
}
})
// Combine results
const result = {
data: JSON.parse(jsonResponse.text),
sources: grounding.groundingChunks
}
Source: GitHub Issue #829
Error 11: Google Search and File Search Tools Are Mutually Exclusive
Symptom:
Error: "Search as a tool and file search tool are not supported together"
Status: INVALID_ARGUMENT
Cause: The Gemini API does not allow using googleSearch and fileSearch tools in the same request.
Prevention:
// ❌ WRONG: Combining search tools
const response = await ai.models.generateContent({
model: 'gemini-3-flash',
contents: 'What are the latest industry guidelines?',
config: {
tools: [
{ googleSearch: {} },
{ fileSearch: { fileSearchStoreNames: [storeName] } }
]
}
})
// ✅ CORRECT: Use separate specialist agents
async function searchWeb(query: string) {
return ai.models.generateContent({
model: 'gemini-3-flash',
contents: query,
config: { tools: [{ googleSearch: {} }] }
})
}
async function searchDocuments(query: string) {
return ai.models.generateContent({
model: 'gemini-3-flash',
contents: query,
config: { tools: [{ fileSearch: { fileSearchStoreNames: [storeName] } }] }
})
}
// Orchestrate based on query type
const needsWeb = query.includes('late
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [Kgeminic](https://github.com/Kgeminic)
- **Source:** [Kgeminic/claude-skills-1](https://github.com/Kgeminic/claude-skills-1)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.