# Nano Banana Generate stunning AI images directly in your Convex backend with persistent storage, real-time status tracking, and a clean TypeScript API. This is an upstream package. It has not been adapted or qualified for Bijection. - Package: convex-nano-banana - Source: https://github.com/dperussina/convex-nano-banana - npm: https://www.npmjs.com/package/convex-nano-banana - Directory: https://bijection.com/components/convex-nano-banana Read the upstream README for setup, license, and compatibility before using it. ## Upstream package documentation Recorded 2026-10-23. This describes convex-nano-banana; check the Bijection adaptation separately. Nano Banana integrates Google Gemini's AI image generation directly into your Convex backend with persistent file storage and real-time status tracking. It provides a TypeScript API for text-to-image generation and image editing, handling the complete lifecycle from generation through storage in Convex. The component includes reactive queries that automatically update as generation progresses, plus user management and generation history tracking. ### Use cases • **Content creation apps** - Build social media platforms or creative tools where users generate custom images from text prompts with real-time progress updates • **E-commerce product visualization** - Generate product mockups or lifestyle images by editing existing product photos with text instructions • **Multi-tenant SaaS applications** - Implement AI image generation features where each customer uses their own Gemini API key for billing isolation • **Content moderation workflows** - Generate test images for training moderation systems or create placeholder content with specific characteristics • **Creative collaboration platforms** - Allow users to iterate on images through text-based editing while maintaining version history and persistent storage ### How it works The component wraps Google Gemini's image generation API in a Convex-native interface, automatically handling file storage and status tracking. You initialize the `NanoBanana` class with your component reference and optional default configuration, then call methods like `generate()` for text-to-image or `edit()` for image modification. Generation requests create database records with status tracking (pending, generating, complete, failed) that update reactively through Convex queries. Images are automatically stored in Convex file storage and linked to generation records. The `get()` method provides real-time status updates that trigger React re-renders as generation progresses. The component supports multiple Gemini models and accepts configuration options like aspect ratios and image sizes. API keys can be set globally during initialization or passed per-request for multi-tenant scenarios. All generations are tied to user IDs for access control and include timing metrics and error handling. ### Upstream README # 🍌 Nano Banana **AI Image Generation Component for Convex, powered by Google Gemini** Generate stunning AI images directly in your Convex backend with persistent storage, real-time status tracking, and a clean TypeScript API. Bring your own Gemini API key. ## ✨ Features - 🎨 **Text-to-Image Generation** - Create images from text prompts using Gemini's native image generation - ✏️ **Image Editing** - Edit existing images with text instructions - 📦 **Persistent Storage** - Images automatically stored in Convex file storage - ⚡ **Real-time Status** - Reactive queries update as generation progresses - 🔮 **Future-proof** - Accepts any model name for upcoming Gemini models - 🔐 **BYO API Key** - Users provide their own Gemini API key (secure, server-side only) - 📊 **Generation History** - Track all generations with timing metrics ## 📦 Installation ```bash npm install convex-nano-banana ``` ## 🚀 Quick Start ### 1. Add the component to your Convex app Create or update `convex/convex.config.ts`: ```typescript import { defineApp } from "convex/server"; import nanoBanana from "convex-nano-banana/convex.config"; const app = defineApp(); app.use(nanoBanana); export default app; ``` ### 2. Create your image generation functions Create `convex/images.ts`: ```typescript import { v } from "convex/values"; import { action, query, mutation } from "./_generated/server"; import { components } from "./_generated/api"; import { NanoBanana } from "convex-nano-banana"; // Initialize the client const nanoBanana = new NanoBanana(components.nanoBanana, { // Optional: Set a default API key (can also be passed per-request) // GEMINI_API_KEY: process.env.GEMINI_API_KEY, }); // Generate an image export const generateImage = action({ args: { prompt: v.string(), apiKey: v.string(), aspectRatio: v.optional(v.string()), }, handler: async (ctx, args) => { return await nanoBanana.generate(ctx, { userId: "user_123", // Replace with actual user ID from auth prompt: args.prompt, aspectRatio: args.aspectRatio, GEMINI_API_KEY: args.apiKey, }); }, }); // Get generation status (reactive!) export const getGeneration = query({ args: { generationId: v.string() }, handler: async (ctx, args) => { return await nanoBanana.get(ctx, args); }, }); // List user's generations export const listGenerations = query({ args: { userId: v.string() }, handler: async (ctx, args) => { return await nanoBanana.list(ctx, args); }, }); // Delete a generation export const deleteGeneration = mutation({ args: { generationId: v.string() }, handler: async (ctx, args) => { return await nanoBanana.delete(ctx, args); }, }); ``` ### 3. Use in your React app ```tsx import { useAction, useQuery } from "convex/react"; import { api } from "../convex/_generated/api"; import { useState } from "react"; function ImageGenerator() { const [prompt, setPrompt] = useState(""); const [generationId, setGenerationId] = useState(null); const generate = useAction(api.images.generateImage); const generation = useQuery( api.images.getGeneration, generationId ? { generationId } : "skip" ); const handleGenerate = async () => { const id = await generate({ prompt, apiKey: "YOUR_GEMINI_API_KEY", // In production, handle securely! }); setGenerationId(id); }; return (
setPrompt(e.target.value)} placeholder="Describe your image..." /> {generation?.status === "generating" && (

🎨 Generating your image...

)} {generation?.status === "complete" && generation.imageUrl && ( )} {generation?.status === "failed" && (

❌ Error: {generation.error}

)}
); } ``` ## 📚 API Reference ### NanoBanana Class ```typescript import { NanoBanana } from "convex-nano-banana"; const nanoBanana = new NanoBanana(components.nanoBanana, { GEMINI_API_KEY: "optional-default-key", defaultModel: "gemini-2.5-flash-image", // Optional }); ``` ### Methods #### `generate(ctx, options)` → `Promise` Generate a new image from a text prompt. ```typescript const generationId = await nanoBanana.generate(ctx, { userId: "user_123", // Required: User identifier prompt: "A futuristic city", // Required: Image description model: "gemini-2.5-flash-image", // Optional: Model to use aspectRatio: "16:9", // Optional: Aspect ratio imageSize: "2K", // Optional: For Pro models GEMINI_API_KEY: "...", // Optional: Override API key }); ``` #### `edit(ctx, options)` → `Promise` Edit images using a text prompt and input images. ```typescript const generationId = await nanoBanana.edit(ctx, { userId: "user_123", prompt: "Add a rainbow to the sky", inputImages: [{ base64: "...", mimeType: "image/png", }], GEMINI_API_KEY: "...", }); ``` #### `editFromStorage(ctx, options)` → `Promise` Edit using previously stored images (by storage ID). ```typescript const generationId = await nanoBanana.editFromStorage(ctx, { userId: "user_123", prompt: "Make it more colorful", inputStorageIds: ["storage_id_here"], GEMINI_API_KEY: "...", }); ``` #### `get(ctx, { generationId })` → `Promise` Get a generation by ID. **This is a reactive query** - in React, it automatically updates when the status changes. ```typescript const generation = await nanoBanana.get(ctx, { generationId: "abc123", }); // Returns: { status, imageUrl, prompt, durationMs, ... } ``` #### `list(ctx, { userId, limit?, cursor? })` → `Promise` List generations for a user with pagination. ```typescript const { generations, hasMore, cursor } = await nanoBanana.list(ctx, { userId: "user_123", limit: 20, }); ``` #### `listByStatus(ctx, { status, limit? })` → `Promise` List generations by status. ```typescript const failed = await nanoBanana.listByStatus(ctx, { status: "failed", limit: 10, }); ``` #### `delete(ctx, { generationId })` → `Promise` Delete a generation and its stored images. ```typescript await nanoBanana.delete(ctx, { generationId: "abc123" }); ``` #### `deleteAllForUser(ctx, { userId })` → `Promise<{ deleted: number }>` Delete all generations for a user. ```typescript const { deleted } = await nanoBanana.deleteAllForUser(ctx, { userId: "user_123", }); ``` ## 🎯 Supported Models | Model | Description | Resolution | |-------|-------------|------------| | `gemini-2.5-flash-image` | Fast generation (default) | 1024px | | `gemini-3-pro-image-preview` | High quality, up to 4K | 4096px | **Future-proof**: Pass any model name string - when Google releases new models, they'll work immediately! ## 📐 Aspect Ratios Supported values: `"1:1"`, `"2:3"`, `"3:2"`, `"3:4"`, `"4:3"`, `"4:5"`, `"5:4"`, `"9:16"`, `"16:9"`, `"21:9"` ## 📏 Image Sizes (Pro Model Only) For `gemini-3-pro-image-preview`: `"1K"`, `"2K"`, `"4K"` ## 📊 Generation Status | Status | Description | |--------|-------------| | `pending` | Created but not started | | `generating` | Currently generating | | `complete` | Successfully generated | | `failed` | Generation failed (check `error` field) | ## 🔐 Security Best Practices **Never expose your Gemini API key to clients!** ### Option 1: Environment Variable (Recommended) ```bash # Set in Convex dashboard or .env.local npx convex env set GEMINI_API_KEY your-key-here ``` ```typescript // In your action const nanoBanana = new NanoBanana(components.nanoBanana, { GEMINI_API_KEY: process.env.GEMINI_API_KEY, }); ``` ### Option 2: Per-User Keys (Multi-tenant) Store keys securely and pass them server-side: ```typescript export const generateImage = action({ args: { prompt: v.string() }, handler: async (ctx, args) => { const user = await getAuthenticatedUser(ctx); const apiKey = await getUserApiKey(ctx, user.id); return await nanoBanana.generate(ctx, { userId: user.id, prompt: args.prompt, GEMINI_API_KEY: apiKey, }); }, }); ``` ## 🧪 Testing For testing with `convex-test`: ```typescript import nanoBananaTest from "convex-nano-banana/test"; import { convexTest } from "convex-test"; function initConvexTest() { const t = convexTest(); nanoBananaTest.register(t); return t; } test("Generate image", async () => { const t = initConvexTest(); // ... your tests }); ``` ## 🔧 Development ```bash # Install dependencies npm install # Build the package npm run build # Run tests npm test # Type check npm run typecheck ``` ## 📄 License Apache-2.0 ## 🙏 Credits Built with: - [Convex](https://convex.dev) - The backend platform - [Google Gemini](https://ai.google.dev/) - AI image generation - [@google/genai](https://www.npmjs.com/package/@google/genai) - Gemini SDK --- Made with 🍌 by the Convex community