Skip to main content

Vibe Storage

GenKit includes a pluggable storage system for persisting user-generated components. Use the built-in Prisma adapter, or create your own for any database.

Quick Start

import { createVibeApi, createPrismaStorage } from 'genkit'
import { prisma } from '@/lib/prisma'

const vibe = createVibeApi({
  storage: createPrismaStorage(prisma)
})

// Save a component
const saved = await vibe.save({
  userId: 'user-123',
  name: 'Sales Dashboard',
  description: 'Pipeline visualization',
  code: generatedCode
})

// Load a component
const component = await vibe.load(saved.id)

// List user's components
const components = await vibe.list({ userId: 'user-123' })

// Delete a component
await vibe.delete(saved.id)

Prisma Setup

1. Add the Model

Add this to your schema.prisma:
model VibeComponent {
  id          String   @id @default(cuid())
  userId      String
  name        String
  description String?
  code        String   @db.Text
  compiled    String?  @db.Text
  version     Int      @default(1)
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  @@index([userId])
}

2. Run Migration

npx prisma migrate dev --name add-vibe-components

3. Create the API

// lib/vibe.ts
import { createVibeApi, createPrismaStorage } from 'genkit'
import { prisma } from '@/lib/prisma'

export const vibe = createVibeApi({
  storage: createPrismaStorage(prisma)
})

API Reference

vibe.save(input)

Save a new component.
const component = await vibe.save({
  userId: 'user-123',
  name: 'Stats Card',
  description: 'Displays key metrics',
  code: `export default function StatsCard({ data }) { ... }`
})

// Returns:
{
  id: 'cuid123...',
  userId: 'user-123',
  name: 'Stats Card',
  description: 'Displays key metrics',
  code: '...',
  version: 1,
  createdAt: Date,
  updatedAt: Date
}

vibe.load(id)

Load a component by ID.
const component = await vibe.load('cuid123...')

if (!component) {
  throw new Error('Component not found')
}

vibe.list(options)

List components for a user.
const components = await vibe.list({
  userId: 'user-123',
  limit: 20,    // default: 50
  offset: 0     // for pagination
})

vibe.delete(id)

Delete a component.
await vibe.delete('cuid123...')

Memory Storage (Testing)

For development and testing, use in-memory storage:
import { createVibeApi, createMemoryStorage } from 'genkit'

const vibe = createVibeApi({
  storage: createMemoryStorage()
})

// Works the same as Prisma storage
await vibe.save({ ... })
Memory storage is cleared when the server restarts. Only use for development.

Custom Storage Adapter

Implement the VibeStorage interface for custom backends:
import type { VibeStorage, VibeComponent, VibeSaveInput, VibeListOptions } from 'genkit'

export function createCustomStorage(db: YourDatabase): VibeStorage {
  return {
    async save(input: VibeSaveInput): Promise<VibeComponent> {
      const id = generateId()
      const now = new Date()

      const component = {
        id,
        ...input,
        version: 1,
        createdAt: now,
        updatedAt: now
      }

      await db.components.insert(component)
      return component
    },

    async load(id: string): Promise<VibeComponent | null> {
      return db.components.findById(id)
    },

    async list(options: VibeListOptions): Promise<VibeComponent[]> {
      return db.components.find({
        where: { userId: options.userId },
        orderBy: { updatedAt: 'desc' },
        limit: options.limit ?? 50,
        offset: options.offset ?? 0
      })
    },

    async delete(id: string): Promise<void> {
      await db.components.deleteById(id)
    },

    async update(id: string, data: Partial<VibeSaveInput>): Promise<VibeComponent> {
      const existing = await this.load(id)
      if (!existing) throw new Error('Not found')

      const updated = {
        ...existing,
        ...data,
        version: existing.version + 1,
        updatedAt: new Date()
      }

      await db.components.update(id, updated)
      return updated
    }
  }
}

Drizzle Example

import { eq, desc } from 'drizzle-orm'
import { vibeComponents } from './schema'

export function createDrizzleStorage(db: DrizzleDB): VibeStorage {
  return {
    async save(input) {
      const [component] = await db
        .insert(vibeComponents)
        .values({
          userId: input.userId,
          name: input.name,
          description: input.description,
          code: input.code
        })
        .returning()

      return component
    },

    async load(id) {
      const [component] = await db
        .select()
        .from(vibeComponents)
        .where(eq(vibeComponents.id, id))
        .limit(1)

      return component ?? null
    },

    async list(options) {
      return db
        .select()
        .from(vibeComponents)
        .where(eq(vibeComponents.userId, options.userId))
        .orderBy(desc(vibeComponents.updatedAt))
        .limit(options.limit ?? 50)
        .offset(options.offset ?? 0)
    },

    async delete(id) {
      await db
        .delete(vibeComponents)
        .where(eq(vibeComponents.id, id))
    },

    async update(id, data) {
      const [updated] = await db
        .update(vibeComponents)
        .set({
          name: data.name,
          description: data.description,
          code: data.code,
          updatedAt: new Date()
        })
        .where(eq(vibeComponents.id, id))
        .returning()

      return updated
    }
  }
}

API Routes

Save Component

// app/api/vibe/save/route.ts
import { vibe } from '@/lib/vibe'
import { auth } from '@/lib/auth'

export async function POST(request: Request) {
  const session = await auth()
  if (!session) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { name, description, code } = await request.json()

  const component = await vibe.save({
    userId: session.user.id,
    name,
    description,
    code
  })

  return Response.json({ component })
}

List Components

// app/api/vibe/list/route.ts
import { vibe } from '@/lib/vibe'
import { auth } from '@/lib/auth'

export async function GET(request: Request) {
  const session = await auth()
  if (!session) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { searchParams } = new URL(request.url)
  const limit = parseInt(searchParams.get('limit') ?? '50')
  const offset = parseInt(searchParams.get('offset') ?? '0')

  const components = await vibe.list({
    userId: session.user.id,
    limit,
    offset
  })

  return Response.json({ components })
}

Load Component

// app/api/vibe/[id]/route.ts
import { vibe } from '@/lib/vibe'
import { auth } from '@/lib/auth'

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const session = await auth()
  if (!session) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const component = await vibe.load(params.id)

  if (!component) {
    return Response.json({ error: 'Not found' }, { status: 404 })
  }

  // Check ownership
  if (component.userId !== session.user.id) {
    return Response.json({ error: 'Forbidden' }, { status: 403 })
  }

  return Response.json({ component })
}

Component Types

interface VibeComponent {
  id: string
  userId: string
  name: string
  description?: string
  code: string
  compiled?: string
  version: number
  createdAt: Date
  updatedAt: Date
}

interface VibeSaveInput {
  userId: string
  name: string
  description?: string
  code: string
}

interface VibeListOptions {
  userId: string
  limit?: number
  offset?: number
}

Next Steps

VibeFrame

Render saved components securely

Compiler

Validate code before saving