Gemini FSS Management

Used in this guide:
Next.js 16.0.1
google/genai 1.29.0

Core Rule: How Updates Work in File Search Store

  • When you upload a file into a File Search Store, it becomes a Document in that store and gets indexed (chunked + embedded).
  • Once indexed, that Document is read-only. If your content changes, you don’t edit it in place.
  • The official pattern right now is:

Find the document → delete it → upload the new version (which gets indexed as a fresh document).


Current Best Practice Flow to Update FSS

Delete and reupload the document (best practice once things are “real”). This is what you’ll want for your actual personal site where FSS_ID is stable.

  • Keep the same store (FFS_ID never changes).
  • Update document by:
  • List documents in the store.
  • Find the one with the displayName you're looking for.
  • Delete that document
  • Upload the updated local file to the same store with the same displayName

Avoid:

Upload new versions without deleting old ones

  • You’ll end up with multiple docs that say different things.
  • Gemini’s retrieval might pull chunks from both, so it could answer with outdated info.

Assume Files API edits will auto-update File Search

  • When you import into File Search, the indexed data is stored in its own store and doesn’t automatically track later changes to a File elsewhere.



1. Delete Document

I have already cover this in Gemini AI FSS so be sure to check it out for more context.

root/scripts/deleteDocument.ts
import { GoogleGenAI } from "@google/genai";
import dotenv from "dotenv";
dotenv.config({ path: ".env.local" });
 
const DOCUMENT_TO_DELETE = process.env.FSS_DOC_ID; 
 
const genAI = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
 
async function deleteSpecificDocument() {
    
    if (!DOCUMENT_TO_DELETE) {
        console.error("ERROR: FSS_DOC_ID environment variable is not set.");
        console.error("Please set it to the document's full unique ID.");
        return;
    }
 
    console.log(`--- Attempting to Delete Specific Document ---`);
    console.log(`Targeting Name: ${DOCUMENT_TO_DELETE}`);
 
    try {
        await genAI.fileSearchStores.documents.delete({
            name: DOCUMENT_TO_DELETE,
            config: { force: true },
        });
 
        console.log(`Successfully deleted Document: ${DOCUMENT_TO_DELETE}`);
        
    } catch (error) {
        console.error(`Failed to delete document ${DOCUMENT_TO_DELETE}.`, error);
        console.error("Common reason: Document does not exist or API key permissions are insufficient.");
    }
}
 
deleteSpecificDocument();

Run the script:

npm run run-script -- scripts/deleteDocument.ts


2. Upload New Document

root/scripts/uploadDocument.ts
import { GoogleGenAI } from "@google/genai";
import fs from "fs";
import dotenv from "dotenv";
dotenv.config({ path: ".env.local" });
 
const STORE_ID = process.env.FSS_ID;
const genAI = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
 
async function uploadDocumentToStore() {
    try {
 
        if (!STORE_ID) {
            console.error("ERROR: FSS_ID environment variable is not set.");
            console.error("Please set it in your .env.local file to the store's unique ID.");
            return;
        }
 
        console.log("--- Uploading Document to File Search Store ---");
        console.log(`Target Store ID: ${STORE_ID}`);
 
        const filePath = './ffs-files/JakkritBio.txt';
 
        if (fs.existsSync(filePath)) {
            console.log(`Uploading file '${filePath}' and indexing it...`);
            const res = await genAI.fileSearchStores.uploadToFileSearchStore({
                fileSearchStoreName: STORE_ID, 
                file: filePath,
            });
            console.log("File uploaded and indexed successfully.")
            console.log(`Operation Name (to track indexing status): ${res.name}`);
            console.log("Indexing is now running in the background. It may take a few minutes.");
        } else {
            console.error(`ERROR: File not found at path: ${filePath}`);
        }
 
    } catch (error) {
        console.error("Setup failed. An API error occurred.", error);
    }
}
 
uploadDocumentToStore();

Run the script:

npm run run-script -- scripts/uploadDocument.ts

2.1 Better upload new doc with custom metadata

root/scripts/uploadDocument.ts
import { GoogleGenAI } from "@google/genai";
import fs from "fs";
import dotenv from "dotenv";
dotenv.config({ path: ".env.local" });
 
const FILE_NAME = "frontracer.md";
const MIME_TYPE = "text/markdown";
const CUSTOM_METADATA = [
    { key: "doc_type", stringValue: "portfolio_project" },
    { key: "project", stringValue: "FrontRacer" },
    { 
        key: "tags", 
        stringListValue: { 
            values: [
                "frontracer",
                "social voting platform",
                "next.js 15",
                "typescript",
                "payload cms",
                "postgresql",
                "cloudflare r2",
                "pairing algorithm",
                "vote session tracking",
                "stats aggregation"
            ]
        }
    },
    { key: "author", stringValue: "Jakkrit Turner" },
];
 
const STORE_ID = process.env.FSS_ID;
const genAI = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
 
