Docs
Platform Kit

Platform Kit

The easiest way to build platforms on top of Supabase

Installation

Folder structure

  • app
    • api
      • ai
        • sql
      • supabase-proxy
        • [...path]
  • registry
    • default
      • platform
        • platform-kit-nextjs
          • components
            • supabase-manager
          • hooks
          • lib
            • pg-meta
            • schemas
  • contexts
1import type { paths } from '@/platform/platform-kit-nextjs/lib/management-api-schema'
2import { listTablesSql } from '@/platform/platform-kit-nextjs/lib/pg-meta'
3import { NextResponse } from 'next/server'
4import OpenAI from 'openai'
5import createClient from 'openapi-fetch'
6
7const openai = new OpenAI({
8  apiKey: process.env.OPENAI_API_KEY,
9})
10
11const client = createClient<paths>({
12  baseUrl: 'https://api.supabase.com',
13  headers: {
14    Authorization: `Bearer ${process.env.SUPABASE_MANAGEMENT_API_TOKEN}`,
15  },
16})
17
18// Function to get database schema
19async function getDbSchema(projectRef: string) {
20  const token = process.env.SUPABASE_MANAGEMENT_API_TOKEN
21  if (!token) {
22    throw new Error('Supabase Management API token is not configured.')
23  }
24
25  const sql = listTablesSql()
26
27  const { data, error } = await client.POST('/v1/projects/{ref}/database/query', {
28    params: {
29      path: {
30        ref: projectRef,
31      },
32    },
33    body: {
34      query: sql,
35      read_only: true,
36    },
37  })
38
39  if (error) {
40    throw error
41  }
42
43  return data as any
44}
45
46function formatSchemaForPrompt(schema: any) {
47  let schemaString = ''
48  if (schema && Array.isArray(schema)) {
49    schema.forEach((table: any) => {
50      const columnInfo = table.columns.map((c: any) => `${c.name} (${c.data_type})`)
51      schemaString += `Table "${table.name}" has columns: ${columnInfo.join(', ')}.\n`
52    })
53  }
54  return schemaString
55}
56
57export async function POST(request: Request) {
58  try {
59    const { prompt, projectRef } = await request.json()
60
61    if (!prompt) {
62      return NextResponse.json({ message: 'Prompt is required.' }, { status: 400 })
63    }
64    if (!projectRef) {
65      return NextResponse.json({ message: 'projectRef is required.' }, { status: 400 })
66    }
67
68    // Implement your permission check here (e.g. check if the user is a member of the project)
69    // In this example, everyone can access all projects
70    const userHasPermissionForProject = Boolean(projectRef)
71
72    if (!userHasPermissionForProject) {
73      return NextResponse.json(
74        { message: 'You do not have permission to access this project.' },
75        { status: 403 }
76      )
77    }
78
79    // 1. Get database schema
80    const schema = await getDbSchema(projectRef)
81    const formattedSchema = formatSchemaForPrompt(schema)
82
83    // 2. Create a prompt for OpenAI
84    const systemPrompt = `You are an expert SQL assistant. Given the following database schema, write a SQL query that answers the user's question. Return only the SQL query, do not include any explanations or markdown.\n\nSchema:\n${formattedSchema}`
85
86    // 3. Call OpenAI to generate SQL using responses.create (plain text output)
87    const response = await openai.responses.create({
88      model: 'gpt-4.1',
89      instructions: systemPrompt, // Use systemPrompt as instructions
90      input: prompt, // User's question
91    })
92
93    const sql = response.output_text
94
95    if (!sql) {
96      return NextResponse.json(
97        { message: 'Could not generate SQL from the prompt.' },
98        { status: 500 }
99      )
100    }
101
102    // 4. Return the generated SQL
103    return NextResponse.json({ sql })
104  } catch (error: any) {
105    console.error('AI SQL generation error:', error)
106    const errorMessage = error.message || 'An unexpected error occurred.'
107    const status = error.response?.status || 500
108    return NextResponse.json({ message: errorMessage }, { status })
109  }
110}

Introduction

The Platform Kit is a collection of customizable API's, hooks and components you can use to provide an embedded Supabase experience within your own platform. It comes in the form of a single dialog that enables the management of database, authentication, storage, users, secrets, logs, and performance monitoring.

Features

  • Database, Auth, Storage, User, Secrets, Logs, and Performance management
  • Responsive dialog/drawer interface (desktop & mobile)
  • API proxy for Management API
  • AI-powered SQL generation (optional)
  • Customize to your liking

Who is it for

Anyone who is providing Postgres databases to their users.

Usage

Embed the manager dialog in your app and manage its state:

import { useState } from 'react'
import { useMobile } from '@/hooks/use-mobile'
import { Button } from '@/components/ui/button'
import SupabaseManagerDialog from '@/components/supabase-manager'
 
export default function Example() {
  const [open, setOpen] = useState(false)
  const projectRef = 'your-project-ref' // Replace with your actual project ref
  const isMobile = useMobile()
 
  return (
    <>
      <Button onClick={() => setOpen(true)}>Open Supabase Manager</Button>
      <SupabaseManagerDialog
        projectRef={projectRef}
        open={open}
        onOpenChange={setOpen}
        isMobile={isMobile}
      />
    </>
  )
}

Quick Start

  1. Set up environment variables: in your .env.local file:

    SUPABASE_MANAGEMENT_API_TOKEN=your-personal-access-token
    NEXT_PUBLIC_ENABLE_AI_QUERIES=true
    OPENAI_API_KEY=your-openai-api-key
  2. Add project-level authentication checks in your API proxy at app/api/supabase-proxy/[...path]/route.ts as well as your ai/sql route at app/api/ai/sql/route.ts to ensure only authorized users can access their own project resources.

  3. Add a Toaster for notifications:
    Place the following component at the root of your app (e.g., in your layout.tsx or App.tsx) to enable toast notifications:

    import { Toaster } from '@/components/ui/sonner'
     
    export default function RootLayout({ children }) {
      return (
        <html lang="en">
          <head />
          <body>
            <main>{children}</main>
            <Toaster />
          </body>
        </html>
      )
    }

That's it! The default setup uses your Supabase personal access token for the Management API.

Security

  • Never expose your Management API token to the client
  • Always implement authentication and permission checks in your proxy

Further reading