Guide · Google

Migrate @google/generative-ai to @google/genai (JS/TS)

@google/genai replaces @google/generative-ai. Model objects from getGenerativeModel() give way to one GoogleGenAI client, and response.text() becomes the property response.text.

Google's old JavaScript SDK now lives in a repository named deprecated-generative-ai-js, and its README says all support, including bug fixes, ended on November 30, 2025. The new SDK covers the Gemini Developer API and Google Cloud from one package, and the Gemini docs only show it. Most changes are mechanical, but two of them fail at runtime rather than at compile time in plain JavaScript: calling text() on what is now a string, and iterating result.stream, which no longer exists. This guide follows Google's migration guide and the js-genai README, linked at the end.

What changed

  • new GoogleGenerativeAI(key) becomes new GoogleGenAI({ apiKey }).
  • genAI.getGenerativeModel({ model }) is gone. Pass model to each call on ai.models.
  • model.generateContent(prompt) becomes ai.models.generateContent({ model, contents }), and the result is the response itself, not { response }.
  • response.text() is now a property: response.text.
  • Streams are iterated directly: for await (const chunk of stream) instead of result.stream.
  • generationConfig and safetySettings move into a per-request config object.
  • GoogleAIFileManager and GoogleAICacheManager from @google/generative-ai/server are replaced by ai.files and ai.caches.

Migration steps

  1. Swap the package

    npm uninstall @google/generative-ai
    npm install @google/genai

    The README lists Node.js 20 or later as a prerequisite and warns that SDK version 3.0.0 will require Node.js 22. It also says not to ship API keys in browser code; keep calls on the server.

  2. Create a client and call ai.models

    Before

    import { GoogleGenerativeAI } from "@google/generative-ai";
    
    const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
    const model = genAI.getGenerativeModel({ model: "gemini-flash-latest" });
    
    const result = await model.generateContent("Tell me a story in 300 words");
    console.log(result.response.text());

    After

    import { GoogleGenAI } from "@google/genai";
    
    const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
    
    const response = await ai.models.generateContent({
      model: "gemini-flash-latest",
      contents: "Tell me a story in 300 words",
    });
    console.log(response.text);

    Google's code generation notes also list the type renames: GenerateContentResult becomes GenerateContentResponse and GenerateContentRequest becomes GenerateContentParameters.

  3. Move generationConfig into config

    Before

    const model = genAI.getGenerativeModel({
      model: "gemini-flash-latest",
      generationConfig: { stopSequences: ["x"], maxOutputTokens: 20, temperature: 1.0 },
    });
    const result = await model.generateContent("Tell me a story about a magic backpack.");

    After

    const response = await ai.models.generateContent({
      model: "gemini-flash-latest",
      contents: "Tell me a story about a magic backpack.",
      config: {
        stopSequences: ["x"],
        maxOutputTokens: 20,
        temperature: 1.0,
        systemInstruction: "You are a storyteller for young children",
      },
    });

    Settings that used to live on the model object are now per request. safetySettings goes in the same config, with string categories such as "HARM_CATEGORY_HARASSMENT". If several call sites shared one configured model, keep a shared config object and spread it into each call.

  4. Iterate streams directly

    Before

    const result = await model.generateContentStream(prompt);
    for await (const chunk of result.stream) {
      process.stdout.write(chunk.text());
    }

    After

    const stream = await ai.models.generateContentStream({
      model: "gemini-flash-latest",
      contents: prompt,
    });
    for await (const chunk of stream) {
      process.stdout.write(chunk.text ?? "");
    }
  5. Create chats from ai.chats

    Before

    const chat = model.startChat({
      history: [
        { role: "user", parts: [{ text: "Hello" }] },
        { role: "model", parts: [{ text: "Great to meet you. What would you like to know?" }] },
      ],
    });
    const result = await chat.sendMessage("I have 2 dogs in my house.");
    console.log(result.response.text());

    After

    const chat = ai.chats.create({
      model: "gemini-flash-latest",
      history: [
        { role: "user", parts: [{ text: "Hello" }] },
        { role: "model", parts: [{ text: "Great to meet you. What would you like to know?" }] },
      ],
    });
    const response = await chat.sendMessage({ message: "I have 2 dogs in my house." });
    console.log(response.text);

    sendMessage takes an object with a message field instead of a bare string. For streaming chats use chat.sendMessageStream({ message }) and iterate the result with for await.

  6. Replace the server helpers and other model methods

    • new GoogleAIFileManager(key).uploadFile(...) to ai.files.upload({ file })
    • new GoogleAICacheManager(key).create(...) to ai.caches.create(...)
    • model.countTokens(prompt) to ai.models.countTokens({ model, contents: prompt }); totalTokens is still on the result
    • model.embedContent(text) to ai.models.embedContent({ model: "gemini-embedding-001", contents: text }), which returns embeddings (plural)
    • result.response.usageMetadata to response.usageMetadata
  7. Catch ApiError

    import { ApiError } from "@google/genai";
    
    try {
      await ai.models.generateContent({ model: "non-existent-model", contents: "Hi" });
    } catch (e) {
      if (e instanceof ApiError) console.error(e.status, e.message);
      else throw e;
    }

    ApiError extends Error and adds status, the HTTP code.

How to find every affected call

Use ast-grep to match the syntax tree rather than text, so calls split across lines are found. $GENAI and $MODEL match whatever you named the instances:

sg run --pattern 'new GoogleGenerativeAI($$$ARGS)' --lang ts
sg run --pattern '$GENAI.getGenerativeModel($$$ARGS)' --lang ts
sg run --pattern '$RESULT.response.text()' --lang ts
sg run --pattern '$RESULT.stream' --lang ts
sg run --pattern '$MODEL.startChat($$$ARGS)' --lang ts
sg run --pattern 'new GoogleAIFileManager($$$ARGS)' --lang ts

Repeat with --lang tsx and --lang js. $RESULT.stream also matches unrelated objects with a stream property, so review each hit. chunk.text() inside old stream loops is a separate search: sg run --pattern '$CHUNK.text()' --lang ts.

Testing the change

  • Run tsc --noEmit. Leftover .text() calls, result.response reads and old type names show up as type errors in TypeScript files.
  • Update mocks. A stub of getGenerativeModel that returns { response: { text: () => "..." } } keeps passing while production breaks; return the new shape with text as a string.
  • Run one streamed request and one chat in staging, since both changed how you iterate and how you send a message.
  • Trigger an error with a bad model name and confirm your handler sees an ApiError with a numeric status.

Automating it

Google does not publish a codemod. It publishes codegen_instructions.md, a list of incorrect and correct patterns meant to be pasted into an AI coding tool so it writes new-SDK code. That helps a model avoid old calls, but nothing checks that the result builds.

Versyka finds the call sites deterministically with ast-grep, rewrites them together with the code that depends on them (the .text() reads, stream loops, mocks), then runs your repository's install, typecheck, build and tests in a disposable sandbox. A pull request is opened only when those pass.

Was this guide helpful?

Check your own repository

Paste your package.json, requirements.txt or pyproject.toml into the free upgrade check to see which API SDKs are behind, with no account. Or let Versyka run this migration on one repository and open a pull request that has already passed your tests.

Sources