Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Nano Banana

Generate stunning AI images directly in your Convex backend with persistent storage, real-time status tracking, and a clean TypeScript API.

Upstream source
git clone https://github.com/dperussina/convex-nano-banana.git
cd convex-nano-banana

This upstream package has not been adapted or qualified for Bijection. Read the original documentation before adopting it.

Description

Generate stunning AI images directly in your Convex backend with persistent storage, real-time status tracking, and a clean TypeScript API.

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available to evaluate

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: dperussina/convex-nano-banana. License: See repository. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

AI Image Generation Component for Convex powered by Gemini

Upstream overview

From the upstream package documentation.

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

From the upstream package documentation.

  • 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

From the upstream package documentation.

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.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate Nano Banana with Bijection.

Read: https://bijection.com/components/convex-nano-banana/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from dperussina/convex-nano-banana, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

🍌 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

npm install convex-nano-banana

🚀 Quick Start

1. Add the component to your Convex app

Create or update convex/convex.config.ts:

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:

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

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<string | null>(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 (
    <div>
      <input
        value={prompt}
        onChange={(e) => setPrompt(e.target.value)}
        placeholder="Describe your image..."
      />
      <button onClick={handleGenerate} disabled={!prompt}>
        Generate
      </button>

      {generation?.status === "generating" && (
        <p>🎨 Generating your image...</p>
      )}

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

      {generation?.status === "failed" && (
        <p>❌ Error: {generation.error}</p>
      )}
    </div>
  );
}

📚 API Reference

NanoBanana Class

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<string>

Generate a new image from a text prompt.

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<string>

Edit images using a text prompt and input images.

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<string>

Edit using previously stored images (by storage ID).

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<Generation | null>

Get a generation by ID. This is a reactive query - in React, it automatically updates when the status changes.

const generation = await nanoBanana.get(ctx, {
  generationId: "abc123",
});
// Returns: { status, imageUrl, prompt, durationMs, ... }

list(ctx, { userId, limit?, cursor? }) → Promise<ListResult>

List generations for a user with pagination.

const { generations, hasMore, cursor } = await nanoBanana.list(ctx, {
  userId: "user_123",
  limit: 20,
});

listByStatus(ctx, { status, limit? }) → Promise<Generation[]>

List generations by status.

const failed = await nanoBanana.listByStatus(ctx, {
  status: "failed",
  limit: 10,
});

delete(ctx, { generationId }) → Promise<void>

Delete a generation and its stored images.

await nanoBanana.delete(ctx, { generationId: "abc123" });

deleteAllForUser(ctx, { userId }) → Promise<{ deleted: number }>

Delete all generations for a user.

const { deleted } = await nanoBanana.deleteAllForUser(ctx, {
  userId: "user_123",
});

🎯 Supported Models

ModelDescriptionResolution
gemini-2.5-flash-imageFast generation (default)1024px
gemini-3-pro-image-previewHigh quality, up to 4K4096px

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

StatusDescription
pendingCreated but not started
generatingCurrently generating
completeSuccessfully generated
failedGeneration failed (check error field)

🔐 Security Best Practices

Never expose your Gemini API key to clients!

Option 1: Environment Variable (Recommended)

# Set in Convex dashboard or .env.local
npx convex env set GEMINI_API_KEY your-key-here
// 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:

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:

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

# 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:


Made with 🍌 by the Convex community