async function uploadDocumentToStore() {
    try {
 
        if (!STORE_ID) {
            console.error("ERROR: FSS_ID environment variable is not set.");
            console.error("Please set it in your .env.local file to the store's unique ID.");
            return;
        }
 
        console.log("--- Uploading Document to File Search Store ---");
        console.log(`Target Store ID: ${STORE_ID}`);
 
        const filePath = `./ffs-files/${FILE_NAME}`;
 
        if (fs.existsSync(filePath)) {
            console.log(`Uploading file '${filePath}' and indexing it...`);
            const res = await genAI.fileSearchStores.uploadToFileSearchStore({
                fileSearchStoreName: STORE_ID, 
                file: filePath,
                config: {
                    mimeType: MIME_TYPE,
                    displayName: FILE_NAME,
                    customMetadata: CUSTOM_METADATA,
                }
            });
            console.log("File uploaded and indexed successfully.")
            console.log(`Operation Name (to track indexing status): ${res.name}`);
            console.log("Indexing is now running in the background. It may take a few minutes.");
        } else {
            console.error(`ERROR: File not found at path: ${filePath}`);
        }
 
    } catch (error) {
        console.error("Setup failed. An API error occurred.", error);
    }
}
 
uploadDocumentToStore();

Run the script:

npm run run-script -- scripts/uploadDocument.ts


Extra - Route & Page

app/api/gemini/jakkrit-bio/route.ts
import { GoogleGenAI } from "@google/genai";
import { NextRequest, NextResponse } from "next/server";
 
const STORE_ID_ENV = process.env.FSS_ID;
const PROJECT_ID_ENV = process.env.GOOGLE_CLOUD_PROJECT;
 
if (!STORE_ID_ENV || !PROJECT_ID_ENV) {
    throw new Error("Error: FSS_ID or GOOGLE_CLOUD_PROJECT environment variable is not set.");
}
 
const STORE_ID: string = STORE_ID_ENV;
const PROJECT_ID: string = PROJECT_ID_ENV;
 
const genAI = new GoogleGenAI({ 
    // apiKey: process.env.GEMINI_API_KEY,
    project: PROJECT_ID,
});
 
export async function POST(req: NextRequest) {
    try {
        const { prompt } = await req.json() as { prompt: string };
        if (!prompt) {
            return NextResponse.json({ error: "Missing 'prompt' in request body"}, { status: 400 });
        }
 
        const config = {
            tools: [
                {
                    fileSearch: {
                        fileSearchStoreNames: [STORE_ID],
                    }
                }
            ]
        };
 
        console.log(`Received prompt: "${prompt}"`);
        
        const res = await genAI.models.generateContent({
            model: "gemini-2.5-flash",
            contents: prompt,
            config: config,
        });
 
        const output = res.text;
        console.log("Response from Gemini: ", output);
        return NextResponse.json({ output });
    } catch (error) {
        console.error("Gemini API Error:", (error as Error).message);
        return NextResponse.json({ error: "Failed to generate content."}, { status: 500 });
    }
}
app/(pages)/gemini-rag/page.tsx
"use client"
import { useState } from "react"
 
export default function GeminiRagPage() {
 
    const [prompt, setPrompt] = useState("");
    const [response, setResponse] = useState('Type a prompt and click "Generate" to ask Gemini AI.');
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState("");
 
    const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        setError("");
 
        if (!prompt.trim()) {
            setError("Please enter a prompt first.");
            return;
        }
 
        setIsLoading(true);
 
        try {
            const res = await fetch('/api/gemini/jakkrit-bio', {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({ prompt }),
            });
 
            const data = await res.json();
 
            if (!res.ok || data.error) {
                setError(data.error || `Error: Status ${res.status}`);
                setResponse("Failed to get a response from Gemini.");
            };
 
            setResponse(data.output);
 
        } catch (error) {
            console.error("Fetch error: ", error);
            setError("Error: A network error occurred. Check your connection or server status.");
            setResponse("Failed to connect to Gemini.");
        } finally {
            setIsLoading(false);
        }
    }
 
    return (
        <div className="flex flex-col gap-4 w-full max-w-2xl p-8">
            <div className="">
                <h1 className="font-bold text-md">Gemini RAG Example</h1>
                <p className="text-sm text-gray-400">This Gemini uses specific indexed data from File Search Store RAG. Please ask about Jakkrit Turner in this specific case.</p>
            </div>
            <form onSubmit={handleSubmit}>
                <textarea
                    placeholder="Ask Gemini anything..."
                    value={prompt}
                    rows={4}
                    disabled={isLoading}
                    onChange={(e) => setPrompt(e.target.value)}
                    className="bg-gray-700 rounded-lg w-full p-4 text-sm"
                />
                <div className="flex w-full justify-end">
                    <button
                        type="submit"
                        disabled={isLoading}
                        className="bg-sky-600 py-2 px-4 rounded-md text-sm cursor-pointer"
                    >
                        {isLoading ? "Generating response...": "Submit"}
                    </button>
                </div>
            </form>
 
            {error && (
                <div className="text-amber-500">
                    <p>{error}</p>
                </div>
            )}
 
            <div className="space-y-4 text-sm">
                <h2>Gemini AI Response</h2>
                {isLoading && !response.startsWith("Type a prompt") ? (
                    <p>Thinking...</p>
                ) : (
                    <div className="border border-gray-700 rounded-lg p-4 leading-8">
                        <p>{response}</p>
                    </div>
                )}
            </div>
 
        </div>
    )
}

At this point everything should works perfectly fine. However, if you get undefined response from Gemin API it could mean that it hasn't finished indexing the data. To check the status of the documents, please refer to listDocuments.

You can also Restart Extension Host with Ctrl + Shift + P and type restart

JKT

Stay focused, and the rest will follow

©Jakkrit Turner. All rights reserved