supabase
blockplatform-kit-nextjssource: ok · hit
Platform Kit for Nextjs and Supabase
Platform Kit for Nextjs and Supabase
npx shadcn add "https://supabase.com/ui/r/platform-kit-nextjs.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · mediumforms@hookform/resolversreact-hook-form
commandcmdk
chartsrecharts
toastssonner
Registry dependencies
alertbadgebuttoncardchartcommanddialogdrawerformhover-cardinputlabelpopoverselectskeletonswitchtabletoggletoggle-grouptooltip
Files32 · source cached 10/8/2026, 5:56:46 PM
registry/default/platform/platform-kit-nextjs/app/api/ai/sql/route.ts
import { NextResponse } from 'next/server'
import OpenAI from 'openai'
import createClient from 'openapi-fetch'
import type { paths } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api-schema'
import { listTablesSql } from '@/registry/default/platform/platform-kit-nextjs/lib/pg-meta'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
const client = createClient<paths>({
baseUrl: 'https://api.supabase.com',
headers: {
Authorization: `Bearer ${process.env.SUPABASE_MANAGEMENT_API_TOKEN}`,
},
})
// Function to get database schema
async function getDbSchema(projectRef: string) {
const token = process.env.SUPABASE_MANAGEMENT_API_TOKEN
if (!token) {
throw new Error('Supabase Management API token is not configured.')
}
const sql = listTablesSql()
const { data, error } = await client.POST('/v1/projects/{ref}/database/query', {
params: {
path: {
ref: projectRef,
},
},
body: {
query: sql,
read_only: true,
},
})
if (error) {
throw error
}
return data as any
}
function formatSchemaForPrompt(schema: any) {
let schemaString = ''
if (schema && Array.isArray(schema)) {
schema.forEach((table: any) => {
const columnInfo = table.columns.map((c: any) => `${c.name} (${c.data_type})`)
schemaString += `Table "${table.name}" has columns: ${columnInfo.join(', ')}.\n`
})
}
return schemaString
}
export async function POST(request: Request) {
try {
const { prompt, projectRef } = await request.json()
if (!prompt) {
return NextResponse.json({ message: 'Prompt is required.' }, { status: 400 })
}
if (!projectRef) {
return NextResponse.json({ message: 'projectRef is required.' }, { status: 400 })
}
// Implement your permission check here (e.g. check if the user is a member of the project)
// In this example, everyone can access all projects
const userHasPermissionForProject = Boolean(projectRef)
if (!userHasPermissionForProject) {
return NextResponse.json(
{ message: 'You do not have permission to access this project.' },
{ status: 403 }
)
}
// 1. Get database schema
const schema = await getDbSchema(projectRef)
const formattedSchema = formatSchemaForPrompt(schema)
// 2. Create a prompt for OpenAI
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}`
// 3. Call OpenAI to generate SQL using responses.create (plain text output)
const response = await openai.responses.create({
model: 'gpt-4.1',
instructions: systemPrompt, // Use systemPrompt as instructions
input: prompt, // User's question
})
const sql = response.output_text
if (!sql) {
return NextResponse.json(
{ message: 'Could not generate SQL from the prompt.' },
{ status: 500 }
)
}
// 4. Return the generated SQL
return NextResponse.json({ sql })
} catch (error: any) {
console.error('AI SQL generation error:', error)
const errorMessage = error.message || 'An unexpected error occurred.'
const status = error.response?.status || 500
return NextResponse.json({ message: errorMessage }, { status })
}
}
registry/default/platform/platform-kit-nextjs/app/api/supabase-proxy/[...path]/route.ts
import { NextResponse } from 'next/server'
async function forwardToSupabaseAPI(request: Request, method: string, params: { path: string[] }) {
// eslint-disable-next-line turbo/no-undeclared-env-vars
if (!process.env.SUPABASE_MANAGEMENT_API_TOKEN) {
console.error('Supabase Management API token is not configured.')
return NextResponse.json({ message: 'Server configuration error.' }, { status: 500 })
}
const { path } = params
const apiPath = path.join('/')
const url = new URL(request.url)
url.protocol = 'https'
url.hostname = 'api.supabase.com'
url.port = '443'
url.pathname = apiPath
const projectRef = path[2]
// Implement your permission check here (e.g. check if the user is a member of the project)
// In this example, everyone can access all projects
const userHasPermissionForProject = Boolean(projectRef)
if (!userHasPermissionForProject) {
return NextResponse.json(
{ message: 'You do not have permission to access this project.' },
{ status: 403 }
)
}
try {
const forwardHeaders: HeadersInit = {
// eslint-disable-next-line turbo/no-undeclared-env-vars
Authorization: `Bearer ${process.env.SUPABASE_MANAGEMENT_API_TOKEN}`,
}
// Copy relevant headers from the original request
const contentType = request.headers.get('content-type')
if (contentType) {
forwardHeaders['Content-Type'] = contentType
}
const fetchOptions: RequestInit = {
method,
headers: forwardHeaders,
}
// Include body for methods that support it
if (method !== 'GET' && method !== 'HEAD') {
try {
const body = await request.text()
if (body) {
fetchOptions.body = body
}
} catch (error) {
// Handle cases where body is not readable
console.warn('Could not read request body:', error)
}
}
const response = await fetch(url, fetchOptions)
// Get response body
const responseText = await response.text()
let responseData
try {
responseData = responseText ? JSON.parse(responseText) : null
} catch {
responseData = responseText
}
// Return the response with the same status
return NextResponse.json(responseData, { status: response.status })
} catch (error: any) {
console.error('Supabase API proxy error:', error)
const errorMessage = error.message || 'An unexpected error occurred.'
return NextResponse.json({ message: errorMessage }, { status: 500 })
}
}
export async function GET(request: Request, { params }: { params: Promise<{ path: string[] }> }) {
const resolvedParams = await params
return forwardToSupabaseAPI(request, 'GET', resolvedParams)
}
export async function HEAD(request: Request, { params }: { params: Promise<{ path: string[] }> }) {
const resolvedParams = await params
return forwardToSupabaseAPI(request, 'HEAD', resolvedParams)
}
export async function POST(request: Request, { params }: { params: Promise<{ path: string[] }> }) {
const resolvedParams = await params
return forwardToSupabaseAPI(request, 'POST', resolvedParams)
}
export async function PUT(request: Request, { params }: { params: Promise<{ path: string[] }> }) {
const resolvedParams = await params
return forwardToSupabaseAPI(request, 'PUT', resolvedParams)
}
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ path: string[] }> }
) {
const resolvedParams = await params
return forwardToSupabaseAPI(request, 'DELETE', resolvedParams)
}
export async function PATCH(request: Request, { params }: { params: Promise<{ path: string[] }> }) {
const resolvedParams = await params
return forwardToSupabaseAPI(request, 'PATCH', resolvedParams)
}
registry/default/platform/platform-kit-nextjs/components/dynamic-form.tsx
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import { useEffect, useRef } from 'react'
import { useForm } from 'react-hook-form'
import type { z, ZodTypeAny } from 'zod'
import { Button } from '@/registry/default/components/ui/button'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/registry/default/components/ui/form'
import { Input } from '@/registry/default/components/ui/input'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/registry/default/components/ui/select'
import { Switch } from '@/registry/default/components/ui/switch'
interface DynamicFormProps<T extends z.ZodRawShape = z.ZodRawShape> {
schema: z.ZodObject<T>
onSubmit: (data: z.infer<z.ZodObject<T>>) => void
isLoading?: boolean
initialValues?: Partial<z.infer<z.ZodObject<T>>>
labels?: Record<string, string | { label: string; options?: Record<string, string> }>
columnInfo?: Record<string, { data_type: string; is_nullable: boolean }>
}
const getZodDef = (schema: ZodTypeAny): Record<string, any> =>
(schema as any)._def || (schema as any).def
const unwrapZodType = (fieldSchema: ZodTypeAny): ZodTypeAny => {
if (
!fieldSchema ||
typeof getZodDef(fieldSchema) !== 'object' ||
getZodDef(fieldSchema) === null
) {
throw new Error(
`unwrapZodType received an invalid Zod schema object. Check the console for the problematic schema/key.`
)
}
let currentSchema = fieldSchema
// Support both old (typeName) and new (type) Zod formats
const getTypeName = (def: Record<string, any>) => def.typeName || def.type
while (
getTypeName(getZodDef(currentSchema)) === 'ZodOptional' ||
getTypeName(getZodDef(currentSchema)) === 'optional' ||
getTypeName(getZodDef(currentSchema)) === 'ZodDefault' ||
getTypeName(getZodDef(currentSchema)) === 'default' ||
getTypeName(getZodDef(currentSchema)) === 'ZodNullable' ||
getTypeName(getZodDef(currentSchema)) === 'nullable' ||
getTypeName(getZodDef(currentSchema)) === 'ZodEffects' ||
getTypeName(getZodDef(currentSchema)) === 'effects'
) {
const typeName = getTypeName(getZodDef(currentSchema))
if (typeName === 'ZodEffects' || typeName === 'effects') {
// For ZodEffects, get the schema inside the effect
currentSchema = getZodDef(currentSchema).schema
} else {
currentSchema = getZodDef(currentSchema).innerType
}
}
return currentSchema
}
export function DynamicForm<T extends z.ZodRawShape = z.ZodRawShape>({
schema,
onSubmit,
isLoading = false,
initialValues,
labels,
columnInfo,
}: DynamicFormProps<T>) {
const isInitializingRef = useRef(true)
const defaultValues = Object.keys(schema.shape).reduce(
(acc, key) => {
const originalFieldSchema = schema.shape[key]
if (typeof originalFieldSchema === 'undefined') {
throw new Error(
`Schema error: schema.shape['${key}'] is undefined. Check schema definition.`
)
}
// Support both old (typeName) and new (type) Zod formats
const getTypeName = (def: Record<string, any>) => def.typeName || def.type
if (
getTypeName(getZodDef(originalFieldSchema)) === 'ZodDefault' ||
getTypeName(getZodDef(originalFieldSchema)) === 'default'
) {
acc[key] = getZodDef(originalFieldSchema).defaultValue()
return acc
}
const baseType = unwrapZodType(originalFieldSchema)
switch (getTypeName(getZodDef(baseType))) {
case 'ZodString':
case 'string':
acc[key] = ''
break
case 'ZodBoolean':
case 'boolean':
acc[key] = false // Default optional booleans to false
break
case 'ZodEnum':
case 'enum':
// For enums, use the first enum value as default
const enumValues = getZodDef(baseType).values || []
acc[key] = enumValues.length > 0 ? enumValues[0] : ''
break
case 'ZodNumber':
case 'number':
acc[key] = 0 // Default optional numbers to 0
break
case 'ZodArray':
case 'array':
acc[key] = [] // Default arrays to empty array
break
default:
acc[key] = undefined // For other types, or if truly no default makes sense
break
}
return acc
},
{} as Record<string, any>
)
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: defaultValues as any,
})
useEffect(() => {
if (initialValues) {
isInitializingRef.current = true
const schemaKeys = Object.keys(schema.shape)
const processedInitialValues = schemaKeys.reduce(
(acc, key) => {
const fieldDefFromSchema = schema.shape[key]
if (typeof fieldDefFromSchema === 'undefined') {
throw new Error(`Schema error in useEffect: schema.shape['${key}'] is undefined.`)
}
const value = initialValues.hasOwnProperty(key) ? initialValues[key] : undefined
const baseFieldType = unwrapZodType(fieldDefFromSchema)
// Support both old (typeName) and new (type) Zod formats
const getTypeName = (def: Record<string, any>) => def.typeName || def.type
const fieldTypeName = getTypeName(getZodDef(baseFieldType))
if (fieldTypeName === 'ZodBoolean' || fieldTypeName === 'boolean') {
acc[key] = !!value
} else if (fieldTypeName === 'ZodString' || fieldTypeName === 'string') {
acc[key] = value === null || value === undefined ? '' : String(value)
} else if (fieldTypeName === 'ZodEnum' || fieldTypeName === 'enum') {
const enumValues = getZodDef(baseFieldType).values || []
acc[key] =
value === null || value === undefined || !enumValues.includes(value)
? enumValues[0] || ''
: String(value)
} else if (fieldTypeName === 'ZodNumber' || fieldTypeName === 'number') {
const num = Number(value)
acc[key] = isNaN(num) ? 0 : num
} else if (fieldTypeName === 'ZodArray' || fieldTypeName === 'array') {
// For arrays, the value should already be an array from the database
// Handle null values properly
if (Array.isArray(value)) {
acc[key] = value
} else if (value === null || value === undefined) {
acc[key] = []
} else if (typeof value === 'string' && value.startsWith('{') && value.endsWith('}')) {
// Parse PostgreSQL array format like {ONE,TWO} into JavaScript array
try {
const innerContent = value.slice(1, -1) // Remove { and }
if (innerContent.trim() === '') {
acc[key] = []
} else {
// Split by comma and trim whitespace
acc[key] = innerContent.split(',').map((item: string) => item.trim())
}
} catch {
// If parsing fails, default to empty array
acc[key] = []
}
} else {
acc[key] = []
}
} else {
acc[key] = value
}
return acc
},
{} as Record<string, any>
)
form.reset(processedInitialValues as any)
setTimeout(() => {
isInitializingRef.current = false
}, 0)
} else {
isInitializingRef.current = false
}
}, [initialValues, form, schema])
const renderField = (fieldName: string, fieldSchema: ZodTypeAny) => {
const baseType = unwrapZodType(fieldSchema)
// Support both old (typeName) and new (type) Zod formats
const getTypeName = (def: Record<string, any>) => def.typeName || def.type
const typeName = getTypeName(getZodDef(baseType))
const description = fieldSchema.description
return (
<FormField
key={fieldName}
control={form.control}
name={fieldName as any}
render={({ field }) => {
const labelConfig = labels?.[fieldName]
const label =
typeof labelConfig === 'string' ? labelConfig : labelConfig?.label || fieldName
const typeDisplay = columnInfo?.[fieldName]?.data_type || ''
switch (typeName) {
case 'ZodString':
case 'string':
return (
<FormItem className="py-6 border-b">
<div className="w-full flex flex-col lg:flex-row lg:items-center justify-between w-full gap-4 lg:gap-8">
<div className="flex-1 pr-4">
<FormLabel>{label}</FormLabel>
<div className="text-sm text-muted-foreground mt-1">{typeDisplay}</div>
{description && <FormDescription>{description}</FormDescription>}
<FormMessage />
</div>
<div className="flex-1 lg:max-w-1/2">
<FormControl>
<Input
placeholder={`Enter your ${fieldName}`}
{...field}
value={String(field.value || '')}
/>
</FormControl>
</div>
</div>
</FormItem>
)
case 'ZodNumber':
case 'number':
return (
<FormItem className="py-6 border-b">
<div className="w-full flex flex-col lg:flex-row lg:items-center justify-between w-full gap-4 lg:gap-8">
<div className="flex-1 pr-4">
<FormLabel>{label}</FormLabel>
<div className="text-sm text-muted-foreground">{typeDisplay}</div>
{description && <FormDescription>{description}</FormDescription>}
<FormMessage />
</div>
<div className="flex-1 lg:max-w-1/2">
<FormControl>
<Input
type="number"
placeholder={`Enter value for ${fieldName}`}
{...field}
value={String(field.value ?? '')}
onChange={(e) => {
const value = e.target.value
const num = parseInt(value, 10)
field.onChange(isNaN(num) ? undefined : num)
}}
/>
</FormControl>
</div>
</div>
</FormItem>
)
case 'ZodBoolean':
case 'boolean':
return (
<FormItem className="py-6 border-b flex flex-row items-center justify-between gap-8">
<div>
<FormLabel>{label}</FormLabel>
<div className="text-sm text-muted-foreground">{typeDisplay}</div>
{description && <FormDescription>{description}</FormDescription>}
<FormMessage />
</div>
<FormControl>
<Switch checked={!!field.value} onCheckedChange={field.onChange} />
</FormControl>
</FormItem>
)
case 'ZodEnum':
case 'enum':
const options = getZodDef(baseType).values
const optionLabels = typeof labelConfig === 'object' ? labelConfig.options : undefined
return (
<FormItem className="py-6 border-b">
<div className="w-full flex flex-col lg:flex-row lg:items-center justify-between w-full gap-4 lg:gap-8">
<div className="flex-1 pr-4">
<FormLabel>{label}</FormLabel>
<div className="text-sm text-muted-foreground">{typeDisplay}</div>
{description && <FormDescription>{description}</FormDescription>}
<FormMessage />
</div>
<div className="flex-1 lg:max-w-1/2">
<Select
onValueChange={(value) => {
if (!isInitializingRef.current) {
field.onChange(value)
}
}}
value={String(field.value || '')}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={`Select a ${fieldName}`} />
</SelectTrigger>
</FormControl>
<SelectContent>
{options.map((option: string) => (
<SelectItem key={option} value={option}>
{optionLabels?.[option] || option}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</FormItem>
)
case 'ZodArray':
case 'array':
return (
<FormItem className="py-6 border-b">
<div className="flex flex-row items-center justify-between w-full gap-8">
<div className="flex-1 pr-4">
<FormLabel>{label}</FormLabel>
<div className="text-sm text-muted-foreground">
Enter as JSON array: ["item1", "item2"]
{columnInfo?.[fieldName]?.is_nullable && ' (leave empty for null)'}
</div>
{description && <FormDescription>{description}</FormDescription>}
<FormMessage />
</div>
<div className="flex-1">
<FormControl>
<Input
placeholder={
columnInfo?.[fieldName]?.is_nullable
? `["item1", "item2"] or leave empty for null`
: `["item1", "item2"]`
}
{...field}
value={
field.value === null || field.value === undefined
? ''
: Array.isArray(field.value)
? JSON.stringify(field.value)
: String(field.value || '')
}
onChange={(e) => {
const value = e.target.value
if (value.trim() === '') {
// Empty string means null (for nullable) or empty array (for non-nullable)
field.onChange(null)
} else {
try {
const parsed = JSON.parse(value)
if (Array.isArray(parsed)) {
field.onChange(parsed)
} else {
// If it's not an array, treat as invalid input
field.onChange(value)
}
} catch {
// Invalid JSON, keep the string value to show error
field.onChange(value)
}
}
}}
/>
</FormControl>
</div>
</div>
</FormItem>
)
default:
return <></>
}
}}
/>
)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{Object.keys(schema.shape).map((fieldName) =>
renderField(fieldName, schema.shape[fieldName])
)}
<div className="pt-6">
<Button type="submit" disabled={isLoading}>
{isLoading ? 'Saving...' : 'Save'}
</Button>
</div>
</form>
</Form>
)
}
registry/default/platform/platform-kit-nextjs/components/logo-supabase.tsx
export function LogoSupabase({ className, size = 24 }: { className?: string; size?: number }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 109 113"
className={className}
style={{ width: size, height: size }}
>
<path
fill="url(#supabase_svg__a)"
d="M63.708 110.284c-2.86 3.601-8.658 1.628-8.727-2.97l-1.007-67.251h45.22c8.19 0 12.758 9.46 7.665 15.874z"
></path>
<path
fill="url(#supabase_svg__b)"
fillOpacity="0.2"
d="M63.708 110.284c-2.86 3.601-8.658 1.628-8.727-2.97l-1.007-67.251h45.22c8.19 0 12.758 9.46 7.665 15.874z"
></path>
<path
fill="#3ECF8E"
d="M45.317 2.071c2.86-3.601 8.657-1.628 8.726 2.97l.442 67.251H9.83c-8.19 0-12.759-9.46-7.665-15.875z"
></path>
<defs>
<linearGradient
id="supabase_svg__a"
x1="53.974"
x2="94.163"
y1="54.974"
y2="71.829"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#249361"></stop>
<stop offset="1" stopColor="#3ECF8E"></stop>
</linearGradient>
<linearGradient
id="supabase_svg__b"
x1="36.156"
x2="54.484"
y1="30.578"
y2="65.081"
gradientUnits="userSpaceOnUse"
>
<stop></stop>
<stop offset="1" stopOpacity="0"></stop>
</linearGradient>
</defs>
</svg>
)
}
registry/default/platform/platform-kit-nextjs/components/results-table.tsx
'use client'
import { cn } from '@/lib/utils'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/registry/default/components/ui/table'
interface ResultsTableProps {
data: any[]
onRowClick?: (row: any) => void
}
export function ResultsTable({ data, onRowClick }: ResultsTableProps) {
if (!data || data.length === 0) {
return <p className="p-4 text-center">The query returned no results.</p>
}
const headers = Object.keys(data[0])
return (
<div className="overflow-auto">
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent">
{headers.map((header) => (
<TableHead className="first:pl-6 lg:first:pl-8 last:pr-6 lg:last:pr-8" key={header}>
{header}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, rowIndex) => (
<TableRow
key={rowIndex}
onClick={() => onRowClick?.(row)}
className={cn(onRowClick && 'cursor-pointer hover:bg-muted/50 group')}
>
{headers.map((header) => (
<TableCell
className="first:pl-6 lg:first:pl-8 last:pr-6 lg:last:pr-8 text-xs text-muted-foreground group-hover:text-foreground min-w-32"
key={`${rowIndex}-${header}`}
>
<div className="text-xs font-mono w-fit max-w-96 truncate">
{JSON.stringify(row[header]).replace(/^"|"$/g, '')}
</div>
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}
registry/default/platform/platform-kit-nextjs/components/sql-editor.tsx
'use client'
import { Editor } from '@monaco-editor/react'
import {
AlertTriangle,
ArrowUp,
BarChart as BarChartIcon,
FileText,
Loader2,
Wand,
} from 'lucide-react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'
import { Button } from '@/registry/default/components/ui/button'
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from '@/registry/default/components/ui/chart'
import { Input } from '@/registry/default/components/ui/input'
import { Label } from '@/registry/default/components/ui/label'
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/components/ui/popover'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/registry/default/components/ui/select'
import { Skeleton } from '@/registry/default/components/ui/skeleton'
import { Switch } from '@/registry/default/components/ui/switch'
import { ToggleGroup, ToggleGroupItem } from '@/registry/default/components/ui/toggle-group'
import { ResultsTable } from '@/registry/default/platform/platform-kit-nextjs/components/results-table'
import { useRunQuery } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-run-query'
interface SqlEditorProps {
projectRef: string
initialSql?: string
queryKey?: any
label?: string
onResults?: (data: any[] | undefined) => void
onRowClick?: (row: any, queryKey?: any) => void
hideSql?: boolean
readOnly?: boolean
runAutomatically?: boolean
refetch?: number
initialNaturalLanguageMode?: boolean
hideChartOption?: boolean
}
export function SqlEditor({
projectRef,
initialSql,
queryKey,
onResults,
onRowClick,
label = 'Query your data',
hideSql = false,
readOnly = false,
runAutomatically = false,
refetch,
initialNaturalLanguageMode = false,
hideChartOption = false,
}: SqlEditorProps) {
const [sql, setSql] = useState(initialSql || '')
const [isSqlVisible, setIsSqlVisible] = useState(!hideSql)
const [isNaturalLanguageMode, setIsNaturalLanguageMode] = useState(
process.env.NEXT_PUBLIC_ENABLE_AI_QUERIES === 'true' && initialNaturalLanguageMode
)
const [naturalLanguageQuery, setNaturalLanguageQuery] = useState('')
const { mutate: runQuery, data, isPending, error } = useRunQuery()
const [isGeneratingSql, setIsGeneratingSql] = useState(false)
const [aiError, setAiError] = useState<string | null>(null)
const [isChartVisible, setIsChartVisible] = useState(false)
const [xAxisColumn, setXAxisColumn] = useState<string | null>(null)
const [yAxisColumn, setYAxisColumn] = useState<string | null>(null)
const columns = useMemo(() => {
if (!data || data.length === 0) return []
return Object.keys(data[0])
}, [data])
useEffect(() => {
if (initialSql) {
setSql(initialSql)
}
}, [initialSql])
const handleRunNaturalLanguageQuery = async () => {
if (!naturalLanguageQuery) return
setIsGeneratingSql(true)
setAiError(null)
try {
const response = await fetch('/api/ai/sql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: naturalLanguageQuery,
projectRef,
}),
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.message || 'Failed to generate SQL')
}
const { sql: generatedSql } = await response.json()
setSql(generatedSql)
runQuery({ projectRef, query: generatedSql, readOnly: true })
} catch (error: any) {
console.error(error)
setAiError(error.message)
} finally {
setIsGeneratingSql(false)
}
}
const handleRunQuery = useCallback(() => {
if (sql) {
runQuery({ projectRef, query: sql, readOnly: true })
}
}, [sql, projectRef, runQuery])
useEffect(() => {
setIsSqlVisible(!hideSql)
}, [hideSql])
useEffect(() => {
if (runAutomatically && initialSql) {
runQuery({ projectRef, query: initialSql, readOnly: true })
}
}, [runAutomatically, initialSql, projectRef, runQuery])
useEffect(() => {
if (refetch && refetch > 0) {
handleRunQuery()
}
}, [refetch, handleRunQuery])
useEffect(() => {
if (onResults) {
onResults(data)
}
}, [data, onResults])
useEffect(() => {
const noResults = !data || (Array.isArray(data) && data.length === 0)
if (noResults && !isSqlVisible && !isNaturalLanguageMode && !readOnly && !isPending) {
setIsSqlVisible(true)
}
}, [data, isSqlVisible, isNaturalLanguageMode])
const serverErrorMessage = (error as any)?.response?.data?.message || ''
const isReadOnlyError =
serverErrorMessage.includes('permission denied') || serverErrorMessage.includes('42501')
const customReadOnlyError = "You can't directly alter your database schema, use chat instead"
// Build the toggle-group selection based on current UI state
const toggleValues = useMemo(() => {
const values: string[] = []
if (isNaturalLanguageMode) values.push('chat')
if (isSqlVisible) values.push('sql')
if (!hideChartOption && isChartVisible) values.push('chart')
return values
}, [isNaturalLanguageMode, isSqlVisible, isChartVisible, hideChartOption])
const handleToggleGroupChange = (values: string[]) => {
setIsNaturalLanguageMode(values.includes('chat'))
setIsSqlVisible(values.includes('sql'))
if (!hideChartOption) {
setIsChartVisible(values.includes('chart'))
}
}
return (
<div>
<div className="px-6 pt-4 lg:px-8 lg:pt-8">
<div className="flex items-center gap-4 mb-4 ">
<h2 className="font-semibold flex-1">{label}</h2>
<ToggleGroup
type="multiple"
size="sm"
value={toggleValues}
onValueChange={handleToggleGroupChange}
className="gap-1"
>
{process.env.NEXT_PUBLIC_ENABLE_AI_QUERIES === 'true' && (
<ToggleGroupItem value="chat" aria-label="Chat">
<Wand className="h-4 w-4" />
</ToggleGroupItem>
)}
<ToggleGroupItem value="sql" aria-label="SQL">
<FileText className="h-4 w-4" />
</ToggleGroupItem>
{!hideChartOption && (
<Popover>
<PopoverTrigger asChild>
<ToggleGroupItem value="chart" aria-label="Chart">
<BarChartIcon className="h-4 w-4" />
</ToggleGroupItem>
</PopoverTrigger>
<PopoverContent className="w-80">
<div className="grid gap-4">
<div className="grid gap-2">
<div className="flex items-center space-x-2">
<div className="flex-1">
<Label className="flex-1 mb-2 block">Show Chart</Label>
<p className="text-xs text-muted-foreground">
Visualize your data with a chart.
</p>
</div>
<Switch
id="show-chart"
size="sm"
className="mb-0"
checked={isChartVisible}
onCheckedChange={setIsChartVisible}
/>
</div>
{isChartVisible && (
<div className="mt-2">
<div className="grid grid-cols-3 items-center gap-4 mb-2">
<Label htmlFor="x-axis">X-Axis</Label>
<Select
onValueChange={setXAxisColumn}
defaultValue={xAxisColumn || undefined}
>
<SelectTrigger className="col-span-2 h-8">
<SelectValue placeholder="Select column" />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col} value={col}>
{col}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-3 items-center gap-4 mb-2">
<Label htmlFor="y-axis">Y-Axis</Label>
<Select
onValueChange={setYAxisColumn}
defaultValue={yAxisColumn || undefined}
>
<SelectTrigger className="col-span-2 h-8">
<SelectValue placeholder="Select column" />
</SelectTrigger>
<SelectContent>
{columns.map((col) => (
<SelectItem key={col} value={col}>
{col}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
)}
</div>
</div>
</PopoverContent>
</Popover>
)}
</ToggleGroup>
</div>
<div>
{isNaturalLanguageMode && (
<div className="relative mb-4">
<Wand
strokeWidth={1.5}
size={16}
className="absolute left-4 top-1/2 -translate-y-1/2"
/>
<Input
placeholder="e.g. Show me all users who signed up in the last 7 days"
value={naturalLanguageQuery}
onChange={(e) => setNaturalLanguageQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleRunNaturalLanguageQuery()
}
}}
className="w-full px-10"
/>
<Button
onClick={handleRunNaturalLanguageQuery}
disabled={isGeneratingSql || isPending}
className="h-7 w-7 rounded-full p-0 shrink-0 absolute right-1 top-1/2 -translate-y-1/2"
>
{isGeneratingSql ? (
<Loader2 size={16} className="animate-spin" />
) : isPending ? (
<Loader2 size={16} className="animate-spin" />
) : (
<ArrowUp size={16} />
)}
</Button>
</div>
)}
{isSqlVisible && (
<div className="border-t border-b bg-muted overflow-hidden -mx-6 lg:-mx-8 mt-4 relative">
<Editor
height="200px"
language="sql"
value={sql}
onChange={(value) => setSql(value || '')}
theme="vs-dark"
className="bg-transparent"
options={{
minimap: { enabled: false },
fontSize: 14,
readOnly,
padding: {
top: 24,
bottom: 24,
},
}}
/>
<Button
size="sm"
onClick={handleRunQuery}
disabled={isPending}
className="absolute bottom-4 right-4"
>
{isPending ? 'Running...' : 'Run Query'}
</Button>
</div>
)}
</div>
</div>
{isPending && (
<div className="space-y-2 p-4 px-6 lg:px-8">
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
</div>
)}
{aiError && (
<div className="px-6 lg:px-8 py-4">
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Error generating SQL</AlertTitle>
<AlertDescription>{aiError}</AlertDescription>
</Alert>
</div>
)}
{error && (
<div className="px-6 lg:px-8 py-4">
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Query Error</AlertTitle>
<AlertDescription>
{isReadOnlyError
? customReadOnlyError
: serverErrorMessage || (error as Error)?.message || 'An unexpected error occurred'}
</AlertDescription>
</Alert>
</div>
)}
{!hideChartOption && data && isChartVisible && xAxisColumn && yAxisColumn && (
<div className="px-8 mt-8 mb-4">
<QueryResultChart data={data} xAxis={xAxisColumn} yAxis={yAxisColumn} />
</div>
)}
{data && (
<div>
<ResultsTable data={data} onRowClick={(row) => onRowClick?.(row, queryKey)} />
</div>
)}
</div>
)
}
function QueryResultChart({ data, xAxis, yAxis }: { data: any[]; xAxis: string; yAxis: string }) {
const chartConfig = {
[yAxis]: {
label: yAxis,
color: 'var(--chart-1)',
},
} satisfies ChartConfig
return (
<ChartContainer config={chartConfig} className="aspect-auto h-[250px] w-full">
<BarChart
accessibilityLayer
data={data}
margin={{
left: -24,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis dataKey={xAxis} tickLine={false} axisLine={false} tickMargin={8} minTickGap={32} />
<YAxis
dataKey={yAxis}
tickLine={false}
axisLine={false}
tickMargin={8}
tickCount={5}
allowDecimals={false}
/>
<ChartTooltip content={<ChartTooltipContent className="w-[150px]" indicator="dot" />} />
<Bar dataKey={yAxis} fill={`var(--color-${yAxis})`} />
</BarChart>
</ChartContainer>
)
}
registry/default/platform/platform-kit-nextjs/components/supabase-manager/auth.tsx
'use client'
import { AlertTriangle, ChevronRight, Mail, Phone, User } from 'lucide-react'
import { useCallback, useMemo } from 'react'
import { z } from 'zod'
import {
authEmailProviderSchema,
authFieldLabels,
authGeneralSettingsSchema,
authGoogleProviderSchema,
authPhoneProviderSchema,
type AuthGeneralSettingsSchema,
} from '../../lib/schemas/auth'
import { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'
import { Button } from '@/registry/default/components/ui/button'
import { Skeleton } from '@/registry/default/components/ui/skeleton'
import { DynamicForm } from '@/registry/default/platform/platform-kit-nextjs/components/dynamic-form'
import { useSheetNavigation } from '@/registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext'
import {
useGetAuthConfig,
useUpdateAuthConfig,
} from '@/registry/default/platform/platform-kit-nextjs/hooks/use-auth'
function ProviderSettingsView({
projectRef,
schema,
title,
initialValues: allInitialValues,
onSuccess,
}: {
projectRef: string
schema: z.ZodObject<any> | z.ZodEffects<z.ZodObject<any>>
title: string
initialValues: any
onSuccess: () => void
}) {
const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useUpdateAuthConfig()
const actualSchema = 'shape' in schema ? schema : (schema._def.schema as z.ZodObject<any>)
const handleUpdateAuthConfig = (formData: z.infer<typeof actualSchema>) => {
const payload = Object.fromEntries(
Object.entries(formData).filter(([_, value]) => value !== undefined)
)
if (Object.keys(payload).length === 0) {
alert('No changes to submit. Please modify a field to update.')
return
}
updateAuthConfig(
{
projectRef,
payload,
},
{
onSuccess,
}
)
}
const formInitialValues = useMemo(() => {
if (!allInitialValues) {
return undefined
}
const schemaKeys = Object.keys(actualSchema.shape)
const result = schemaKeys.reduce(
(acc, key) => {
if (Object.prototype.hasOwnProperty.call(allInitialValues, key)) {
acc[key] = allInitialValues[key]
}
return acc
},
{} as Record<string, any>
)
return result
}, [allInitialValues, actualSchema])
return (
<div className="w-full max-w-3xl mx-auto p-6 pt-4 lg:p-12 lg:pt-12">
<h2 className="lg:text-xl font-semibold mb-2 lg:mb-4">{title}</h2>
<DynamicForm
schema={actualSchema}
onSubmit={handleUpdateAuthConfig}
isLoading={isUpdatingConfig}
initialValues={formInitialValues}
labels={authFieldLabels}
/>
</div>
)
}
export function AuthManager({ projectRef }: { projectRef: string }) {
const {
data: authConfigData,
isLoading: isLoadingConfig,
error: errorLoadingConfig,
} = useGetAuthConfig(projectRef)
const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useUpdateAuthConfig()
const { push, pop } = useSheetNavigation()
const handleUpdateGeneralSettings = (formData: AuthGeneralSettingsSchema) => {
const payload = Object.fromEntries(
Object.entries(formData).filter(([_, value]) => value !== undefined)
)
if (Object.keys(payload).length === 0) {
alert('No changes to submit. Please modify a field to update.')
return
}
updateAuthConfig({
projectRef,
payload,
})
}
const providers: {
name: string
icon: React.ReactNode
description: string
schema: z.ZodObject<any> | z.ZodEffects<z.ZodObject<any>>
}[] = [
{
icon: <Mail className="h-4 w-4 text-muted-foreground" />,
name: 'Email',
description: 'Sign in with email and password',
schema: authEmailProviderSchema,
},
{
icon: <Phone className="h-4 w-4 text-muted-foreground" />,
name: 'Phone',
description: 'Sign in with phone number',
schema: authPhoneProviderSchema,
},
{
icon: <User className="h-4 w-4 text-muted-foreground" />,
name: 'Google',
description: 'Sign in with Google',
schema: authGoogleProviderSchema,
},
]
const handleProviderClick = useCallback(
(provider: { name: string; schema: z.ZodObject<any> | z.ZodEffects<z.ZodObject<any>> }) => {
push({
title: `${provider.name} Provider Settings`,
component: (
<ProviderSettingsView
projectRef={projectRef}
schema={provider.schema}
title={`${provider.name} Provider Settings`}
initialValues={authConfigData}
onSuccess={() => pop()}
/>
),
})
},
[projectRef, authConfigData, push, pop]
)
const formInitialValues = useMemo(() => {
if (!authConfigData) {
return undefined
}
const schemaKeys = Object.keys(authGeneralSettingsSchema.shape)
return schemaKeys.reduce(
(acc, key) => {
if (Object.prototype.hasOwnProperty.call(authConfigData, key)) {
acc[key] = authConfigData[key as keyof typeof authConfigData]
}
return acc
},
{} as Record<string, any>
)
}, [authConfigData])
if (errorLoadingConfig) {
return (
<div className="mx-6 lg:mx-8 mt-8">
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Error loading auth settings</AlertTitle>
<AlertDescription>
There was a problem loading your authentication configuration.
</AlertDescription>
</Alert>
</div>
)
}
if (isLoadingConfig)
return (
<div className="w-full max-w-3xl mx-auto p-12 space-y-2">
<Skeleton className="h-12 w-full mb-8" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
</div>
)
return (
<div className="w-full max-w-3xl mx-auto p-6 pt-4 lg:p-12 lg:pt-12">
<div className="mb-12">
<h2 className="text-base lg:text-xl font-semibold">General Settings</h2>
<p className="text-sm lg:text-base text-muted-foreground mt-1 mb-2">
Allow people to sign up to your app
</p>
<DynamicForm
schema={authGeneralSettingsSchema}
onSubmit={handleUpdateGeneralSettings}
isLoading={isUpdatingConfig}
initialValues={formInitialValues}
labels={authFieldLabels}
/>
</div>
<div>
<h2 className="font-semibold lg:text-lg">Sign in methods</h2>
<p className="text-muted-foreground mb-6 mt-1 text-sm lg:text-base">
Configure how people sign in and up to your app.
</p>
<div className="border rounded-md overflow-hidden bg-background">
{providers.map((provider) => (
<Button
variant="ghost"
size="lg"
onClick={() => handleProviderClick(provider)}
key={provider.name}
className="rounded-none justify-start flex-row text-left h-auto px-8 py-4 w-full gap-4 border-b last:border-b-0"
>
{provider.icon}
<div className="flex-1">
<h3 className="font-bold">{provider.name}</h3>
<p className="text-muted-foreground text-sm">{provider.description}</p>
</div>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
</Button>
))}
</div>
</div>
</div>
)
}
registry/default/platform/platform-kit-nextjs/components/supabase-manager/database.tsx
'use client'
import { AlertTriangle, Table, Wand } from 'lucide-react'
import { useCallback, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { z, type ZodTypeAny } from 'zod'
import { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'
import { Button } from '@/registry/default/components/ui/button'
import { Skeleton } from '@/registry/default/components/ui/skeleton'
import { DynamicForm } from '@/registry/default/platform/platform-kit-nextjs/components/dynamic-form'
import { SqlEditor } from '@/registry/default/platform/platform-kit-nextjs/components/sql-editor'
import { useSheetNavigation } from '@/registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext'
import { useRunQuery } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-run-query'
import { useListTables } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-tables'
// Helper to generate a Zod schema from the table's column definitions
function generateZodSchema(table: any): z.ZodObject<any, any, any> {
if (!table || !table.columns) {
return z.object({})
}
const shape: Record<string, ZodTypeAny> = {}
for (const column of table.columns) {
// We can only edit updatable columns that are not generated
if (!column.is_updatable || column.is_generated) continue
let fieldSchema: ZodTypeAny
const dataType = column.data_type.toLowerCase()
if (dataType.includes('array')) {
fieldSchema = z.array(z.any())
} else if (dataType.includes('int') || dataType.includes('numeric')) {
fieldSchema = z.number()
} else if (dataType.includes('bool')) {
fieldSchema = z.boolean()
} else if (
dataType === 'user-defined' &&
column.enums &&
Array.isArray(column.enums) &&
column.enums.length > 0
) {
// Handle enum types
fieldSchema = z.enum(column.enums as [string, ...string[]])
} else {
// Default to string for text, varchar, timestamp, uuid, etc.
fieldSchema = z.string()
}
if (column.is_nullable) {
fieldSchema = fieldSchema.nullish()
}
shape[column.name] = fieldSchema
}
return z.object(shape)
}
const getPrimaryKeys = (table: any): string[] => {
if (!table || !table.primary_keys) {
return []
}
return table.primary_keys.map((pk: any) => pk.name)
}
function EditRowView({
projectRef,
table,
row,
onSuccess,
}: {
projectRef: string
table: any
row: any
onSuccess: () => void
}) {
const { mutate: runUpdateQuery, isPending: isUpdatePending } = useRunQuery()
const formSchema = useMemo(() => generateZodSchema(table), [table])
const columnInfo = useMemo(() => {
if (!table || !table.columns) return {}
const info: Record<string, any> = {}
for (const column of table.columns) {
// Only include updatable columns that are not generated
if (!column.is_updatable || column.is_generated) continue
const dataType = column.data_type.toLowerCase()
const displayType =
dataType === 'user-defined' &&
column.enums &&
Array.isArray(column.enums) &&
column.enums.length > 0
? 'enum'
: dataType
info[column.name] = {
data_type: displayType,
is_nullable: column.is_nullable,
}
}
return info
}, [table])
const handleFormSubmit = useCallback(
(formData: any) => {
const pks = getPrimaryKeys(table)
if (pks.length === 0) {
toast.error('Cannot update row. This table does not have a primary key.')
return
}
const setClauses = Object.entries(formData)
.map(([key, value]) => {
if (JSON.stringify(row[key]) === JSON.stringify(value)) return null
// Find the column type to determine formatting
const column = table.columns.find((col: any) => col.name === key)
const dataType = column?.data_type?.toLowerCase() || ''
const isNullable = column?.is_nullable || false
let formattedValue
// Handle empty/null values
if (
value === null ||
value === undefined ||
(typeof value === 'string' && value.trim() === '')
) {
if (isNullable) {
formattedValue = 'NULL'
} else if (dataType.includes('array')) {
// Non-nullable array, set to empty array
const jsonObj = JSON.stringify({ [key]: [] })
formattedValue = `(select ${key} from json_populate_record(null::public."${
table.name
}", '${jsonObj.replace(/'/g, "''")}'))`
} else {
// Non-nullable text field, set to empty string
formattedValue = "''"
}
} else if (Array.isArray(value) && value.length === 0) {
// Explicitly empty array (different from null)
const jsonObj = JSON.stringify({ [key]: [] })
formattedValue = `(select ${key} from json_populate_record(null::public."${
table.name
}", '${jsonObj.replace(/'/g, "''")}'))`
} else if (dataType.includes('array')) {
// Array type with actual values - use json_populate_record syntax
const jsonObj = JSON.stringify({ [key]: value })
formattedValue = `(select ${key} from json_populate_record(null::public."${
table.name
}", '${jsonObj.replace(/'/g, "''")}'))`
} else if (dataType === 'user-defined' && column?.enums) {
// Handle enum values - treat as strings with proper escaping
formattedValue = `'${String(value).replace(/'/g, "''")}'`
} else if (typeof value === 'string') {
formattedValue = `'${value.replace(/'/g, "''")}'`
} else {
formattedValue = value
}
return `"${key}" = ${formattedValue}`
})
.filter(Boolean)
.join(', ')
if (!setClauses) {
toast.error('No changes to save')
onSuccess()
return
}
const whereClauses = pks
.map((pk) => {
const value = row[pk]
const formattedValue =
typeof value === 'string' ? `'${value.replace(/'/g, "''")}'` : value
return `"${pk}" = ${formattedValue}`
})
.join(' AND ')
const updateSql = `UPDATE public."${table.name}" SET ${setClauses} WHERE ${whereClauses};`
runUpdateQuery({ projectRef, query: updateSql, readOnly: false }, { onSuccess })
},
[projectRef, table, row, runUpdateQuery, onSuccess]
)
return (
<div className="px-6 pt-4 pb-10 lg:px-12 lg:pt-10">
<h2 className="text-base lg:text-xl font-semibold mb-4">Editing row in {table.name}</h2>
<DynamicForm
schema={formSchema}
initialValues={row}
onSubmit={handleFormSubmit}
isLoading={isUpdatePending}
columnInfo={columnInfo}
/>
</div>
)
}
function TableRecordsView({ projectRef, table }: { projectRef: string; table: any }) {
const { push, pop } = useSheetNavigation()
const [refetchCounter, setRefetchCounter] = useState(0)
const handleRowClick = useCallback(
(row: any) => {
push({
title: `Editing row`,
component: (
<EditRowView
projectRef={projectRef}
table={table}
row={row}
onSuccess={() => {
pop()
setRefetchCounter((c) => c + 1)
}}
/>
),
})
},
[push, pop, projectRef, table]
)
return (
<SqlEditor
hideChartOption={true}
projectRef={projectRef}
label={`View records in ${table.name}`}
initialSql={`SELECT * FROM public."${table.name}" LIMIT 100;`}
queryKey={table.id}
onRowClick={handleRowClick}
hideSql={true}
runAutomatically={true}
refetch={refetchCounter}
readOnly
/>
)
}
export function DatabaseManager({ projectRef }: { projectRef: string }) {
const { push } = useSheetNavigation()
const { data: tables, isLoading, isError } = useListTables(projectRef, ['public'])
const handleTableClick = useCallback(
(table: any) => {
push({
title: table.name,
component: <TableRecordsView projectRef={projectRef} table={table} />,
})
},
[push, projectRef]
)
const handleNaturalLanguageQueryClick = useCallback(() => {
push({
title: 'Talk to your database',
component: (
<SqlEditor projectRef={projectRef} initialNaturalLanguageMode={true} hideSql={true} />
),
})
}, [push, projectRef])
return (
<div className="p-6 pt-4 lg:p-8 lg:pt-8">
<div className="flex items-center justify-between mb-6 gap-6">
<div className="flex-1">
<h1 className="text-base lg:text-xl font-semibold">Database</h1>
<p className="hidden lg:block text-sm lg:text-base text-muted-foreground mt-1">
View and manage the data stored in your app.
</p>
</div>
<Button
variant="outline"
key="talk-to-db"
className="flex-row justify-between"
onClick={handleNaturalLanguageQueryClick}
>
<Wand strokeWidth={1.5} size={16} />
Query your database
</Button>
</div>
{isError && (
<Alert variant="destructive" className="mt-8">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Error loading tables</AlertTitle>
<AlertDescription>There was a problem loading your database tables.</AlertDescription>
</Alert>
)}
{isLoading && (
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
</div>
)}
{tables && tables.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
{tables.map((table: any) => (
<Button
variant="outline"
key={table.id}
size="lg"
className="flex-row justify-between text-left"
onClick={() => handleTableClick(table)}
>
<Table className="h-4 w-4 text-muted-foreground" />
<h2 className="text-sm font-medium font-mono truncate flex-1">{table.name}</h2>
<div className="text-sm text-muted-foreground font-mono shrink-0">
{table.live_rows_estimate} rows
</div>
</Button>
))}
</div>
) : !isLoading && (!tables || tables.length === 0) ? (
<Alert className="mt-8">
<Table className="h-4 w-4" />
<AlertTitle>No database tables</AlertTitle>
<AlertDescription>Create tables to store and organize your data.</AlertDescription>
</Alert>
) : null}
</div>
)
}
registry/default/platform/platform-kit-nextjs/components/supabase-manager/index.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
ChevronLeft,
ChevronRight,
Database,
ExternalLink,
HardDrive,
KeyRound,
Lightbulb,
ScrollText,
Shield,
Users,
} from 'lucide-react'
import Link from 'next/link'
import { ReactNode, useMemo, useState } from 'react'
import { Button } from '@/registry/default/components/ui/button'
import {
Dialog,
DialogContent,
DialogTitle,
DialogTrigger,
} from '@/registry/default/components/ui/dialog'
import {
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
} from '@/registry/default/components/ui/drawer'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '@/registry/default/components/ui/hover-card'
import { LogoSupabase } from '@/registry/default/platform/platform-kit-nextjs/components/logo-supabase'
import { AuthManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/auth'
import { DatabaseManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/database'
import { LogsManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/logs'
import { SecretsManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/secrets'
import { StorageManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/storage'
import { SuggestionsManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/suggestions'
import { UsersManager } from '@/registry/default/platform/platform-kit-nextjs/components/supabase-manager/users'
import {
SheetNavigationProvider,
useSheetNavigation,
} from '@/registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext'
const queryClient = new QueryClient()
function DialogView({ projectRef, isMobile }: { projectRef: string; isMobile?: boolean }) {
const { stack, push, popTo, reset } = useSheetNavigation()
const handleTopLevelNavigation = (title: string, component: ReactNode) => {
if (stack.length === 1 && stack[0].title === title) {
return
}
reset()
push({ title, component })
}
const currentView = stack[stack.length - 1]
const activeManager = stack.length > 0 ? stack[0].title : null
const navigationItems = useMemo(
() => [
{
title: 'Database',
icon: Database,
component: <DatabaseManager projectRef={projectRef} />,
},
{
title: 'Storage',
icon: HardDrive,
component: <StorageManager projectRef={projectRef} />,
},
{
title: 'Auth',
icon: Shield,
component: <AuthManager projectRef={projectRef} />,
},
{
title: 'Users',
icon: Users,
component: <UsersManager projectRef={projectRef} />,
},
{
title: 'Secrets',
icon: KeyRound,
component: <SecretsManager projectRef={projectRef} />,
},
{
title: 'Logs',
icon: ScrollText,
component: <LogsManager projectRef={projectRef} />,
},
{
title: 'Suggestions',
icon: Lightbulb,
component: <SuggestionsManager projectRef={projectRef} />,
},
],
[projectRef]
)
if (isMobile) {
return (
<div className="flex flex-col h-full overflow-hidden">
{/* Content Area */}
<div className="flex flex-col overflow-hidden h-full">
<div className="grow overflow-y-auto">
{currentView ? (
currentView.component
) : (
<div className="flex items-center justify-center h-full">
<p className="text-muted-foreground">
Select a manager from the bottom navigation to get started.
</p>
</div>
)}
</div>
</div>
{/* Bottom Navigation Bar */}
<div className="border-t bg-background">
<div className="overflow-x-auto">
<div className="flex gap-1 p-2 min-w-max">
{navigationItems.map((item) => {
const Icon = item.icon
return (
<Button
key={item.title}
variant={activeManager === item.title ? 'secondary' : 'ghost'}
className="flex-col h-16 w-20 min-w-16 text-xs gap-1 px-2"
onClick={() => handleTopLevelNavigation(item.title, item.component)}
>
<Icon className="h-5 w-5" />
<span className="text-[10px] leading-tight text-center">
{item.title === 'Auth' ? 'Auth' : item.title}
</span>
</Button>
)
})}
</div>
</div>
</div>
</div>
)
}
return (
<div className="grid grid-cols-[240px_1fr] h-full overflow-hidden">
{/* Sidebar */}
<div className="flex flex-col border-r px-3 py-6 pb-3">
<div className="px-4 mb-4">
<h2 className="text-muted-foreground font-semibold text-sm">Manage your back-end</h2>
</div>
<div className="grow space-y-0.5">
{navigationItems.map((item) => {
const Icon = item.icon
return (
<Button
key={item.title}
variant={activeManager === item.title ? 'secondary' : 'ghost'}
className="w-full justify-start text-sm"
onClick={() => handleTopLevelNavigation(item.title, item.component)}
>
<Icon className="mr-2 text-muted-foreground" />
{item.title === 'Auth' ? 'Authentication' : item.title}
</Button>
)
})}
</div>
<footer className="p-0 text-sm text-muted-foreground flex items-center gap-3 -m-3 border-t">
<HoverCard>
<HoverCardTrigger asChild>
<Link
href={`https://supabase.com/dashboard/project/${projectRef}`}
target="_blank"
className="flex items-center px-4 w-full rounded-none text-sm py-4 h-auto justify-start gap-3 text-sm text-left hover:bg-accent"
>
<LogoSupabase size={16} />
<span className="flex-1">Open in Supabase</span>
<ExternalLink className="ml-2 h-4 w-4 text-muted-foreground/50" />
</Link>
</HoverCardTrigger>
<HoverCardContent
sideOffset={8}
align="start"
side="top"
className="text-sm bg-muted/50 w-[216px]"
>
<h4 className="font-semibold mb-1">About Supabase</h4>
<p className="text-muted-foreground">
Access powerful back-end tools for database, auth, storage, and logs directly in
Supabase.
</p>
</HoverCardContent>
</HoverCard>
</footer>
</div>
{/* Content Area */}
<div className="flex flex-col overflow-hidden h-full">
{/* Header with breadcrumbs */}
<div className="flex items-center h-12 shrink-0 px-4 relative border-b">
{stack.length > 1 && (
<Button
variant="outline"
size="icon"
className="h-8 w-8 relative z-10"
onClick={() => popTo(stack.length - 2)}
>
<ChevronLeft className="h-4 w-4" />
<span className="sr-only">Back</span>
</Button>
)}
{/* Breadcrumbs */}
<div className="ml-4 flex items-center gap-1.5 text-sm text-muted-foreground relative z-10">
{stack.map((item: { title: string }, index: number) => (
<div key={`${item.title}-${index}`} className="flex items-center gap-1.5">
{index > 0 && <ChevronRight className="h-3 w-3" />}
{index === stack.length - 1 ? (
<span className="font-semibold text-foreground">{item.title}</span>
) : (
<button
type="button"
tabIndex={0}
onClick={() => popTo(index)}
className="hover:underline"
>
{item.title}
</button>
)}
</div>
))}
</div>
</div>
<div className="grow overflow-y-auto">
{currentView ? (
currentView.component
) : (
<div className="flex items-center justify-center h-full">
<p className="text-muted-foreground">
Select a manager from the sidebar to get started.
</p>
</div>
)}
</div>
</div>
</div>
)
}
export default function SupabaseManagerDialog({
projectRef,
open,
onOpenChange,
isMobile,
}: {
projectRef: string
open: boolean
onOpenChange: (open: boolean) => void
isMobile: boolean
}) {
const content = (
<SheetNavigationProvider
onStackEmpty={() => {}}
initialStack={[
{
title: 'Database',
component: <DatabaseManager projectRef={projectRef} />,
},
]}
>
<DialogView projectRef={projectRef} isMobile={isMobile} />
</SheetNavigationProvider>
)
if (!isMobile) {
return (
<QueryClientProvider client={queryClient}>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full h-[80vh] max-h-[700px] sm:max-w-[calc(100%-2rem)] w-[1180px] p-0 overflow-hidden sm:rounded-lg">
<DialogTitle className="sr-only">Manage your back-end</DialogTitle>
{content}
</DialogContent>
</Dialog>
</QueryClientProvider>
)
}
return (
<QueryClientProvider client={queryClient}>
<Drawer open={open} onOpenChange={onOpenChange}>
<DrawerContent className="h-[90vh] p-0 overflow-hidden">
<DrawerHeader className="sr-only">
<DrawerTitle>Manage your back-end</DrawerTitle>
</DrawerHeader>
{content}
</DrawerContent>
</Drawer>
</QueryClientProvider>
)
}
registry/default/platform/platform-kit-nextjs/components/supabase-manager/logs.tsx
'use client'
import { Check, ChevronsUpDown, Logs, Terminal } from 'lucide-react'
import { useMemo, useState } from 'react'
import { genDefaultQuery, LogsTableName } from '../../lib/logs'
import { cn } from '@/lib/utils'
import { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'
import { Button } from '@/registry/default/components/ui/button'
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/registry/default/components/ui/command'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '@/registry/default/components/ui/hover-card'
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/components/ui/popover'
import { Skeleton } from '@/registry/default/components/ui/skeleton'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/registry/default/components/ui/table'
import { useGetLogs } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-logs'
// Define log types with names and descriptions
const logTypes = [
{
value: LogsTableName.FN_EDGE,
label: 'Function Edge Logs',
description: 'Edge function execution logs with request and response metadata',
},
{
value: LogsTableName.AUTH,
label: 'Authentication Logs',
description: 'User authentication events and security logs',
},
{
value: LogsTableName.POSTGRES,
label: 'PostgreSQL Logs',
description: 'Database queries, errors, and performance metrics',
},
{
value: LogsTableName.REALTIME,
label: 'Realtime Logs',
description: 'WebSocket connections and realtime subscriptions',
},
{
value: LogsTableName.STORAGE,
label: 'Storage Logs',
description: 'File uploads, downloads, and storage operations',
},
{
value: LogsTableName.PG_CRON,
label: 'Cron Job Logs',
description: 'Scheduled job executions and cron task logs',
},
{
value: LogsTableName.EDGE,
label: 'Edge Logs',
description: 'HTTP requests and responses from the data API',
},
{
value: LogsTableName.FUNCTIONS,
label: 'Function Logs',
description: 'Serverless function execution logs and events',
},
{
value: LogsTableName.POSTGREST,
label: 'PostgREST Logs',
description: 'API requests to your database through PostgREST',
},
{
value: LogsTableName.SUPAVISOR,
label: 'Supavisor Logs',
description: 'Connection pooling and database proxy logs',
},
{
value: LogsTableName.PGBOUNCER,
label: 'PgBouncer Logs',
description: 'Legacy connection pooling logs',
},
{
value: LogsTableName.PG_UPGRADE,
label: 'PostgreSQL Upgrade Logs',
description: 'Database upgrade processes and migration logs',
},
]
export function LogsManager({ projectRef }: { projectRef: string }) {
const [activeTab, setActiveTab] = useState<LogsTableName>(LogsTableName.FN_EDGE)
const [open, setOpen] = useState(false)
const sql = useMemo(() => genDefaultQuery(activeTab), [activeTab])
const { data: logs, isLoading, error } = useGetLogs(projectRef, { sql })
const selectedLogType = logTypes.find((type) => type.value === activeTab)
return (
<>
<div className="p-6 pt-4 lg:p-8 lg:pt-8 border-b bg-background z-10 sticky top-0 flex items-center justify-between mb-6">
<div className="flex-1">
<h1 className="text-base lg:text-xl font-semibold">Logs</h1>
<p className="hidden lg:block text-sm lg:text-base text-muted-foreground mt-1">
Debug errors and track activity in your app
</p>
</div>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-64 justify-between"
>
{selectedLogType ? selectedLogType.label : 'Select log type...'}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 p-0" align="end">
<Command>
<CommandInput placeholder="Search log types..." className="h-9" />
<CommandList
className="max-h-60 overflow-y-auto"
onWheel={(e) => {
e.stopPropagation()
}}
style={{ overscrollBehavior: 'contain' }}
>
<CommandEmpty>No log type found.</CommandEmpty>
<CommandGroup>
{logTypes.map((logType) => (
<CommandItem
key={logType.value}
value={logType.value}
onSelect={(currentValue) => {
setActiveTab(currentValue as LogsTableName)
setOpen(false)
}}
className="flex items-center gap-2 p-3"
>
<div className="flex-1">
<div className="text-xs mb-1 font-medium leading-none">{logType.label}</div>
<div className="text-xs text-muted-foreground leading-snug">
{logType.description}
</div>
</div>
<Check
className={cn(
'h-4 w-4 mt-0.5',
activeTab === logType.value ? 'opacity-100' : 'opacity-0'
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
{isLoading && (
<div className="space-y-2 mx-8 mt-8">
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
</div>
)}
{(error || (logs && logs.error)) && (
<div className="mx-6 lg:mx-8 mt-8">
<Alert variant="destructive">
<Terminal className="h-4 w-4" />
<AlertTitle>Error fetching logs</AlertTitle>
<AlertDescription>
{(error as any)?.message ||
(typeof logs?.error === 'object' && logs.error?.message) ||
'An unexpected error occurred. Please try again.'}
</AlertDescription>
</Alert>
</div>
)}
{logs && logs.result && logs.result.length > 0 && (
<div className="mt-4 overflow-auto w-full">
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent">
{Object.keys(logs.result[0] as object).map((key, idx, arr) => (
<TableHead
key={key}
className={
(idx === 0 ? 'first:pl-6 lg:first:pl-8 ' : '') +
(idx === arr.length - 1 ? 'last:pr-6 lg:last:pr-8 ' : '')
}
>
{key.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase())}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{(logs.result as any[]).map((log, index) => (
<TableRow key={log.id || index} className="group hover:bg-muted/50 relative">
{Object.keys(logs.result?.[0] ?? []).map((key, idx, arr) => {
const value = log[key]
const formattedValue = (() => {
if (key === 'timestamp' && typeof value === 'number') {
return new Date(value / 1000).toLocaleString()
}
if (value === null) {
return 'NULL'
}
return typeof value === 'object'
? JSON.stringify(value, null, 2)
: String(value)
})()
return (
<TableCell
key={key}
className="first:pl-6 lg:first:pl-8 last:pr-6 lg:last:pr-8 text-xs text-muted-foreground group-hover:text-foreground min-w-32"
>
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-xs font-mono w-fit max-w-96 truncate cursor-default">
{formattedValue}
</div>
</HoverCardTrigger>
<HoverCardContent className="w-96 max-h-96 overflow-auto p-3">
<pre className="text-xs font-mono whitespace-pre-wrap wrap-break-word">
{formattedValue}
</pre>
</HoverCardContent>
</HoverCard>
</TableCell>
)
})}
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
{logs && logs.result && logs.result.length === 0 && (
<div className="mt-8 mx-8">
<Alert>
<Logs className="h-4 w-4" />
<AlertTitle>No logs found</AlertTitle>
<AlertDescription>
Logs will appear here when your application generates activity.
</AlertDescription>
</Alert>
</div>
)}
</>
)
}
registry/default/platform/platform-kit-nextjs/components/supabase-manager/secrets.tsx
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import { AlertTriangle, Key, Minus, PlusIcon } from 'lucide-react'
import { useFieldArray, useForm } from 'react-hook-form'
import { z } from 'zod'
import { secretsSchema } from '../../lib/schemas/secrets'
import { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'
import { Button } from '@/registry/default/components/ui/button'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/registry/default/components/ui/form'
import { Input } from '@/registry/default/components/ui/input'
import { Skeleton } from '@/registry/default/components/ui/skeleton'
import {
useCreateSecrets,
useDeleteSecrets,
useGetSecrets,
} from '@/registry/default/platform/platform-kit-nextjs/hooks/use-secrets'
export function SecretsManager({ projectRef }: { projectRef: string }) {
const { data: secrets, isLoading, error } = useGetSecrets(projectRef)
const { mutate: createSecrets, isPending: isCreating } = useCreateSecrets()
const { mutate: deleteSecrets, isPending: isDeleting } = useDeleteSecrets()
const form = useForm<z.infer<typeof secretsSchema>>({
resolver: zodResolver(secretsSchema),
defaultValues: {
secrets: [{ name: '', value: '' }],
},
})
const { fields, append, remove } = useFieldArray({
control: form.control,
name: 'secrets',
})
const handleCreateSecrets = (formData: z.infer<typeof secretsSchema>) => {
createSecrets(
{
projectRef,
secrets: formData.secrets,
},
{
onSuccess: () => {
form.reset({ secrets: [{ name: '', value: '' }] })
},
}
)
}
const handleDeleteSecret = (secretName: string) => {
if (window.confirm(`Are you sure you want to delete the secret "${secretName}"?`)) {
deleteSecrets({
projectRef,
secretNames: [secretName],
})
}
}
if (isLoading) {
return (
<div className="w-full max-w-4xl mx-auto p-12 space-y-4">
<Skeleton className="h-48 w-full" />
<Skeleton className="h-16 w-full" />
<Skeleton className="h-16 w-full" />
</div>
)
}
if (error) {
return (
<div className="mx-6 lg:mx-8 mt-8">
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Error loading secrets</AlertTitle>
<AlertDescription>
There was a problem loading your secrets configuration.
</AlertDescription>
</Alert>
</div>
)
}
return (
<div className="w-full max-w-3xl mx-auto p-6 pt-4 lg:p-12 lg:pt-12">
<div className="mb-16">
<h2 className="text-base lg:text-xl font-semibold mb-1">Add New Secret</h2>
<p className="text-muted-foreground mb-6 text-sm lg:text-base">
Add sensitive information like API keys that your app needs to work securely.
</p>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleCreateSecrets)} className="space-y-2">
{fields.map((field, index) => (
<div key={field.id} className="flex items-start space-x-2">
<FormField
control={form.control}
name={`secrets.${index}.name`}
render={({ field }) => (
<FormItem className="flex-1">
{index === 0 && <FormLabel className="mb-4">Name</FormLabel>}
<FormControl>
<Input placeholder="SECRET_NAME" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`secrets.${index}.value`}
render={({ field }) => (
<FormItem className="flex-1">
{index === 0 && <FormLabel className="mb-4">Value</FormLabel>}
<FormControl>
<Input type="password" placeholder="••••••••" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="w-8">
{index === 0 && <FormLabel className="opacity-0 mb-4">Remove</FormLabel>}
<Button
type="button"
variant="outline"
size="icon"
onClick={() => remove(index)}
disabled={fields.length <= 1}
>
<Minus className="h-4 w-4" />
</Button>
</div>
</div>
))}
<div className="flex items-center space-x-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => append({ name: '', value: '' })}
>
<PlusIcon className="mr-2 h-4 w-4" />
Add Secret
</Button>
</div>
<Button type="submit" disabled={isCreating} className="mt-6">
{isCreating ? 'Creating...' : 'Create Secrets'}
</Button>
</form>
</Form>
</div>
<div>
<h2 className="font-semibold mb-4 lg:text-lg">Existing secrets</h2>
{secrets && secrets.length > 0 ? (
secrets.map((secret) => (
<div
key={secret.name}
className="flex items-center justify-between py-4 border-b last:border-b-0"
>
<div>
<p className="font-mono text-sm tracking-wider">{secret.name}</p>
{secret.updated_at && (
<p className="text-xs text-muted-foreground mt-1">
Last updated: {new Date(secret.updated_at).toLocaleString()}
</p>
)}
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleDeleteSecret(secret.name)}
disabled={isDeleting}
>
Delete
</Button>
</div>
))
) : (
<Alert>
<Key className="h-4 w-4" />
<AlertTitle>No secrets found</AlertTitle>
<AlertDescription>No secrets found for this project.</AlertDescription>
</Alert>
)}
</div>
</div>
)
}
registry/default/platform/platform-kit-nextjs/components/supabase-manager/storage.tsx
'use client'
import { AlertTriangle, Folder } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'
import { Badge } from '@/registry/default/components/ui/badge'
import { Button } from '@/registry/default/components/ui/button'
import { Skeleton } from '@/registry/default/components/ui/skeleton'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/registry/default/components/ui/tooltip'
import { useGetBuckets } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-storage'
export function StorageManager({ projectRef }: { projectRef: string }) {
const { data: buckets, isLoading, isError } = useGetBuckets(projectRef)
return (
<div className="p-6 pt-4 lg:p-8 lg:pt-8">
<h1 className="text-base lg:text-xl font-semibold">Storage</h1>
<p className="hidden lg:block text-sm lg:text-base text-muted-foreground mt-1">
View and manage the files stored in your app.
</p>
{isLoading && (
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
<Skeleton className="h-12 w-full" />
</div>
)}
{isError && (
<Alert variant="destructive" className="mt-8">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Error loading buckets</AlertTitle>
<AlertDescription>There was a problem loading your storage buckets.</AlertDescription>
</Alert>
)}
{buckets && buckets.length > 0 ? (
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{buckets.map((bucket: any) => (
<Tooltip key={bucket.id}>
<TooltipTrigger asChild>
<Button
key={bucket.id}
variant="outline"
className="flex-row justify-start text-left h-auto p-4 gap-4"
>
<Folder className="h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<h2 className="font-semibold mb-1">{bucket.name}</h2>
<p className="text-xs text-muted-foreground">
Updated {new Date(bucket.updated_at).toLocaleDateString()}
</p>
</div>
<Badge variant={bucket.public ? 'default' : 'secondary'}>
{bucket.public ? 'Public' : 'Private'}
</Badge>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Viewing files is coming soon</p>
</TooltipContent>
</Tooltip>
))}
</div>
) : buckets && buckets.length === 0 ? (
<Alert className="mt-8">
<Folder className="h-4 w-4" />
<AlertTitle>No storage buckets</AlertTitle>
<AlertDescription>
A bucket is a container used to store and protect files in your app.
</AlertDescription>
</Alert>
) : null}
</div>
)
}
registry/default/platform/platform-kit-nextjs/components/supabase-manager/suggestions.tsx
'use client'
import { Terminal } from 'lucide-react'
import { useMemo } from 'react'
import ReactMarkdown from 'react-markdown'
import { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'
import { Badge } from '@/registry/default/components/ui/badge'
import { Skeleton } from '@/registry/default/components/ui/skeleton'
import { useGetSuggestions } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-suggestions'
export function SuggestionsManager({ projectRef }: { projectRef: string }) {
const { data: suggestions, isLoading, error } = useGetSuggestions(projectRef)
const sortedSuggestions = useMemo(() => {
if (!suggestions) return []
const levelOrder = { ERROR: 1, WARN: 2, INFO: 3 }
return [...suggestions].sort((a: any, b: any) => {
const levelA = levelOrder[a.level as keyof typeof levelOrder] || 99
const levelB = levelOrder[b.level as keyof typeof levelOrder] || 99
return levelA - levelB
})
}, [suggestions])
const getBadgeVariant = (level: 'ERROR' | 'WARN' | 'INFO') => {
switch (level) {
case 'ERROR':
return 'destructive'
case 'WARN':
return 'secondary'
default:
return 'outline'
}
}
return (
<div className="p-6 pt-4 lg:p-12 lg:pt-12 max-w-3xl mx-auto">
<h2 className="text-base lg:text-xl font-semibold mb-1">Suggestions</h2>
<p className="text-muted-foreground mb-4 lg:mb-8 text-sm lg:text-base">
Improve your project's security and performance.
</p>
{isLoading && (
<div className="space-y-2">
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
</div>
)}
{error && (
<Alert variant="destructive" className="mt-8">
<Terminal className="h-4 w-4" />
<AlertTitle>Error fetching suggestions</AlertTitle>
<AlertDescription>
{(error as any)?.message || 'An unexpected error occurred. Please try again.'}
</AlertDescription>
</Alert>
)}
{suggestions && (
<div>
{sortedSuggestions.length > 0 ? (
<div>
{sortedSuggestions.map((suggestion: any) => (
<div
key={suggestion.cache_key}
className="py-4 border-b last:border-b-0 group relative"
>
<div className="flex-1">
<div className="flex justify-start items-start gap-4">
<h4 className="font-semibold text-sm">{suggestion.title}</h4>
<div className="flex items-center gap-1">
<Badge variant={getBadgeVariant(suggestion.level)} className="shrink-0">
{suggestion.level}
</Badge>
{suggestion.type && (
<Badge
variant={suggestion.type === 'security' ? 'destructive' : 'secondary'}
className="shrink-0"
>
{suggestion.type.charAt(0).toUpperCase() + suggestion.type.slice(1)}
</Badge>
)}
</div>
</div>
<div className="text-sm text-muted-foreground mt-2 prose prose-sm max-w-none">
<ReactMarkdown
components={{
code({ inline, children, ...props }: any) {
return inline ? (
<code className="bg-muted px-1 rounded-sm" {...props}>
{children}
</code>
) : (
<pre className="bg-muted p-2 rounded-sm overflow-x-auto" {...props}>
<code>{children}</code>
</pre>
)
},
}}
>
{suggestion.detail}
</ReactMarkdown>
</div>
</div>
</div>
))}
</div>
) : (
<Alert>
<Terminal className="h-4 w-4" />
<AlertTitle>No suggestions found</AlertTitle>
<AlertDescription>
Your project looks good! No suggestions at this time.
</AlertDescription>
</Alert>
)}
</div>
)}
</div>
)
}
registry/default/platform/platform-kit-nextjs/components/supabase-manager/users.tsx
'use client'
import { useState } from 'react'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/registry/default/components/ui/select'
import { SqlEditor } from '@/registry/default/platform/platform-kit-nextjs/components/sql-editor'
import { UsersGrowthChart } from '@/registry/default/platform/platform-kit-nextjs/components/users-growth-chart'
export function UsersManager({ projectRef }: { projectRef: string }) {
const [timeRange, setTimeRange] = useState(90)
const defaultSql = `SELECT * FROM auth.users ORDER BY created_at DESC LIMIT 100;`
return (
<div className="pb-8">
<div className="flex items-center justify-between p-6 pt-4 lg:p-8 lg:pt-8">
<div className="flex-1">
<h1 className="text-base lg:text-xl font-semibold">Users</h1>
<p className="hidden lg:block text-sm lg:text-base text-muted-foreground mt-1">
View user signups over time
</p>
</div>
<Select value={String(timeRange)} onValueChange={(value) => setTimeRange(Number(value))}>
<SelectTrigger className="w-[160px] rounded-lg sm:ml-auto" aria-label="Select a value">
<SelectValue placeholder="Select time range" />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="90" className="rounded-lg">
Last 90 days
</SelectItem>
<SelectItem value="30" className="rounded-lg">
Last 30 days
</SelectItem>
<SelectItem value="7" className="rounded-lg">
Last 7 days
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="px-8">
<UsersGrowthChart projectRef={projectRef} timeRange={timeRange} />
</div>
<SqlEditor
hideChartOption
projectRef={projectRef}
initialSql={defaultSql}
initialNaturalLanguageMode={true}
label="Recent users"
hideSql={true}
readOnly={true}
runAutomatically={true}
/>
</div>
)
}
registry/default/platform/platform-kit-nextjs/components/users-growth-chart.tsx
'use client'
import { AlertTriangle } from 'lucide-react'
import * as React from 'react'
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { Alert, AlertDescription, AlertTitle } from '@/registry/default/components/ui/alert'
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from '@/registry/default/components/ui/chart'
import { Skeleton } from '@/registry/default/components/ui/skeleton'
import { useGetUserCountsByDay } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-user-counts'
const chartConfig = {
users: {
label: 'New Users',
color: 'var(--chart-1)',
},
} satisfies ChartConfig
export function UsersGrowthChart({
projectRef,
timeRange,
}: {
projectRef: string
timeRange: number
}) {
const { data: chartData, isLoading, isError } = useGetUserCountsByDay(projectRef, timeRange)
return (
<div>
{isLoading && <Skeleton className="h-[250px] w-full" />}
{isError && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Error loading chart data</AlertTitle>
<AlertDescription>There was a problem loading your chart data.</AlertDescription>
</Alert>
)}
{chartData && !isLoading && (
<ChartContainer config={chartConfig} className="aspect-auto h-[250px] w-full">
<BarChart
accessibilityLayer
data={chartData}
margin={{
left: -24,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={32}
tickFormatter={(value) => {
const date = new Date(value)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})
}}
/>
<YAxis
tickLine={false}
axisLine={false}
tickMargin={8}
tickCount={5}
allowDecimals={false}
/>
<ChartTooltip
content={
<ChartTooltipContent
className="w-[150px]"
labelFormatter={(value) => {
return new Date(value).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
}}
indicator="dot"
/>
}
/>
<Bar dataKey="users" fill="var(--color-users)" />
</BarChart>
</ChartContainer>
)}
</div>
)
}
registry/default/platform/platform-kit-nextjs/hooks/use-auth.ts
'use client'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { type AxiosError } from 'axios'
import { toast } from 'sonner'
import { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'
import type { components } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api-schema'
const getAuthConfig = async (projectRef: string) => {
const { data, error } = await client.GET('/v1/projects/{ref}/config/auth', {
params: {
path: { ref: projectRef },
},
})
if (error) {
throw error
}
return data
}
export const useGetAuthConfig = (projectRef: string) => {
return useQuery({
queryKey: ['auth-config', projectRef],
queryFn: () => getAuthConfig(projectRef),
enabled: !!projectRef,
retry: false,
})
}
// UPDATE Auth Config
const updateAuthConfig = async ({
projectRef,
payload,
}: {
projectRef: string
payload: components['schemas']['UpdateAuthConfigBody']
}) => {
const { data, error } = await client.PATCH('/v1/projects/{ref}/config/auth', {
params: {
path: {
ref: projectRef,
},
},
body: payload,
})
if (error) {
throw error
}
return data
}
export const useUpdateAuthConfig = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: updateAuthConfig,
onSuccess: (data, variables) => {
toast.success(`Auth config updated.`)
queryClient.invalidateQueries({
queryKey: ['auth-config', variables.projectRef],
})
},
onError: (error: AxiosError<{ message: string }>) => {
toast.error(error.response?.data?.message || 'There was a problem with your request.')
},
})
}
registry/default/platform/platform-kit-nextjs/hooks/use-logs.ts
'use client'
import { useQuery } from '@tanstack/react-query'
import { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'
// GET Logs
const getLogs = async ({
projectRef,
iso_timestamp_start,
iso_timestamp_end,
sql,
}: {
projectRef: string
iso_timestamp_start?: string
iso_timestamp_end?: string
sql?: string
}) => {
const { data, error } = await client.GET('/v1/projects/{ref}/analytics/endpoints/logs.all', {
params: {
path: {
ref: projectRef,
},
query: {
iso_timestamp_start,
iso_timestamp_end,
sql,
},
},
})
if (error) {
throw error
}
return data
}
export const useGetLogs = (
projectRef: string,
params: {
iso_timestamp_start?: string
iso_timestamp_end?: string
sql?: string
} = {}
) => {
const queryKey = ['logs', projectRef, params.sql]
return useQuery({
queryKey: queryKey,
queryFn: () => {
const now = new Date()
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000)
const queryParams = {
sql: params.sql,
iso_timestamp_start: params.iso_timestamp_start ?? oneHourAgo.toISOString(),
iso_timestamp_end: params.iso_timestamp_end ?? now.toISOString(),
}
return getLogs({ projectRef, ...queryParams })
},
enabled: !!projectRef,
retry: false,
})
}
registry/default/platform/platform-kit-nextjs/hooks/use-run-query.ts
'use client'
import { useMutation } from '@tanstack/react-query'
import { type AxiosError } from 'axios'
import { toast } from 'sonner'
import { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'
// RUN SQL Query
export const runQuery = async ({
projectRef,
query,
readOnly,
}: {
projectRef: string
query: string
readOnly?: boolean
}) => {
const { data, error } = await client.POST('/v1/projects/{ref}/database/query', {
params: {
path: {
ref: projectRef,
},
},
body: {
query,
read_only: readOnly,
},
})
if (error) {
throw error
}
return data as any
}
export const useRunQuery = () => {
return useMutation({
mutationFn: runQuery,
onError: (error: AxiosError<{ message: string }>) => {
toast.error(error.response?.data?.message || 'There was a problem with your query.')
},
})
}
registry/default/platform/platform-kit-nextjs/hooks/use-secrets.ts
'use client'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { type AxiosError } from 'axios'
import { toast } from 'sonner'
import { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'
import type { components } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api-schema'
// GET Secrets
const getSecrets = async (projectRef: string) => {
const { data, error } = await client.GET('/v1/projects/{ref}/secrets', {
params: {
path: {
ref: projectRef,
},
},
})
if (error) {
throw error
}
return data
}
export const useGetSecrets = (projectRef: string) => {
return useQuery({
queryKey: ['secrets', projectRef],
queryFn: () => getSecrets(projectRef),
enabled: !!projectRef,
retry: false,
})
}
// CREATE Secrets
const createSecrets = async ({
projectRef,
secrets,
}: {
projectRef: string
secrets: components['schemas']['CreateSecretBody']
}) => {
const { data, error } = await client.POST('/v1/projects/{ref}/secrets', {
params: {
path: {
ref: projectRef,
},
},
body: secrets,
})
if (error) {
throw error
}
return data
}
export const useCreateSecrets = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: createSecrets,
onSuccess: (data, variables) => {
toast.success(`Secrets created successfully.`)
queryClient.refetchQueries({
queryKey: ['secrets', variables.projectRef],
})
},
onError: (error: AxiosError<{ message: string }>) => {
toast.error(error.response?.data?.message || 'There was a problem with your request.')
},
})
}
// DELETE Secrets
const deleteSecrets = async ({
projectRef,
secretNames,
}: {
projectRef: string
secretNames: string[]
}) => {
const { data, error } = await client.DELETE('/v1/projects/{ref}/secrets', {
params: {
path: {
ref: projectRef,
},
},
body: secretNames,
})
if (error) {
throw error
}
return data
}
export const useDeleteSecrets = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: deleteSecrets,
onSuccess: (data, variables) => {
toast.success(`Secrets deleted successfully.`)
queryClient.invalidateQueries({
queryKey: ['secrets', variables.projectRef],
})
},
onError: (error: AxiosError<{ message: string }>) => {
toast.error(error.response?.data?.message || 'There was a problem with your request.')
},
})
}
registry/default/platform/platform-kit-nextjs/hooks/use-storage.ts
'use client'
import { useQuery } from '@tanstack/react-query'
import { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'
// GET Buckets
const getBuckets = async (projectRef: string) => {
const { data, error } = await client.GET('/v1/projects/{ref}/storage/buckets', {
params: {
path: {
ref: projectRef,
},
},
})
if (error) {
throw error
}
return data
}
export const useGetBuckets = (projectRef: string) => {
return useQuery({
queryKey: ['buckets', projectRef],
queryFn: () => getBuckets(projectRef),
enabled: !!projectRef,
retry: false,
})
}
// LIST Objects
const listObjects = async ({ projectRef, bucketId }: { projectRef: string; bucketId: string }) => {
const { data, error } = await client.POST(
// TODO
// @ts-expect-error this endpoint is not yet implemented
'/v1/projects/{ref}/storage/buckets/{bucketId}/objects/list',
{
params: {
path: {
ref: projectRef,
bucketId,
},
},
body: {
path: '',
options: { limit: 100, offset: 0 },
},
}
)
if (error) {
throw error
}
return data as any
}
export const useListObjects = (projectRef: string, bucketId: string) => {
return useQuery({
queryKey: ['objects', projectRef, bucketId],
queryFn: () => listObjects({ projectRef, bucketId }),
enabled: !!projectRef && !!bucketId,
})
}
registry/default/platform/platform-kit-nextjs/hooks/use-suggestions.ts
'use client'
import { useQuery } from '@tanstack/react-query'
import { client } from '@/registry/default/platform/platform-kit-nextjs/lib/management-api'
// GET Suggestions
const getSuggestions = async (projectRef: string) => {
const [
{ data: performanceData, error: performanceError },
{ data: securityData, error: securityError },
] = await Promise.all([
client.GET('/v1/projects/{ref}/advisors/performance', {
params: {
path: {
ref: projectRef,
},
},
}),
client.GET('/v1/projects/{ref}/advisors/security', {
params: {
path: {
ref: projectRef,
},
},
}),
])
if (performanceError) {
throw performanceError
}
if (securityError) {
throw securityError
}
// Add type to each suggestion
const performanceLints = (performanceData?.lints || []).map((lint) => ({
...lint,
type: 'performance' as const,
}))
const securityLints = (securityData?.lints || []).map((lint) => ({
...lint,
type: 'security' as const,
}))
return [...performanceLints, ...securityLints]
}
export const useGetSuggestions = (projectRef: string) => {
return useQuery({
queryKey: ['suggestions', projectRef],
queryFn: () => getSuggestions(projectRef),
enabled: !!projectRef,
retry: false,
})
}
registry/default/platform/platform-kit-nextjs/hooks/use-tables.ts
'use client'
import { useQuery } from '@tanstack/react-query'
import { runQuery } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-run-query'
import { listTablesSql } from '@/registry/default/platform/platform-kit-nextjs/lib/pg-meta'
// LIST Tables
const listTables = ({ projectRef, schemas }: { projectRef: string; schemas?: string[] }) => {
const sql = listTablesSql(schemas)
return runQuery({
projectRef,
query: sql,
readOnly: true,
})
}
export const useListTables = (projectRef: string, schemas?: string[]) => {
return useQuery({
queryKey: ['tables', projectRef, schemas],
queryFn: () => listTables({ projectRef, schemas }),
enabled: !!projectRef,
})
}
registry/default/platform/platform-kit-nextjs/hooks/use-user-counts.ts
'use client'
import { useQuery } from '@tanstack/react-query'
import { runQuery } from '@/registry/default/platform/platform-kit-nextjs/hooks/use-run-query'
// GET User Counts by day
const getUserCountsByDay = ({ projectRef, days }: { projectRef: string; days: number }) => {
const sql = `
WITH days_series AS (
SELECT generate_series(
date_trunc('day', now() - interval '${Number(days) - 1} days'),
date_trunc('day', now()),
'1 day'::interval
)::date AS date
)
SELECT
d.date,
COALESCE(u.users, 0)::int as users
FROM
days_series d
LEFT JOIN (
SELECT
date_trunc('day', created_at AT TIME ZONE 'UTC')::date as date,
count(id) as users
FROM
auth.users
GROUP BY 1
) u ON d.date = u.date
ORDER BY
d.date ASC;
`
return runQuery({
projectRef,
query: sql,
readOnly: true,
})
}
export const useGetUserCountsByDay = (projectRef: string, days: number) => {
return useQuery({
queryKey: ['user-counts', projectRef, days],
queryFn: () => getUserCountsByDay({ projectRef, days }),
enabled: !!projectRef,
retry: false,
})
}
registry/default/platform/platform-kit-nextjs/lib/logs.ts
export enum LogsTableName {
FN_EDGE = 'function_edge_logs',
AUTH = 'auth_logs',
POSTGRES = 'postgres_logs',
REALTIME = 'realtime_logs',
STORAGE = 'storage_logs',
PG_CRON = 'pg_cron_logs',
EDGE = 'edge_logs',
FUNCTIONS = 'function_logs',
POSTGREST = 'postgrest_logs',
SUPAVISOR = 'supavisor_logs',
PGBOUNCER = 'pgbouncer_logs',
PG_UPGRADE = 'pg_upgrade_logs',
}
const genCrossJoinUnnests = (table: LogsTableName) => {
switch (table) {
case LogsTableName.EDGE:
return `cross join unnest(metadata) as m
cross join unnest(m.request) as request
cross join unnest(m.response) as response`
case LogsTableName.POSTGRES:
return `cross join unnest(metadata) as m
cross join unnest(m.parsed) as parsed`
case LogsTableName.FUNCTIONS:
return `cross join unnest(metadata) as metadata`
case LogsTableName.AUTH:
return `cross join unnest(metadata) as metadata`
case LogsTableName.FN_EDGE:
return `cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request`
case LogsTableName.SUPAVISOR:
return `cross join unnest(metadata) as m`
default:
return ''
}
}
export const genDefaultQuery = (table: LogsTableName, limit: number = 100) => {
const joins = genCrossJoinUnnests(table)
const orderBy = 'order by timestamp desc'
switch (table) {
case LogsTableName.EDGE:
return `select id, ${table}.timestamp, event_message, request.method, request.path, request.search, response.status_code
from ${table}
${joins}
${orderBy}
limit ${limit}`
case LogsTableName.POSTGRES:
return `select ${table}.timestamp, id, event_message, parsed.error_severity, parsed.detail, parsed.hint
from ${table}
${joins}
${orderBy}
limit ${limit}`
case LogsTableName.FUNCTIONS:
return `select id, ${table}.timestamp, event_message, metadata.event_type, metadata.function_id, metadata.level
from ${table}
${joins}
${orderBy}
limit ${limit}`
case LogsTableName.AUTH:
return `select id, ${table}.timestamp, event_message, metadata.level, metadata.status, metadata.path, metadata.msg as msg, metadata.error
from ${table}
${joins}
${orderBy}
limit ${limit}`
case LogsTableName.FN_EDGE:
return `select id, ${table}.timestamp, event_message, response.status_code, request.method, m.function_id, m.execution_time_ms, m.deployment_id, m.version
from ${table}
${joins}
${orderBy}
limit ${limit}`
case LogsTableName.SUPAVISOR:
return `select id, ${table}.timestamp, event_message
from ${table}
${joins}
${orderBy}
limit ${limit}`
case LogsTableName.PG_UPGRADE:
return `select id, ${table}.timestamp, event_message
from ${table}
${joins}
${orderBy}
limit ${limit}`
case LogsTableName.PG_CRON:
return `select postgres_logs.timestamp, id, event_message, parsed.error_severity, parsed.query
from postgres_logs
cross join unnest(metadata) as m
cross join unnest(m.parsed) as parsed
where (parsed.application_name = 'pg_cron' OR event_message LIKE '%cron job%')
${orderBy}
limit ${limit}`
default:
return `select id, ${table}.timestamp, event_message
from ${table}
${joins}
${orderBy}
limit ${limit}`
}
}
registry/default/platform/platform-kit-nextjs/lib/management-api-schema.d.ts
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
'/v1/branches/{branch_id_or_ref}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Get database branch config
* @description Fetches configurations of the specified database branch
*/
get: operations['v1-get-a-branch-config']
put?: never
post?: never
/**
* Delete a database branch
* @description Deletes the specified database branch
*/
delete: operations['v1-delete-a-branch']
options?: never
head?: never
/**
* Update database branch config
* @description Updates the configuration of the specified database branch
*/
patch: operations['v1-update-a-branch-config']
trace?: never
}
'/v1/branches/{branch_id_or_ref}/push': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/**
* Pushes a database branch
* @description Pushes the specified database branch
*/
post: operations['v1-push-a-branch']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/branches/{branch_id_or_ref}/merge': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/**
* Merges a database branch
* @description Merges the specified database branch
*/
post: operations['v1-merge-a-branch']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/branches/{branch_id_or_ref}/reset': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/**
* Resets a database branch
* @description Resets the specified database branch
*/
post: operations['v1-reset-a-branch']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/branches/{branch_id_or_ref}/diff': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* [Beta] Diffs a database branch
* @description Diffs the specified database branch
*/
get: operations['v1-diff-a-branch']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* List all projects
* @description Returns a list of all projects you've previously created.
*/
get: operations['v1-list-all-projects']
put?: never
/** Create a project */
post: operations['v1-create-a-project']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/organizations': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* List all organizations
* @description Returns a list of organizations that you currently belong to.
*/
get: operations['v1-list-all-organizations']
put?: never
/** Create an organization */
post: operations['v1-create-an-organization']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/oauth/authorize': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Beta] Authorize user through oauth */
get: operations['v1-authorize-user']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/oauth/token': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Exchange auth code for user's access and refresh token */
post: operations['v1-exchange-oauth-token']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/oauth/revoke': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Revoke oauth app authorization and it's corresponding tokens */
post: operations['v1-revoke-token']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/oauth/authorize/project-claim': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Authorize user through oauth and claim a project
* @description Initiates the OAuth authorization flow for the specified provider. After successful authentication, the user can claim ownership of the specified project.
*/
get: operations['v1-oauth-authorize-project-claim']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/snippets': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Lists SQL snippets for the logged in user */
get: operations['v1-list-all-snippets']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/snippets/{id}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets a specific SQL snippet */
get: operations['v1-get-a-snippet']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/api-keys': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Get project api keys */
get: operations['v1-get-project-api-keys']
put?: never
/** Creates a new API key for the project */
post: operations['v1-create-project-api-key']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/api-keys/legacy': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Check whether JWT based legacy (anon, service_role) API keys are enabled. This API endpoint will be removed in the future, check for HTTP 404 Not Found. */
get: operations['v1-get-project-legacy-api-keys']
/** Disable or re-enable JWT based legacy (anon, service_role) API keys. This API endpoint will be removed in the future, check for HTTP 404 Not Found. */
put: operations['v1-update-project-legacy-api-keys']
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/api-keys/{id}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Get API key */
get: operations['v1-get-project-api-key']
put?: never
post?: never
/** Deletes an API key for the project */
delete: operations['v1-delete-project-api-key']
options?: never
head?: never
/** Updates an API key for the project */
patch: operations['v1-update-project-api-key']
trace?: never
}
'/v1/projects/{ref}/branches': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* List all database branches
* @description Returns all database branches of the specified project.
*/
get: operations['v1-list-all-branches']
put?: never
/**
* Create a database branch
* @description Creates a database branch from the specified project.
*/
post: operations['v1-create-a-branch']
/**
* Disables preview branching
* @description Disables preview branching for the specified project
*/
delete: operations['v1-disable-preview-branching']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/custom-hostname': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Beta] Gets project's custom hostname config */
get: operations['v1-get-hostname-config']
put?: never
post?: never
/** [Beta] Deletes a project's custom hostname configuration */
delete: operations['v1-Delete hostname config']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/custom-hostname/initialize': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Updates project's custom hostname configuration */
post: operations['v1-update-hostname-config']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/custom-hostname/reverify': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Attempts to verify the DNS configuration for project's custom hostname configuration */
post: operations['v1-verify-dns-config']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/custom-hostname/activate': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Activates a custom hostname for a project. */
post: operations['v1-activate-custom-hostname']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/network-bans/retrieve': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Gets project's network bans */
post: operations['v1-list-all-network-bans']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/network-bans/retrieve/enriched': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Gets project's network bans with additional information about which databases they affect */
post: operations['v1-list-all-network-bans-enriched']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/network-bans': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
post?: never
/** [Beta] Remove network bans. */
delete: operations['v1-delete-network-bans']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/network-restrictions': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Beta] Gets project's network restrictions */
get: operations['v1-get-network-restrictions']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/network-restrictions/apply': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Updates project's network restrictions */
post: operations['v1-update-network-restrictions']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/pgsodium': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Beta] Gets project's pgsodium config */
get: operations['v1-get-pgsodium-config']
/** [Beta] Updates project's pgsodium config. Updating the root_key can cause all data encrypted with the older key to become inaccessible. */
put: operations['v1-update-pgsodium-config']
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/postgrest': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project's postgrest config */
get: operations['v1-get-postgrest-service-config']
put?: never
post?: never
delete?: never
options?: never
head?: never
/** Updates project's postgrest config */
patch: operations['v1-update-postgrest-service-config']
trace?: never
}
'/v1/projects/{ref}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets a specific project that belongs to the authenticated user */
get: operations['v1-get-project']
put?: never
post?: never
/** Deletes the given project */
delete: operations['v1-delete-a-project']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/secrets': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* List all secrets
* @description Returns all secrets you've previously added to the specified project.
*/
get: operations['v1-list-all-secrets']
put?: never
/**
* Bulk create secrets
* @description Creates multiple secrets and adds them to the specified project.
*/
post: operations['v1-bulk-create-secrets']
/**
* Bulk delete secrets
* @description Deletes all secrets with the given names from the specified project
*/
delete: operations['v1-bulk-delete-secrets']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/ssl-enforcement': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Beta] Get project's SSL enforcement configuration. */
get: operations['v1-get-ssl-enforcement-config']
/** [Beta] Update project's SSL enforcement configuration. */
put: operations['v1-update-ssl-enforcement-config']
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/types/typescript': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Generate TypeScript types
* @description Returns the TypeScript types of your schema for use with supabase-js.
*/
get: operations['v1-generate-typescript-types']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/vanity-subdomain': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Beta] Gets current vanity subdomain config */
get: operations['v1-get-vanity-subdomain-config']
put?: never
post?: never
/** [Beta] Deletes a project's vanity subdomain configuration */
delete: operations['v1-deactivate-vanity-subdomain-config']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/vanity-subdomain/check-availability': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Checks vanity subdomain availability */
post: operations['v1-check-vanity-subdomain-availability']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/vanity-subdomain/activate': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Activates a vanity subdomain for a project. */
post: operations['v1-activate-vanity-subdomain-config']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/upgrade': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Upgrades the project's Postgres version */
post: operations['v1-upgrade-postgres-version']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/upgrade/eligibility': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Beta] Returns the project's eligibility for upgrades */
get: operations['v1-get-postgres-upgrade-eligibility']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/upgrade/status': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Beta] Gets the latest status of the project's upgrade */
get: operations['v1-get-postgres-upgrade-status']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/readonly': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Returns project's readonly mode status */
get: operations['v1-get-readonly-mode-status']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/readonly/temporary-disable': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** Disables project's readonly mode for the next 15 minutes */
post: operations['v1-disable-readonly-mode-temporarily']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/read-replicas/setup': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Set up a read replica */
post: operations['v1-setup-a-read-replica']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/read-replicas/remove': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Remove a read replica */
post: operations['v1-remove-a-read-replica']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/health': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project's service health status */
get: operations['v1-get-services-health']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/config/auth/signing-keys': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Alpha] List all signing keys for the project */
get: operations['v1-get-project-signing-keys']
put?: never
/** [Alpha] Create a new signing key for the project in standby status */
post: operations['v1-create-project-signing-key']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/config/auth/signing-keys/{id}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** [Alpha] Get information about a signing key */
get: operations['v1-get-project-signing-key']
put?: never
post?: never
/** [Alpha] Remove a signing key from a project, where the status is previously_used */
delete: operations['v1-remove-project-signing-key']
options?: never
head?: never
/** [Alpha] Update a signing key, mainly its status */
patch: operations['v1-update-project-signing-key']
trace?: never
}
'/v1/projects/{ref}/config/storage': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project's storage config */
get: operations['v1-get-storage-config']
put?: never
post?: never
delete?: never
options?: never
head?: never
/** Updates project's storage config */
patch: operations['v1-update-storage-config']
trace?: never
}
'/v1/projects/{ref}/config/database/postgres': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project's Postgres config */
get: operations['v1-get-postgres-config']
/** Updates project's Postgres config */
put: operations['v1-update-postgres-config']
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/config/database/pgbouncer': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Get project's pgbouncer config */
get: operations['v1-get-project-pgbouncer-config']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/config/database/pooler': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project's supavisor config */
get: operations['v1-get-pooler-config']
put?: never
post?: never
delete?: never
options?: never
head?: never
/** Updates project's supavisor config */
patch: operations['v1-update-pooler-config']
trace?: never
}
'/v1/projects/{ref}/config/auth': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project's auth config */
get: operations['v1-get-auth-service-config']
put?: never
post?: never
delete?: never
options?: never
head?: never
/** Updates a project's auth config */
patch: operations['v1-update-auth-service-config']
trace?: never
}
'/v1/projects/{ref}/config/auth/third-party-auth': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Lists all third-party auth integrations */
get: operations['v1-list-project-tpa-integrations']
put?: never
/** Creates a new third-party auth integration */
post: operations['v1-create-project-tpa-integration']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/config/auth/third-party-auth/{tpa_id}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Get a third-party integration */
get: operations['v1-get-project-tpa-integration']
put?: never
post?: never
/** Removes a third-party auth integration */
delete: operations['v1-delete-project-tpa-integration']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/pause': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** Pauses the given project */
post: operations['v1-pause-a-project']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/restore': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Lists available restore versions for the given project */
get: operations['v1-list-available-restore-versions']
put?: never
/** Restores the given project */
post: operations['v1-restore-a-project']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/restore/cancel': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** Cancels the given project restoration */
post: operations['v1-cancel-a-project-restoration']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/billing/addons': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Lists project addons */
get: operations['v1-list-project-addons']
put?: never
post?: never
delete?: never
options?: never
head?: never
/** Applies project addon */
patch: operations['v1-apply-project-addon']
trace?: never
}
'/v1/projects/{ref}/billing/addons/{addon_variant}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
post?: never
/** Removes project addon */
delete: operations['v1-remove-project-addon']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/claim-token': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project claim token */
get: operations['v1-get-project-claim-token']
put?: never
/** Creates project claim token */
post: operations['v1-create-project-claim-token']
/** Revokes project claim token */
delete: operations['v1-delete-project-claim-token']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/advisors/performance': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Gets project performance advisors.
* @deprecated
* @description This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.
*/
get: operations['v1-get-performance-advisors']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/advisors/security': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Gets project security advisors.
* @deprecated
* @description This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.
*/
get: operations['v1-get-security-advisors']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/analytics/endpoints/logs.all': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Gets project's logs
* @description Executes a SQL query on the project's logs.
*
* Either the 'iso_timestamp_start' and 'iso_timestamp_end' parameters must be provided.
* If both are not provided, only the last 1 minute of logs will be queried.
* The timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.
*
*/
get: operations['v1-get-project-logs']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/analytics/endpoints/usage.api-counts': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project's usage api counts */
get: operations['v1-get-project-usage-api-count']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/analytics/endpoints/usage.api-requests-count': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project's usage api requests count */
get: operations['v1-get-project-usage-request-count']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/database/migrations': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* [Beta] List applied migration versions
* @description Only available to selected partner OAuth apps
*/
get: operations['v1-list-migration-history']
/**
* [Beta] Upsert a database migration without applying
* @description Only available to selected partner OAuth apps
*/
put: operations['v1-upsert-a-migration']
/**
* [Beta] Apply a database migration
* @description Only available to selected partner OAuth apps
*/
post: operations['v1-apply-a-migration']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/database/query': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Run sql query */
post: operations['v1-run-a-query']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/database/webhooks/enable': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** [Beta] Enables Database Webhooks on the project */
post: operations['v1-enable-database-webhook']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/database/context': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Gets database metadata for the given project.
* @deprecated
* @description This is an **experimental** endpoint. It is subject to change or removal in future versions. Use it with caution, as it may not remain supported or stable.
*/
get: operations['v1-get-database-metadata']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/functions': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* List all functions
* @description Returns all functions you've previously added to the specified project.
*/
get: operations['v1-list-all-functions']
/**
* Bulk update functions
* @description Bulk update functions. It will create a new function or replace existing. The operation is idempotent. NOTE: You will need to manually bump the version.
*/
put: operations['v1-bulk-update-functions']
/**
* Create a function
* @deprecated
* @description This endpoint is deprecated - use the deploy endpoint. Creates a function and adds it to the specified project.
*/
post: operations['v1-create-a-function']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/functions/deploy': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/**
* Deploy a function
* @description A new endpoint to deploy functions. It will create if function does not exist.
*/
post: operations['v1-deploy-a-function']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/functions/{function_slug}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Retrieve a function
* @description Retrieves a function with the specified slug and project.
*/
get: operations['v1-get-a-function']
put?: never
post?: never
/**
* Delete a function
* @description Deletes a function with the specified slug from the specified project.
*/
delete: operations['v1-delete-a-function']
options?: never
head?: never
/**
* Update a function
* @description Updates a function with the specified slug and project.
*/
patch: operations['v1-update-a-function']
trace?: never
}
'/v1/projects/{ref}/functions/{function_slug}/body': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/**
* Retrieve a function body
* @description Retrieves a function body for the specified slug and project.
*/
get: operations['v1-get-a-function-body']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/storage/buckets': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Lists all buckets */
get: operations['v1-list-all-buckets']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/config/auth/sso/providers': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Lists all SSO providers */
get: operations['v1-list-all-sso-provider']
put?: never
/** Creates a new SSO provider */
post: operations['v1-create-a-sso-provider']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/config/auth/sso/providers/{provider_id}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets a SSO provider by its UUID */
get: operations['v1-get-a-sso-provider']
/** Updates a SSO provider by its UUID */
put: operations['v1-update-a-sso-provider']
post?: never
/** Removes a SSO provider by its UUID */
delete: operations['v1-delete-a-sso-provider']
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/database/backups': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Lists all backups */
get: operations['v1-list-all-backups']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/database/backups/restore-pitr': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** Restores a PITR backup for a database */
post: operations['v1-restore-pitr-backup']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/database/backups/restore-point': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Get restore points for project */
get: operations['v1-get-restore-point']
put?: never
/** Initiates a creation of a restore point for a database */
post: operations['v1-create-restore-point']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/projects/{ref}/database/backups/undo': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
get?: never
put?: never
/** Initiates an undo to a given restore point */
post: operations['v1-undo']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/organizations/{slug}/members': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** List members of an organization */
get: operations['v1-list-organization-members']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/organizations/{slug}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets information about the organization */
get: operations['v1-get-an-organization']
put?: never
post?: never
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
'/v1/organizations/{slug}/project-claim/{token}': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
/** Gets project details for the specified organization and claim token */
get: operations['v1-get-organization-project-claim']
put?: never
/** Claims project for the specified organization */
post: operations['v1-claim-project-for-organization']
delete?: never
options?: never
head?: never
patch?: never
trace?: never
}
}
export type webhooks = Record<string, never>
export interface components {
schemas: {
BranchDetailResponse: {
ref: string
postgres_version: string
postgres_engine: string
release_channel: string
/** @enum {string} */
status:
| 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
| 'UNKNOWN'
| 'GOING_DOWN'
| 'INIT_FAILED'
| 'REMOVED'
| 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
| 'RESTORE_FAILED'
| 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
db_host: string
db_port: number
db_user?: string
db_pass?: string
jwt_secret?: string
}
UpdateBranchBody: {
branch_name?: string
git_branch?: string
/**
* @deprecated
* @description This field is deprecated and will be ignored. Use v1-reset-a-branch endpoint directly instead.
*/
reset_on_push?: boolean
persistent?: boolean
/** @enum {string} */
status?:
| 'CREATING_PROJECT'
| 'RUNNING_MIGRATIONS'
| 'MIGRATIONS_PASSED'
| 'MIGRATIONS_FAILED'
| 'FUNCTIONS_DEPLOYED'
| 'FUNCTIONS_FAILED'
}
BranchResponse: {
id: string
name: string
project_ref: string
parent_project_ref: string
is_default: boolean
git_branch?: string
/** Format: int32 */
pr_number?: number
/**
* @deprecated
* @description This field is deprecated and will not be populated.
*/
latest_check_run_id?: number
persistent: boolean
/** @enum {string} */
status:
| 'CREATING_PROJECT'
| 'RUNNING_MIGRATIONS'
| 'MIGRATIONS_PASSED'
| 'MIGRATIONS_FAILED'
| 'FUNCTIONS_DEPLOYED'
| 'FUNCTIONS_FAILED'
created_at: string
updated_at: string
}
BranchDeleteResponse: {
/** @enum {string} */
message: 'ok'
}
BranchActionBody: {
migration_version?: string
}
BranchUpdateResponse: {
workflow_run_id: string
/** @enum {string} */
message: 'ok'
}
V1ProjectWithDatabaseResponse: {
/** @description Id of your project */
id: string
/** @description Slug of your organization */
organization_id: string
/** @description Name of your project */
name: string
/**
* @description Region of your project
* @example us-east-1
*/
region: string
/**
* @description Creation timestamp
* @example 2023-03-29T16:32:59Z
*/
created_at: string
/** @enum {string} */
status:
| 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
| 'UNKNOWN'
| 'GOING_DOWN'
| 'INIT_FAILED'
| 'REMOVED'
| 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
| 'RESTORE_FAILED'
| 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
database: {
/** @description Database host */
host: string
/** @description Database version */
version: string
/** @description Database engine */
postgres_engine: string
/** @description Release channel */
release_channel: string
}
}
V1CreateProjectBody: {
/** @description Database password */
db_pass: string
/** @description Name of your project */
name: string
/** @description Slug of your organization */
organization_id: string
/**
* @deprecated
* @description Subscription Plan is now set on organization level and is ignored in this request
* @enum {string}
*/
plan?: 'free' | 'pro'
/**
* @description Region you want your server to reside in
* @enum {string}
*/
region:
| 'us-east-1'
| 'us-east-2'
| 'us-west-1'
| 'us-west-2'
| 'ap-east-1'
| 'ap-southeast-1'
| 'ap-northeast-1'
| 'ap-northeast-2'
| 'ap-southeast-2'
| 'eu-west-1'
| 'eu-west-2'
| 'eu-west-3'
| 'eu-north-1'
| 'eu-central-1'
| 'eu-central-2'
| 'ca-central-1'
| 'ap-south-1'
| 'sa-east-1'
/**
* @deprecated
* @description This field is deprecated and is ignored in this request
*/
kps_enabled?: boolean
/** @enum {string} */
desired_instance_size?:
| 'pico'
| 'nano'
| 'micro'
| 'small'
| 'medium'
| 'large'
| 'xlarge'
| '2xlarge'
| '4xlarge'
| '8xlarge'
| '12xlarge'
| '16xlarge'
| '24xlarge'
| '24xlarge_optimized_memory'
| '24xlarge_optimized_cpu'
| '24xlarge_high_memory'
| '48xlarge'
| '48xlarge_optimized_memory'
| '48xlarge_optimized_cpu'
| '48xlarge_high_memory'
/**
* Format: uri
* @description Template URL used to create the project from the CLI.
* @example https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone
*/
template_url?: string
}
V1ProjectResponse: {
/** @description Id of your project */
id: string
/** @description Slug of your organization */
organization_id: string
/** @description Name of your project */
name: string
/**
* @description Region of your project
* @example us-east-1
*/
region: string
/**
* @description Creation timestamp
* @example 2023-03-29T16:32:59Z
*/
created_at: string
/** @enum {string} */
status:
| 'INACTIVE'
| 'ACTIVE_HEALTHY'
| 'ACTIVE_UNHEALTHY'
| 'COMING_UP'
| 'UNKNOWN'
| 'GOING_DOWN'
| 'INIT_FAILED'
| 'REMOVED'
| 'RESTORING'
| 'UPGRADING'
| 'PAUSING'
| 'RESTORE_FAILED'
| 'RESTARTING'
| 'PAUSE_FAILED'
| 'RESIZING'
}
OrganizationResponseV1: {
id: string
name: string
}
CreateOrganizationV1: {
name: string
}
OAuthTokenBody: {
/** @enum {string} */
grant_type?: 'authorization_code' | 'refresh_token'
/** Format: uuid */
client_id?: string
client_secret?: string
code?: string
code_verifier?: string
redirect_uri?: string
refresh_token?: string
}
OAuthTokenResponse: {
access_token: string
refresh_token: string
expires_in: number
/** @enum {string} */
token_type: 'Bearer'
}
OAuthRevokeTokenBody: {
/** Format: uuid */
client_id: string
client_secret: string
refresh_token: string
}
SnippetList: {
data: {
id: string
inserted_at: string
updated_at: string
/** @enum {string} */
type: 'sql'
/** @enum {string} */
visibility: 'user' | 'project' | 'org' | 'public'
name: string
description: string | null
project: {
id: number
name: string
}
owner: {
id: number
username: string
}
updated_by: {
id: number
username: string
}
}[]
cursor?: string
}
SnippetResponse: {
id: string
inserted_at: string
updated_at: string
/** @enum {string} */
type: 'sql'
/** @enum {string} */
visibility: 'user' | 'project' | 'org' | 'public'
name: string
description: string | null
project: {
id: number
name: string
}
owner: {
id: number
username: string
}
updated_by: {
id: number
username: string
}
content: {
favorite: boolean
schema_version: string
sql: string
}
}
ApiKeyResponse: {
api_key: string
id?: string | null
/** @enum {string|null} */
type?: 'publishable' | 'secret' | 'legacy' | null
prefix?: string | null
name: string
description?: string | null
hash?: string | null
secret_jwt_template?: {
role: string
} | null
/** Format: date-time */
inserted_at?: string | null
/** Format: date-time */
updated_at?: string | null
}
LegacyApiKeysResponse: {
enabled: boolean
}
CreateApiKeyBody: {
/** @enum {string} */
type: 'publishable' | 'secret'
name: string
description?: string | null
secret_jwt_template?: {
role: string
} | null
}
UpdateApiKeyBody: {
name?: string
description?: string | null
secret_jwt_template?: {
role: string
} | null
}
CreateBranchBody: {
branch_name: string
git_branch?: string
persistent?: boolean
region?: string
/** @enum {string} */
desired_instance_size?:
| 'pico'
| 'nano'
| 'micro'
| 'small'
| 'medium'
| 'large'
| 'xlarge'
| '2xlarge'
| '4xlarge'
| '8xlarge'
| '12xlarge'
| '16xlarge'
| '24xlarge'
| '24xlarge_optimized_memory'
| '24xlarge_optimized_cpu'
| '24xlarge_high_memory'
| '48xlarge'
| '48xlarge_optimized_memory'
| '48xlarge_optimized_cpu'
| '48xlarge_high_memory'
/**
* @description Release channel. If not provided, GA will be used.
* @enum {string}
*/
release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'
/**
* @description Postgres engine version. If not provided, the latest version will be used.
* @enum {string}
*/
postgres_engine?: '15' | '17' | '17-oriole'
secrets?: {
[key: string]: string
}
}
UpdateCustomHostnameResponse: {
/** @enum {string} */
status:
| '1_not_started'
| '2_initiated'
| '3_challenge_verified'
| '4_origin_setup_completed'
| '5_services_reconfigured'
custom_hostname: string
data: {
success: boolean
errors: unknown[]
messages: unknown[]
result: {
id: string
hostname: string
ssl: {
status: string
validation_records: {
txt_name: string
txt_value: string
}[]
validation_errors?: {
message: string
}[]
}
ownership_verification: {
type: string
name: string
value: string
}
custom_origin_server: string
verification_errors?: string[]
status: string
}
}
}
UpdateCustomHostnameBody: {
custom_hostname: string
}
NetworkBanResponse: {
banned_ipv4_addresses: string[]
}
NetworkBanResponseEnriched: {
banned_ipv4_addresses: {
banned_address: string
identifier: string
type: string
}[]
}
RemoveNetworkBanRequest: {
ipv4_addresses: string[]
identifier?: string
}
NetworkRestrictionsResponse: {
/** @enum {string} */
entitlement: 'disallowed' | 'allowed'
/** @description At any given point in time, this is the config that the user has requested be applied to their project. The `status` field indicates if it has been applied to the project, or is pending. When an updated config is received, the applied config is moved to `old_config`. */
config: {
dbAllowedCidrs?: string[]
dbAllowedCidrsV6?: string[]
}
/** @description Populated when a new config has been received, but not registered as successfully applied to a project. */
old_config?: {
dbAllowedCidrs?: string[]
dbAllowedCidrsV6?: string[]
}
/** @enum {string} */
status: 'stored' | 'applied'
}
NetworkRestrictionsRequest: {
dbAllowedCidrs?: string[]
dbAllowedCidrsV6?: string[]
}
PgsodiumConfigResponse: {
root_key: string
}
UpdatePgsodiumConfigBody: {
root_key: string
}
PostgrestConfigWithJWTSecretResponse: {
db_schema: string
max_rows: number
db_extra_search_path: string
/** @description If `null`, the value is automatically configured based on compute size. */
db_pool: number | null
jwt_secret?: string
}
V1UpdatePostgrestConfigBody: {
db_extra_search_path?: string
db_schema?: string
max_rows?: number
db_pool?: number
}
V1PostgrestConfigResponse: {
db_schema: string
max_rows: number
db_extra_search_path: string
/** @description If `null`, the value is automatically configured based on compute size. */
db_pool: number | null
}
V1ProjectRefResponse: {
id: number
ref: string
name: string
}
SecretResponse: {
name: string
value: string
updated_at?: string
}
CreateSecretBody: {
/**
* @description Secret name must not start with the SUPABASE_ prefix.
* @example string
*/
name: string
value: string
}[]
SslEnforcementResponse: {
currentConfig: {
database: boolean
}
appliedSuccessfully: boolean
}
SslEnforcementRequest: {
requestedConfig: {
database: boolean
}
}
TypescriptResponse: {
types: string
}
VanitySubdomainConfigResponse: {
/** @enum {string} */
status: 'not-used' | 'custom-domain-used' | 'active'
custom_domain?: string
}
VanitySubdomainBody: {
vanity_subdomain: string
}
SubdomainAvailabilityResponse: {
available: boolean
}
ActivateVanitySubdomainResponse: {
custom_domain: string
}
UpgradeDatabaseBody: {
target_version: string
/** @enum {string} */
release_channel?: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'
}
ProjectUpgradeInitiateResponse: {
tracking_id: string
}
ProjectUpgradeEligibilityResponse: {
eligible: boolean
current_app_version: string
/** @enum {string} */
current_app_version_release_channel:
| 'internal'
| 'alpha'
| 'beta'
| 'ga'
| 'withdrawn'
| 'preview'
latest_app_version: string
target_upgrade_versions: {
/** @enum {string} */
postgres_version: '15' | '17' | '17-oriole'
/** @enum {string} */
release_channel: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'
app_version: string
}[]
potential_breaking_changes: string[]
duration_estimate_hours: number
legacy_auth_custom_roles: string[]
extension_dependent_objects: string[]
}
DatabaseUpgradeStatusResponse: {
databaseUpgradeStatus: {
initiated_at: string
latest_status_at: string
target_version: number
/** @enum {string} */
error?:
| '1_upgraded_instance_launch_failed'
| '2_volume_detachchment_from_upgraded_instance_failed'
| '3_volume_attachment_to_original_instance_failed'
| '4_data_upgrade_initiation_failed'
| '5_data_upgrade_completion_failed'
| '6_volume_detachchment_from_original_instance_failed'
| '7_volume_attachment_to_upgraded_instance_failed'
| '8_upgrade_completion_failed'
| '9_post_physical_backup_failed'
/** @enum {string} */
progress?:
| '0_requested'
| '1_started'
| '2_launched_upgraded_instance'
| '3_detached_volume_from_upgraded_instance'
| '4_attached_volume_to_original_instance'
| '5_initiated_data_upgrade'
| '6_completed_data_upgrade'
| '7_detached_volume_from_original_instance'
| '8_attached_volume_to_upgraded_instance'
| '9_completed_upgrade'
| '10_completed_post_physical_backup'
status: number
} | null
}
ReadOnlyStatusResponse: {
enabled: boolean
override_enabled: boolean
override_active_until: string
}
SetUpReadReplicaBody: {
/**
* @description Region you want your read replica to reside in
* @example us-east-1
* @enum {string}
*/
read_replica_region:
| 'us-east-1'
| 'us-east-2'
| 'us-west-1'
| 'us-west-2'
| 'ap-east-1'
| 'ap-southeast-1'
| 'ap-northeast-1'
| 'ap-northeast-2'
| 'ap-southeast-2'
| 'eu-west-1'
| 'eu-west-2'
| 'eu-west-3'
| 'eu-north-1'
| 'eu-central-1'
| 'eu-central-2'
| 'ca-central-1'
| 'ap-south-1'
| 'sa-east-1'
}
RemoveReadReplicaBody: {
database_identifier: string
}
V1ServiceHealthResponse: {
/** @enum {string} */
name: 'auth' | 'db' | 'pooler' | 'realtime' | 'rest' | 'storage'
healthy: boolean
/** @enum {string} */
status: 'COMING_UP' | 'ACTIVE_HEALTHY' | 'UNHEALTHY'
info?:
| {
/** @enum {string} */
name: 'GoTrue'
version: string
description: string
}
| {
healthy: boolean
db_connected: boolean
connected_cluster: number
}
error?: string
}
CreateSigningKeyBody: {
/** @enum {string} */
algorithm: 'EdDSA' | 'ES256' | 'RS256' | 'HS256'
/** @enum {string} */
status?: 'in_use' | 'standby'
}
SigningKeyResponse: {
/** Format: uuid */
id: string
/** @enum {string} */
algorithm: 'EdDSA' | 'ES256' | 'RS256' | 'HS256'
/** @enum {string} */
status: 'in_use' | 'previously_used' | 'revoked' | 'standby'
public_jwk?: unknown
/** Format: date-time */
created_at: string
/** Format: date-time */
updated_at: string
}
SigningKeysResponse: {
keys: {
/** Format: uuid */
id: string
/** @enum {string} */
algorithm: 'EdDSA' | 'ES256' | 'RS256' | 'HS256'
/** @enum {string} */
status: 'in_use' | 'previously_used' | 'revoked' | 'standby'
public_jwk?: unknown
/** Format: date-time */
created_at: string
/** Format: date-time */
updated_at: string
}[]
}
UpdateSigningKeyBody: {
/** @enum {string} */
status: 'in_use' | 'previously_used' | 'revoked' | 'standby'
}
StorageConfigResponse: {
/** Format: int64 */
fileSizeLimit: number
features: {
imageTransformation: {
enabled: boolean
}
s3Protocol: {
enabled: boolean
}
}
}
UpdateStorageConfigBody: {
/** Format: int64 */
fileSizeLimit?: number
features?: {
imageTransformation: {
enabled: boolean
}
s3Protocol: {
enabled: boolean
}
}
}
PostgresConfigResponse: {
effective_cache_size?: string
logical_decoding_work_mem?: string
maintenance_work_mem?: string
track_activity_query_size?: string
max_connections?: number
max_locks_per_transaction?: number
max_parallel_maintenance_workers?: number
max_parallel_workers?: number
max_parallel_workers_per_gather?: number
max_replication_slots?: number
max_slot_wal_keep_size?: string
max_standby_archive_delay?: string
max_standby_streaming_delay?: string
max_wal_size?: string
max_wal_senders?: number
max_worker_processes?: number
/** @enum {string} */
session_replication_role?: 'origin' | 'replica' | 'local'
shared_buffers?: string
statement_timeout?: string
track_commit_timestamp?: boolean
wal_keep_size?: string
wal_sender_timeout?: string
work_mem?: string
}
UpdatePostgresConfigBody: {
effective_cache_size?: string
logical_decoding_work_mem?: string
maintenance_work_mem?: string
track_activity_query_size?: string
max_connections?: number
max_locks_per_transaction?: number
max_parallel_maintenance_workers?: number
max_parallel_workers?: number
max_parallel_workers_per_gather?: number
max_replication_slots?: number
max_slot_wal_keep_size?: string
max_standby_archive_delay?: string
max_standby_streaming_delay?: string
max_wal_size?: string
max_wal_senders?: number
max_worker_processes?: number
/** @enum {string} */
session_replication_role?: 'origin' | 'replica' | 'local'
shared_buffers?: string
statement_timeout?: string
track_commit_timestamp?: boolean
wal_keep_size?: string
wal_sender_timeout?: string
work_mem?: string
restart_database?: boolean
}
V1PgbouncerConfigResponse: {
default_pool_size?: number
ignore_startup_parameters?: string
max_client_conn?: number
/** @enum {string} */
pool_mode?: 'transaction' | 'session' | 'statement'
connection_string?: string
}
SupavisorConfigResponse: {
identifier: string
/** @enum {string} */
database_type: 'PRIMARY' | 'READ_REPLICA'
is_using_scram_auth: boolean
db_user: string
db_host: string
db_port: number
db_name: string
connection_string: string
/** @description Use connection_string instead */
connectionString: string
default_pool_size: number | null
max_client_conn: number | null
/** @enum {string} */
pool_mode: 'transaction' | 'session'
}
UpdateSupavisorConfigBody: {
default_pool_size?: number | null
/**
* @description Dedicated pooler mode for the project
* @enum {string}
*/
pool_mode?: 'transaction' | 'session'
}
UpdateSupavisorConfigResponse: {
default_pool_size: number | null
pool_mode: string
}
AuthConfigResponse: {
api_max_request_duration: number | null
db_max_pool_size: number | null
disable_signup: boolean | null
external_anonymous_users_enabled: boolean | null
external_apple_additional_client_ids: string | null
external_apple_client_id: string | null
external_apple_enabled: boolean | null
external_apple_secret: string | null
external_azure_client_id: string | null
external_azure_enabled: boolean | null
external_azure_secret: string | null
external_azure_url: string | null
external_bitbucket_client_id: string | null
external_bitbucket_enabled: boolean | null
external_bitbucket_secret: string | null
external_discord_client_id: string | null
external_discord_enabled: boolean | null
external_discord_secret: string | null
external_email_enabled: boolean | null
external_facebook_client_id: string | null
external_facebook_enabled: boolean | null
external_facebook_secret: string | null
external_figma_client_id: string | null
external_figma_enabled: boolean | null
external_figma_secret: string | null
external_github_client_id: string | null
external_github_enabled: boolean | null
external_github_secret: string | null
external_gitlab_client_id: string | null
external_gitlab_enabled: boolean | null
external_gitlab_secret: string | null
external_gitlab_url: string | null
external_google_additional_client_ids: string | null
external_google_client_id: string | null
external_google_enabled: boolean | null
external_google_secret: string | null
external_google_skip_nonce_check: boolean | null
external_kakao_client_id: string | null
external_kakao_enabled: boolean | null
external_kakao_secret: string | null
external_keycloak_client_id: string | null
external_keycloak_enabled: boolean | null
external_keycloak_secret: string | null
external_keycloak_url: string | null
external_linkedin_oidc_client_id: string | null
external_linkedin_oidc_enabled: boolean | null
external_linkedin_oidc_secret: string | null
external_slack_oidc_client_id: string | null
external_slack_oidc_enabled: boolean | null
external_slack_oidc_secret: string | null
external_notion_client_id: string | null
external_notion_enabled: boolean | null
external_notion_secret: string | null
external_phone_enabled: boolean | null
external_slack_client_id: string | null
external_slack_enabled: boolean | null
external_slack_secret: string | null
external_spotify_client_id: string | null
external_spotify_enabled: boolean | null
external_spotify_secret: string | null
external_twitch_client_id: string | null
external_twitch_enabled: boolean | null
external_twitch_secret: string | null
external_twitter_client_id: string | null
external_twitter_enabled: boolean | null
external_twitter_secret: string | null
external_workos_client_id: string | null
external_workos_enabled: boolean | null
external_workos_secret: string | null
external_workos_url: string | null
external_web3_solana_enabled: boolean | null
external_zoom_client_id: string | null
external_zoom_enabled: boolean | null
external_zoom_secret: string | null
hook_custom_access_token_enabled: boolean | null
hook_custom_access_token_uri: string | null
hook_custom_access_token_secrets: string | null
hook_mfa_verification_attempt_enabled: boolean | null
hook_mfa_verification_attempt_uri: string | null
hook_mfa_verification_attempt_secrets: string | null
hook_password_verification_attempt_enabled: boolean | null
hook_password_verification_attempt_uri: string | null
hook_password_verification_attempt_secrets: string | null
hook_send_sms_enabled: boolean | null
hook_send_sms_uri: string | null
hook_send_sms_secrets: string | null
hook_send_email_enabled: boolean | null
hook_send_email_uri: string | null
hook_send_email_secrets: string | null
hook_before_user_created_enabled: boolean | null
hook_before_user_created_uri: string | null
hook_before_user_created_secrets: string | null
jwt_exp: number | null
mailer_allow_unverified_email_sign_ins: boolean | null
mailer_autoconfirm: boolean | null
mailer_otp_exp: number
mailer_otp_length: number | null
mailer_secure_email_change_enabled: boolean | null
mailer_subjects_confirmation: string | null
mailer_subjects_email_change: string | null
mailer_subjects_invite: string | null
mailer_subjects_magic_link: string | null
mailer_subjects_reauthentication: string | null
mailer_subjects_recovery: string | null
mailer_templates_confirmation_content: string | null
mailer_templates_email_change_content: string | null
mailer_templates_invite_content: string | null
mailer_templates_magic_link_content: string | null
mailer_templates_reauthentication_content: string | null
mailer_templates_recovery_content: string | null
mfa_max_enrolled_factors: number | null
mfa_totp_enroll_enabled: boolean | null
mfa_totp_verify_enabled: boolean | null
mfa_phone_enroll_enabled: boolean | null
mfa_phone_verify_enabled: boolean | null
mfa_web_authn_enroll_enabled: boolean | null
mfa_web_authn_verify_enabled: boolean | null
mfa_phone_otp_length: number
mfa_phone_template: string | null
mfa_phone_max_frequency: number | null
password_hibp_enabled: boolean | null
password_min_length: number | null
/** @enum {string|null} */
password_required_characters:
| 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'
| 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'
| 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\'\\\\:"|<>?,./`~'
| ''
| null
rate_limit_anonymous_users: number | null
rate_limit_email_sent: number | null
rate_limit_sms_sent: number | null
rate_limit_token_refresh: number | null
rate_limit_verify: number | null
rate_limit_otp: number | null
rate_limit_web3: number | null
refresh_token_rotation_enabled: boolean | null
saml_enabled: boolean | null
saml_external_url: string | null
saml_allow_encrypted_assertions: boolean | null
security_captcha_enabled: boolean | null
/** @enum {string|null} */
security_captcha_provider: 'turnstile' | 'hcaptcha' | null
security_captcha_secret: string | null
security_manual_linking_enabled: boolean | null
security_refresh_token_reuse_interval: number | null
security_update_password_require_reauthentication: boolean | null
security_update_password_require_current_password: boolean | null
sessions_inactivity_timeout: number | null
sessions_single_per_user: boolean | null
sessions_tags: string | null
sessions_timebox: number | null
site_url: string | null
sms_autoconfirm: boolean | null
sms_max_frequency: number | null
sms_messagebird_access_key: string | null
sms_messagebird_originator: string | null
sms_otp_exp: number | null
sms_otp_length: number
/** @enum {string|null} */
sms_provider: 'messagebird' | 'textlocal' | 'twilio' | 'twilio_verify' | 'vonage' | null
sms_template: string | null
sms_test_otp: string | null
/** Format: date-time */
sms_test_otp_valid_until: string | null
sms_textlocal_api_key: string | null
sms_textlocal_sender: string | null
sms_twilio_account_sid: string | null
sms_twilio_auth_token: string | null
sms_twilio_content_sid: string | null
sms_twilio_message_service_sid: string | null
sms_twilio_verify_account_sid: string | null
sms_twilio_verify_auth_token: string | null
sms_twilio_verify_message_service_sid: string | null
sms_vonage_api_key: string | null
sms_vonage_api_secret: string | null
sms_vonage_from: string | null
smtp_admin_email: string | null
smtp_host: string | null
smtp_max_frequency: number | null
smtp_pass: string | null
smtp_port: string | null
smtp_sender_name: string | null
smtp_user: string | null
uri_allow_list: string | null
}
UpdateAuthConfigBody: {
site_url?: string | null
disable_signup?: boolean | null
jwt_exp?: number | null
smtp_admin_email?: string | null
smtp_host?: string | null
smtp_port?: string | null
smtp_user?: string | null
smtp_pass?: string | null
smtp_max_frequency?: number | null
smtp_sender_name?: string | null
mailer_allow_unverified_email_sign_ins?: boolean | null
mailer_autoconfirm?: boolean | null
mailer_subjects_invite?: string | null
mailer_subjects_confirmation?: string | null
mailer_subjects_recovery?: string | null
mailer_subjects_email_change?: string | null
mailer_subjects_magic_link?: string | null
mailer_subjects_reauthentication?: string | null
mailer_templates_invite_content?: string | null
mailer_templates_confirmation_content?: string | null
mailer_templates_recovery_content?: string | null
mailer_templates_email_change_content?: string | null
mailer_templates_magic_link_content?: string | null
mailer_templates_reauthentication_content?: string | null
mfa_max_enrolled_factors?: number | null
uri_allow_list?: string | null
external_anonymous_users_enabled?: boolean | null
external_email_enabled?: boolean | null
external_phone_enabled?: boolean | null
saml_enabled?: boolean | null
saml_external_url?: string | null
security_captcha_enabled?: boolean | null
/** @enum {string|null} */
security_captcha_provider?: 'turnstile' | 'hcaptcha' | null
security_captcha_secret?: string | null
sessions_timebox?: number | null
sessions_inactivity_timeout?: number | null
sessions_single_per_user?: boolean | null
sessions_tags?: string | null
rate_limit_anonymous_users?: number | null
rate_limit_email_sent?: number | null
rate_limit_sms_sent?: number | null
rate_limit_verify?: number | null
rate_limit_token_refresh?: number | null
rate_limit_otp?: number | null
rate_limit_web3?: number | null
mailer_secure_email_change_enabled?: boolean | null
refresh_token_rotation_enabled?: boolean | null
password_hibp_enabled?: boolean | null
password_min_length?: number | null
/** @enum {string|null} */
password_required_characters?:
| 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'
| 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'
| 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\'\\\\:"|<>?,./`~'
| ''
| null
security_manual_linking_enabled?: boolean | null
security_update_password_require_reauthentication?: boolean | null
security_update_password_require_current_password?: boolean | null
security_refresh_token_reuse_interval?: number | null
mailer_otp_exp?: number
mailer_otp_length?: number | null
sms_autoconfirm?: boolean | null
sms_max_frequency?: number | null
sms_otp_exp?: number | null
sms_otp_length?: number
/** @enum {string|null} */
sms_provider?: 'messagebird' | 'textlocal' | 'twilio' | 'twilio_verify' | 'vonage' | null
sms_messagebird_access_key?: string | null
sms_messagebird_originator?: string | null
sms_test_otp?: string | null
/** Format: date-time */
sms_test_otp_valid_until?: string | null
sms_textlocal_api_key?: string | null
sms_textlocal_sender?: string | null
sms_twilio_account_sid?: string | null
sms_twilio_auth_token?: string | null
sms_twilio_content_sid?: string | null
sms_twilio_message_service_sid?: string | null
sms_twilio_verify_account_sid?: string | null
sms_twilio_verify_auth_token?: string | null
sms_twilio_verify_message_service_sid?: string | null
sms_vonage_api_key?: string | null
sms_vonage_api_secret?: string | null
sms_vonage_from?: string | null
sms_template?: string | null
hook_mfa_verification_attempt_enabled?: boolean | null
hook_mfa_verification_attempt_uri?: string | null
hook_mfa_verification_attempt_secrets?: string | null
hook_password_verification_attempt_enabled?: boolean | null
hook_password_verification_attempt_uri?: string | null
hook_password_verification_attempt_secrets?: string | null
hook_custom_access_token_enabled?: boolean | null
hook_custom_access_token_uri?: string | null
hook_custom_access_token_secrets?: string | null
hook_send_sms_enabled?: boolean | null
hook_send_sms_uri?: string | null
hook_send_sms_secrets?: string | null
hook_send_email_enabled?: boolean | null
hook_send_email_uri?: string | null
hook_send_email_secrets?: string | null
hook_before_user_created_enabled?: boolean | null
hook_before_user_created_uri?: string | null
hook_before_user_created_secrets?: string | null
external_apple_enabled?: boolean | null
external_apple_client_id?: string | null
external_apple_secret?: string | null
external_apple_additional_client_ids?: string | null
external_azure_enabled?: boolean | null
external_azure_client_id?: string | null
external_azure_secret?: string | null
external_azure_url?: string | null
external_bitbucket_enabled?: boolean | null
external_bitbucket_client_id?: string | null
external_bitbucket_secret?: string | null
external_discord_enabled?: boolean | null
external_discord_client_id?: string | null
external_discord_secret?: string | null
external_facebook_enabled?: boolean | null
external_facebook_client_id?: string | null
external_facebook_secret?: string | null
external_figma_enabled?: boolean | null
external_figma_client_id?: string | null
external_figma_secret?: string | null
external_github_enabled?: boolean | null
external_github_client_id?: string | null
external_github_secret?: string | null
external_gitlab_enabled?: boolean | null
external_gitlab_client_id?: string | null
external_gitlab_secret?: string | null
external_gitlab_url?: string | null
external_google_enabled?: boolean | null
external_google_client_id?: string | null
external_google_secret?: string | null
external_google_additional_client_ids?: string | null
external_google_skip_nonce_check?: boolean | null
external_kakao_enabled?: boolean | null
external_kakao_client_id?: string | null
external_kakao_secret?: string | null
external_keycloak_enabled?: boolean | null
external_keycloak_client_id?: string | null
external_keycloak_secret?: string | null
external_keycloak_url?: string | null
external_linkedin_oidc_enabled?: boolean | null
external_linkedin_oidc_client_id?: string | null
external_linkedin_oidc_secret?: string | null
external_slack_oidc_enabled?: boolean | null
external_slack_oidc_client_id?: string | null
external_slack_oidc_secret?: string | null
external_notion_enabled?: boolean | null
external_notion_client_id?: string | null
external_notion_secret?: string | null
external_slack_enabled?: boolean | null
external_slack_client_id?: string | null
external_slack_secret?: string | null
external_spotify_enabled?: boolean | null
external_spotify_client_id?: string | null
external_spotify_secret?: string | null
external_twitch_enabled?: boolean | null
external_twitch_client_id?: string | null
external_twitch_secret?: string | null
external_twitter_enabled?: boolean | null
external_twitter_client_id?: string | null
external_twitter_secret?: string | null
external_workos_enabled?: boolean | null
external_workos_client_id?: string | null
external_workos_secret?: string | null
external_workos_url?: string | null
external_web3_solana_enabled?: boolean | null
external_zoom_enabled?: boolean | null
external_zoom_client_id?: string | null
external_zoom_secret?: string | null
db_max_pool_size?: number | null
api_max_request_duration?: number | null
mfa_totp_enroll_enabled?: boolean | null
mfa_totp_verify_enabled?: boolean | null
mfa_web_authn_enroll_enabled?: boolean | null
mfa_web_authn_verify_enabled?: boolean | null
mfa_phone_enroll_enabled?: boolean | null
mfa_phone_verify_enabled?: boolean | null
mfa_phone_max_frequency?: number | null
mfa_phone_otp_length?: number | null
mfa_phone_template?: string | null
}
CreateThirdPartyAuthBody: {
oidc_issuer_url?: string
jwks_url?: string
custom_jwks?: unknown
}
ThirdPartyAuth: {
/** Format: uuid */
id: string
type: string
oidc_issuer_url?: string | null
jwks_url?: string | null
custom_jwks?: unknown
resolved_jwks?: unknown
inserted_at: string
updated_at: string
resolved_at?: string | null
}
GetProjectAvailableRestoreVersionsResponse: {
available_versions: {
version: string
/** @enum {string} */
release_channel: 'internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview'
/** @enum {string} */
postgres_engine: '13' | '14' | '15' | '17' | '17-oriole'
}[]
}
ListProjectAddonsResponse: {
selected_addons: {
/** @enum {string} */
type:
| 'custom_domain'
| 'compute_instance'
| 'pitr'
| 'ipv4'
| 'auth_mfa_phone'
| 'auth_mfa_web_authn'
| 'log_drain'
variant: {
id:
| (
| 'ci_micro'
| 'ci_small'
| 'ci_medium'
| 'ci_large'
| 'ci_xlarge'
| 'ci_2xlarge'
| 'ci_4xlarge'
| 'ci_8xlarge'
| 'ci_12xlarge'
| 'ci_16xlarge'
| 'ci_24xlarge'
| 'ci_24xlarge_optimized_cpu'
| 'ci_24xlarge_optimized_memory'
| 'ci_24xlarge_high_memory'
| 'ci_48xlarge'
| 'ci_48xlarge_optimized_cpu'
| 'ci_48xlarge_optimized_memory'
| 'ci_48xlarge_high_memory'
)
| 'cd_default'
| ('pitr_7' | 'pitr_14' | 'pitr_28')
| 'ipv4_default'
| 'auth_mfa_phone_default'
| 'auth_mfa_web_authn_default'
| 'log_drain_default'
name: string
price: {
description: string
/** @enum {string} */
type: 'fixed' | 'usage'
/** @enum {string} */
interval: 'monthly' | 'hourly'
amount: number
}
/** @description Any JSON-serializable value */
meta?: unknown
}
}[]
available_addons: {
/** @enum {string} */
type:
| 'custom_domain'
| 'compute_instance'
| 'pitr'
| 'ipv4'
| 'auth_mfa_phone'
| 'auth_mfa_web_authn'
| 'log_drain'
name: string
variants: {
id:
| (
| 'ci_micro'
| 'ci_small'
| 'ci_medium'
| 'ci_large'
| 'ci_xlarge'
| 'ci_2xlarge'
| 'ci_4xlarge'
| 'ci_8xlarge'
| 'ci_12xlarge'
| 'ci_16xlarge'
| 'ci_24xlarge'
| 'ci_24xlarge_optimized_cpu'
| 'ci_24xlarge_optimized_memory'
| 'ci_24xlarge_high_memory'
| 'ci_48xlarge'
| 'ci_48xlarge_optimized_cpu'
| 'ci_48xlarge_optimized_memory'
| 'ci_48xlarge_high_memory'
)
| 'cd_default'
| ('pitr_7' | 'pitr_14' | 'pitr_28')
| 'ipv4_default'
| 'auth_mfa_phone_default'
| 'auth_mfa_web_authn_default'
| 'log_drain_default'
name: string
price: {
description: string
/** @enum {string} */
type: 'fixed' | 'usage'
/** @enum {string} */
interval: 'monthly' | 'hourly'
amount: number
}
/** @description Any JSON-serializable value */
meta?: unknown
}[]
}[]
}
ApplyProjectAddonBody: {
addon_variant:
| (
| 'ci_micro'
| 'ci_small'
| 'ci_medium'
| 'ci_large'
| 'ci_xlarge'
| 'ci_2xlarge'
| 'ci_4xlarge'
| 'ci_8xlarge'
| 'ci_12xlarge'
| 'ci_16xlarge'
| 'ci_24xlarge'
| 'ci_24xlarge_optimized_cpu'
| 'ci_24xlarge_optimized_memory'
| 'ci_24xlarge_high_memory'
| 'ci_48xlarge'
| 'ci_48xlarge_optimized_cpu'
| 'ci_48xlarge_optimized_memory'
| 'ci_48xlarge_high_memory'
)
| 'cd_default'
| ('pitr_7' | 'pitr_14' | 'pitr_28')
| 'ipv4_default'
/** @enum {string} */
addon_type:
| 'custom_domain'
| 'compute_instance'
| 'pitr'
| 'ipv4'
| 'auth_mfa_phone'
| 'auth_mfa_web_authn'
| 'log_drain'
}
ProjectClaimTokenResponse: {
token_alias: string
expires_at: string
created_at: string
/** Format: uuid */
created_by: string
}
CreateProjectClaimTokenResponse: {
token: string
token_alias: string
expires_at: string
created_at: string
/** Format: uuid */
created_by: string
}
V1ProjectAdvisorsResponse: {
lints: {
/** @enum {string} */
name:
| 'unindexed_foreign_keys'
| 'auth_users_exposed'
| 'auth_rls_initplan'
| 'no_primary_key'
| 'unused_index'
| 'multiple_permissive_policies'
| 'policy_exists_rls_disabled'
| 'rls_enabled_no_policy'
| 'duplicate_index'
| 'security_definer_view'
| 'function_search_path_mutable'
| 'rls_disabled_in_public'
| 'extension_in_public'
| 'rls_references_user_metadata'
| 'materialized_view_in_api'
| 'foreign_table_in_api'
| 'unsupported_reg_types'
| 'auth_otp_long_expiry'
| 'auth_otp_short_length'
| 'ssl_not_enforced'
| 'network_restrictions_not_set'
| 'password_requirements_min_length'
| 'pitr_not_enabled'
| 'auth_leaked_password_protection'
| 'auth_insufficient_mfa_options'
| 'auth_password_policy_missing'
| 'leaked_service_key'
| 'no_backup_admin'
title: string
/** @enum {string} */
level: 'ERROR' | 'WARN' | 'INFO'
/** @enum {string} */
facing: 'EXTERNAL'
categories: ('PERFORMANCE' | 'SECURITY')[]
description: string
detail: string
remediation: string
metadata?: {
schema?: string
name?: string
entity?: string
/** @enum {string} */
type?: 'table' | 'view' | 'auth' | 'function' | 'extension' | 'compliance'
fkey_name?: string
fkey_columns?: number[]
}
cache_key: string
}[]
}
AnalyticsResponse: {
result?: unknown[]
error?:
| string
| {
code: number
errors: {
domain: string
location: string
locationType: string
message: string
reason: string
}[]
message: string
status: string
}
}
V1ListMigrationsResponse: {
version: string
name?: string
}[]
V1CreateMigrationBody: {
query: string
name?: string
}
V1UpsertMigrationBody: {
query: string
name?: string
}
V1RunQueryBody: {
query: string
read_only?: boolean
}
GetProjectDbMetadataResponse: {
databases: ({
name: string
schemas: ({
name: string
} & {
[key: string]: unknown
})[]
} & {
[key: string]: unknown
})[]
}
FunctionResponse: {
id: string
slug: string
name: string
/** @enum {string} */
status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'
version: number
/** Format: int64 */
created_at: number
/** Format: int64 */
updated_at: number
verify_jwt?: boolean
import_map?: boolean
entrypoint_path?: string
import_map_path?: string
}
V1CreateFunctionBody: {
slug: string
name: string
body: string
verify_jwt?: boolean
}
BulkUpdateFunctionBody: {
id: string
slug: string
name: string
/** @enum {string} */
status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'
version: number
/** Format: int64 */
created_at?: number
verify_jwt?: boolean
import_map?: boolean
entrypoint_path?: string
import_map_path?: string
}[]
BulkUpdateFunctionResponse: {
functions: {
id: string
slug: string
name: string
/** @enum {string} */
status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'
version: number
/** Format: int64 */
created_at: number
/** Format: int64 */
updated_at: number
verify_jwt?: boolean
import_map?: boolean
entrypoint_path?: string
import_map_path?: string
}[]
}
FunctionDeployBody: {
file?: string[]
metadata: {
entrypoint_path: string
import_map_path?: string
static_patterns?: string[]
verify_jwt?: boolean
name?: string
}
}
DeployFunctionResponse: {
id: string
slug: string
name: string
/** @enum {string} */
status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'
version: number
/** Format: int64 */
created_at?: number
/** Format: int64 */
updated_at?: number
verify_jwt?: boolean
import_map?: boolean
entrypoint_path?: string
import_map_path?: string
}
FunctionSlugResponse: {
id: string
slug: string
name: string
/** @enum {string} */
status: 'ACTIVE' | 'REMOVED' | 'THROTTLED'
version: number
/** Format: int64 */
created_at: number
/** Format: int64 */
updated_at: number
verify_jwt?: boolean
import_map?: boolean
entrypoint_path?: string
import_map_path?: string
}
StreamableFile: Record<string, never>
V1UpdateFunctionBody: {
name?: string
body?: string
verify_jwt?: boolean
}
V1StorageBucketResponse: {
id: string
name: string
owner: string
created_at: string
updated_at: string
public: boolean
}
CreateProviderBody: {
/**
* @description What type of provider will be created
* @enum {string}
*/
type: 'saml'
metadata_xml?: string
metadata_url?: string
domains?: string[]
attribute_mapping?: {
keys: {
[key: string]: {
name?: string
names?: string[]
default?: Record<string, never> | number | string | boolean
array?: boolean
}
}
}
}
CreateProviderResponse: {
id: string
saml?: {
id: string
entity_id: string
metadata_url?: string
metadata_xml?: string
attribute_mapping?: {
keys: {
[key: string]: {
name?: string
names?: string[]
default?: Record<string, never> | number | string | boolean
array?: boolean
}
}
}
}
domains?: {
id: string
domain?: string
created_at?: string
updated_at?: string
}[]
created_at?: string
updated_at?: string
}
ListProvidersResponse: {
items: {
id: string
saml?: {
id: string
entity_id: string
metadata_url?: string
metadata_xml?: string
attribute_mapping?: {
keys: {
[key: string]: {
name?: string
names?: string[]
default?: Record<string, never> | number | string | boolean
array?: boolean
}
}
}
}
domains?: {
id: string
domain?: string
created_at?: string
updated_at?: string
}[]
created_at?: string
updated_at?: string
}[]
}
GetProviderResponse: {
id: string
saml?: {
id: string
entity_id: string
metadata_url?: string
metadata_xml?: string
attribute_mapping?: {
keys: {
[key: string]: {
name?: string
names?: string[]
default?: Record<string, never> | number | string | boolean
array?: boolean
}
}
}
}
domains?: {
id: string
domain?: string
created_at?: string
updated_at?: string
}[]
created_at?: string
updated_at?: string
}
UpdateProviderBody: {
metadata_xml?: string
metadata_url?: string
domains?: string[]
attribute_mapping?: {
keys: {
[key: string]: {
name?: string
names?: string[]
default?: Record<string, never> | number | string | boolean
array?: boolean
}
}
}
}
UpdateProviderResponse: {
id: string
saml?: {
id: string
entity_id: string
metadata_url?: string
metadata_xml?: string
attribute_mapping?: {
keys: {
[key: string]: {
name?: string
names?: string[]
default?: Record<string, never> | number | string | boolean
array?: boolean
}
}
}
}
domains?: {
id: string
domain?: string
created_at?: string
updated_at?: string
}[]
created_at?: string
updated_at?: string
}
DeleteProviderResponse: {
id: string
saml?: {
id: string
entity_id: string
metadata_url?: string
metadata_xml?: string
attribute_mapping?: {
keys: {
[key: string]: {
name?: string
names?: string[]
default?: Record<string, never> | number | string | boolean
array?: boolean
}
}
}
}
domains?: {
id: string
domain?: string
created_at?: string
updated_at?: string
}[]
created_at?: string
updated_at?: string
}
V1BackupsResponse: {
region: string
walg_enabled: boolean
pitr_enabled: boolean
backups: {
is_physical_backup: boolean
/** @enum {string} */
status: 'COMPLETED' | 'FAILED' | 'PENDING' | 'REMOVED' | 'ARCHIVED' | 'CANCELLED'
inserted_at: string
}[]
physical_backup_data: {
earliest_physical_backup_date_unix?: number
latest_physical_backup_date_unix?: number
}
}
V1RestorePitrBody: {
/** Format: int64 */
recovery_time_target_unix: number
}
V1RestorePointPostBody: {
name: string
}
V1RestorePointResponse: {
name: string
/** @enum {string} */
status: 'AVAILABLE' | 'PENDING' | 'REMOVED'
}
V1UndoBody: {
name: string
}
V1OrganizationMemberResponse: {
user_id: string
user_name: string
email?: string
role_name: string
mfa_enabled: boolean
}
V1OrganizationSlugResponse: {
id: string
name: string
/** @enum {string} */
plan?: 'free' | 'pro' | 'team' | 'enterprise'
opt_in_tags: (
| 'AI_SQL_GENERATOR_OPT_IN'
| 'AI_DATA_GENERATOR_OPT_IN'
| 'AI_LOG_GENERATOR_OPT_IN'
)[]
allowed_release_channels: ('internal' | 'alpha' | 'beta' | 'ga' | 'withdrawn' | 'preview')[]
}
OrganizationProjectClaimResponse: {
project: {
ref: string
name: string
}
preview: {
valid: boolean
warnings: {
key: string
message: string
}[]
errors: {
key: string
message: string
}[]
info: {
key: string
message: string
}[]
members_exceeding_free_project_limit: {
name: string
limit: number
}[]
target_organization_eligible: boolean | null
target_organization_has_free_project_slots: boolean | null
/** @enum {string} */
source_subscription_plan: 'free' | 'pro' | 'team' | 'enterprise'
/** @enum {string|null} */
target_subscription_plan: 'free' | 'pro' | 'team' | 'enterprise' | null
}
expires_at: string
created_at: string
/** Format: uuid */
created_by: string
}
}
responses: never
parameters: never
requestBodies: never
headers: never
pathItems: never
}
export type $defs = Record<string, never>
export interface operations {
'v1-get-a-branch-config': {
parameters: {
query?: never
header?: never
path: {
/** @description Branch ID */
branch_id: string
}
cookie?: never
}
requestBody?: never
responses: {
200: {
headers: {
[name: string]: unknown
}
content: {
'application/json': components['schemas']['BranchDetailResponse']
}
}
/** @description Failed to retrieve database branch */
500: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-delete-a-branch': {
parameters: {
query?: never
header?: never
path: {
/** @description Branch ID */
branch_id: string
}
cookie?: never
}
requestBody?: never
responses: {
200: {
headers: {
[name: string]: unknown
}
content: {
'application/json': components['schemas']['BranchDeleteResponse']
}
}
/** @description Failed to delete database branch */
500: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-update-a-branch-config': {
parameters: {
query?: never
header?: never
path: {
/** @description Branch ID */
branch_id: string
}
cookie?: never
}
requestBody: {
content: {
'application/json': components['schemas']['UpdateBranchBody']
}
}
responses: {
200: {
headers: {
[name: string]: unknown
}
content: {
'application/json': components['schemas']['BranchResponse']
}
}
/** @description Failed to update database branch */
500: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-push-a-branch': {
parameters: {
query?: never
header?: never
path: {
/** @description Branch ID */
branch_id: string
}
cookie?: never
}
requestBody: {
content: {
'application/json': components['schemas']['BranchActionBody']
}
}
responses: {
201: {
headers: {
[name: string]: unknown
}
content: {
'application/json': components['schemas']['BranchUpdateResponse']
}
}
/** @description Failed to push database branch */
500: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-merge-a-branch': {
parameters: {
query?: never
header?: never
path: {
/** @description Branch ID */
branch_id: string
}
cookie?: never
}
requestBody: {
content: {
'application/json': components['schemas']['BranchActionBody']
}
}
responses: {
201: {
headers: {
[name: string]: unknown
}
content: {
'application/json': components['schemas']['BranchUpdateResponse']
}
}
/** @description Failed to merge database branch */
500: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-reset-a-branch': {
parameters: {
query?: never
header?: never
path: {
/** @description Branch ID */
branch_id: string
}
cookie?: never
}
requestBody: {
content: {
'application/json': components['schemas']['BranchActionBody']
}
}
responses: {
201: {
headers: {
[name: string]: unknown
}
content: {
'application/json': components['schemas']['BranchUpdateResponse']
}
}
/** @description Failed to reset database branch */
500: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-diff-a-branch': {
parameters: {
query?: {
included_schemas?: string
}
header?: never
path: {
/** @description Branch ID */
branch_id: string
}
cookie?: never
}
requestBody?: never
responses: {
200: {
headers: {
[name: string]: unknown
}
content: {
'text/plain': string
}
}
/** @description Failed to diff database branch */
500: {
headers: {
[name: string]: unknown
}
content?: never
}
}
}
'v1-list-all-projects': {
parameters: {
query?: never
header?: never
path?: never
cookie?: never
}
requestBody?: never
responses: {
200: {
headers: {
[name: string]: unknown
}
content: {
'application/json': components['schemas']['V1ProjectWithDatabaseResponse'][]
}
}
}
}
'v1-create-a-project': {
parameters: {
query?: never
header?: never
path?: never
cookie?: n
[truncated — showing 100000 of 179991 characters]registry/default/platform/platform-kit-nextjs/lib/management-api.ts
import createClient from 'openapi-fetch'
import type { paths } from './management-api-schema'
export const client = createClient<paths>({
baseUrl: '/api/supabase-proxy',
})
registry/default/platform/platform-kit-nextjs/lib/pg-meta/sql.ts
export const columnsSql = `
SELECT
c.oid :: int8 AS table_id,
nc.nspname AS schema,
c.relname AS table,
(c.oid || '.' || a.attnum) AS id,
a.attnum AS ordinal_position,
a.attname AS name,
CASE
WHEN a.atthasdef THEN pg_get_expr(ad.adbin, ad.adrelid)
ELSE NULL
END AS default_value,
CASE
WHEN t.typtype = 'd' THEN CASE
WHEN bt.typelem <> 0 :: oid
AND bt.typlen = -1 THEN 'ARRAY'
WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, NULL)
ELSE 'USER-DEFINED'
END
ELSE CASE
WHEN t.typelem <> 0 :: oid
AND t.typlen = -1 THEN 'ARRAY'
WHEN nt.nspname = 'pg_catalog' THEN format_type(a.atttypid, NULL)
ELSE 'USER-DEFINED'
END
END AS data_type,
COALESCE(bt.typname, t.typname) AS format,
a.attidentity IN ('a', 'd') AS is_identity,
CASE
a.attidentity
WHEN 'a' THEN 'ALWAYS'
WHEN 'd' THEN 'BY DEFAULT'
ELSE NULL
END AS identity_generation,
a.attgenerated IN ('s') AS is_generated,
NOT (
a.attnotnull
OR t.typtype = 'd' AND t.typnotnull
) AS is_nullable,
(
c.relkind IN ('r', 'p')
OR c.relkind IN ('v', 'f') AND pg_column_is_updatable(c.oid, a.attnum, FALSE)
) AS is_updatable,
uniques.table_id IS NOT NULL AS is_unique,
check_constraints.definition AS "check",
array_to_json(
array(
SELECT
enumlabel
FROM
pg_catalog.pg_enum enums
WHERE
enums.enumtypid = coalesce(bt.oid, t.oid)
OR enums.enumtypid = coalesce(bt.typelem, t.typelem)
ORDER BY
enums.enumsortorder
)
) AS enums,
col_description(c.oid, a.attnum) AS comment
FROM
pg_attribute a
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid
AND a.attnum = ad.adnum
JOIN (
pg_class c
JOIN pg_namespace nc ON c.relnamespace = nc.oid
) ON a.attrelid = c.oid
JOIN (
pg_type t
JOIN pg_namespace nt ON t.typnamespace = nt.oid
) ON a.atttypid = t.oid
LEFT JOIN (
pg_type bt
JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid
) ON t.typtype = 'd'
AND t.typbasetype = bt.oid
LEFT JOIN (
SELECT DISTINCT ON (table_id, ordinal_position)
conrelid AS table_id,
conkey[1] AS ordinal_position
FROM pg_catalog.pg_constraint
WHERE contype = 'u' AND cardinality(conkey) = 1
) AS uniques ON uniques.table_id = c.oid AND uniques.ordinal_position = a.attnum
LEFT JOIN (
-- We only select the first column check
SELECT DISTINCT ON (table_id, ordinal_position)
conrelid AS table_id,
conkey[1] AS ordinal_position,
substring(
pg_get_constraintdef(pg_constraint.oid, true),
8,
length(pg_get_constraintdef(pg_constraint.oid, true)) - 8
) AS "definition"
FROM pg_constraint
WHERE contype = 'c' AND cardinality(conkey) = 1
ORDER BY table_id, ordinal_position, oid asc
) AS check_constraints ON check_constraints.table_id = c.oid AND check_constraints.ordinal_position = a.attnum
WHERE
NOT pg_is_other_temp_schema(nc.oid)
AND a.attnum > 0
AND NOT a.attisdropped
AND (c.relkind IN ('r', 'v', 'm', 'f', 'p'))
AND (
pg_has_role(c.relowner, 'USAGE')
OR has_column_privilege(
c.oid,
a.attnum,
'SELECT, INSERT, UPDATE, REFERENCES'
)
)
`
export const tablesSql = `
SELECT
c.oid :: int8 AS id,
nc.nspname AS schema,
c.relname AS name,
c.relrowsecurity AS rls_enabled,
c.relforcerowsecurity AS rls_forced,
CASE
WHEN c.relreplident = 'd' THEN 'DEFAULT'
WHEN c.relreplident = 'i' THEN 'INDEX'
WHEN c.relreplident = 'f' THEN 'FULL'
ELSE 'NOTHING'
END AS replica_identity,
pg_total_relation_size(format('%I.%I', nc.nspname, c.relname)) :: int8 AS bytes,
pg_size_pretty(
pg_total_relation_size(format('%I.%I', nc.nspname, c.relname))
) AS size,
pg_stat_get_live_tuples(c.oid) AS live_rows_estimate,
pg_stat_get_dead_tuples(c.oid) AS dead_rows_estimate,
obj_description(c.oid) AS comment,
coalesce(pk.primary_keys, '[]') as primary_keys,
coalesce(
jsonb_agg(relationships) filter (where relationships is not null),
'[]'
) as relationships
FROM
pg_namespace nc
JOIN pg_class c ON nc.oid = c.relnamespace
left join (
select
table_id,
jsonb_agg(_pk.*) as primary_keys
from (
select
n.nspname as schema,
c.relname as table_name,
a.attname as name,
c.oid :: int8 as table_id
from
pg_index i,
pg_class c,
pg_attribute a,
pg_namespace n
where
i.indrelid = c.oid
and c.relnamespace = n.oid
and a.attrelid = c.oid
and a.attnum = any (i.indkey)
and i.indisprimary
) as _pk
group by table_id
) as pk
on pk.table_id = c.oid
left join (
select
c.oid :: int8 as id,
c.conname as constraint_name,
nsa.nspname as source_schema,
csa.relname as source_table_name,
sa.attname as source_column_name,
nta.nspname as target_table_schema,
cta.relname as target_table_name,
ta.attname as target_column_name
from
pg_constraint c
join (
pg_attribute sa
join pg_class csa on sa.attrelid = csa.oid
join pg_namespace nsa on csa.relnamespace = nsa.oid
) on sa.attrelid = c.conrelid and sa.attnum = any (c.conkey)
join (
pg_attribute ta
join pg_class cta on ta.attrelid = cta.oid
join pg_namespace nta on cta.relnamespace = nta.oid
) on ta.attrelid = c.confrelid and ta.attnum = any (c.confkey)
where
c.contype = 'f'
) as relationships
on (relationships.source_schema = nc.nspname and relationships.source_table_name = c.relname)
or (relationships.target_table_schema = nc.nspname and relationships.target_table_name = c.relname)
WHERE
c.relkind IN ('r', 'p')
AND NOT pg_is_other_temp_schema(nc.oid)
AND (
pg_has_role(c.relowner, 'USAGE')
OR has_table_privilege(
c.oid,
'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
)
OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
)
group by
c.oid,
c.relname,
c.relrowsecurity,
c.relforcerowsecurity,
c.relreplident,
nc.nspname,
pk.primary_keys
`
export const extensionsSql = `
SELECT
e.name,
n.nspname AS schema,
e.default_version,
x.extversion AS installed_version,
e.comment
FROM
pg_available_extensions() e(name, default_version, comment)
LEFT JOIN pg_extension x ON e.name = x.extname
LEFT JOIN pg_namespace n ON x.extnamespace = n.oid
`
registry/default/platform/platform-kit-nextjs/lib/pg-meta/index.ts
import { stripIndent } from 'common-tags'
import { columnsSql, extensionsSql, tablesSql } from './sql'
export const SYSTEM_SCHEMAS = [
'information_schema',
'pg_catalog',
'pg_toast',
'_timescaledb_internal',
]
/**
* Generates the SQL query to list tables in the database.
*/
export function listTablesSql(schemas: string[] = []) {
let sql = stripIndent`
with
tables as (${tablesSql}),
columns as (${columnsSql})
select
*,
${coalesceRowsToArray('columns', 'columns.table_id = tables.id')}
from tables
`
sql += '\n'
if (schemas.length > 0) {
sql += `where schema in (${schemas.map((s) => `'${s}'`).join(',')})`
} else {
sql += `where schema not in (${SYSTEM_SCHEMAS.map((s) => `'${s}'`).join(',')})`
}
return sql
}
/**
* Generates the SQL query to list all extensions in the database.
*/
export function listExtensionsSql() {
return extensionsSql
}
/**
* Generates a SQL segment that coalesces rows into an array of JSON objects.
*/
export const coalesceRowsToArray = (source: string, filter: string) => {
return stripIndent`
COALESCE(
(
SELECT
array_agg(row_to_json(${source})) FILTER (WHERE ${filter})
FROM
${source}
),
'{}'
) AS ${source}
`
}
registry/default/platform/platform-kit-nextjs/lib/pg-meta/types.ts
import { z } from 'zod'
export const postgresPrimaryKeySchema = z.object({
schema: z.string(),
table_name: z.string(),
name: z.string(),
table_id: z.number().int(),
})
export const postgresRelationshipSchema = z.object({
id: z.number().int(),
constraint_name: z.string(),
source_schema: z.string(),
source_table_name: z.string(),
source_column_name: z.string(),
target_table_schema: z.string(),
target_table_name: z.string(),
target_column_name: z.string(),
})
export const postgresColumnSchema = z.object({
table_id: z.number().int(),
schema: z.string(),
table: z.string(),
id: z.string().regex(/^(\d+)\.(\d+)$/),
ordinal_position: z.number().int(),
name: z.string(),
default_value: z.any(),
data_type: z.string(),
format: z.string(),
is_identity: z.boolean(),
identity_generation: z.union([z.literal('ALWAYS'), z.literal('BY DEFAULT'), z.null()]),
is_generated: z.boolean(),
is_nullable: z.boolean(),
is_updatable: z.boolean(),
is_unique: z.boolean(),
enums: z.array(z.string()),
check: z.union([z.string(), z.null()]),
comment: z.union([z.string(), z.null()]),
})
export const postgresTableSchema = z.object({
id: z.number().int(),
schema: z.string(),
name: z.string(),
rls_enabled: z.boolean(),
rls_forced: z.boolean(),
replica_identity: z.union([
z.literal('DEFAULT'),
z.literal('INDEX'),
z.literal('FULL'),
z.literal('NOTHING'),
]),
bytes: z.number().int(),
size: z.string(),
live_rows_estimate: z.number().int(),
dead_rows_estimate: z.number().int(),
comment: z.string().nullable(),
columns: z.array(postgresColumnSchema).optional(),
primary_keys: z.array(postgresPrimaryKeySchema),
relationships: z.array(postgresRelationshipSchema),
})
export const postgresExtensionSchema = z.object({
name: z.string(),
schema: z.union([z.string(), z.null()]),
default_version: z.string(),
installed_version: z.union([z.string(), z.null()]),
comment: z.union([z.string(), z.null()]),
})
export type PostgresPrimaryKey = z.infer<typeof postgresPrimaryKeySchema>
export type PostgresRelationship = z.infer<typeof postgresRelationshipSchema>
export type PostgresColumn = z.infer<typeof postgresColumnSchema>
export type PostgresTable = z.infer<typeof postgresTableSchema>
export type PostgresExtension = z.infer<typeof postgresExtensionSchema>
registry/default/platform/platform-kit-nextjs/lib/schemas/auth.ts
import { z } from 'zod'
// Field labels mapping for form generation
export const authFieldLabels: Record<
string,
string | { label: string; options?: Record<string, string> }
> = {
// General Settings
disable_signup: 'Disable Signup',
external_anonymous_users_enabled: 'Allow Anonymous Sign-ins',
// Email Provider
external_email_enabled: 'Enable Email Provider',
mailer_autoconfirm: 'Confirm Email',
mailer_secure_email_change_enabled: 'Secure Email Change',
security_update_password_require_reauthentication: 'Secure Password Change',
security_update_password_require_current_password: 'Require Password Change',
password_hibp_enabled: 'Prevent Leaked Passwords',
password_min_length: 'Minimum Password Length',
password_required_characters: {
label: 'Password Requirements',
options: {
NO_REQUIRED_CHARS: 'No requirements',
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789': 'Letters and numbers',
'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789':
'Lowercase, uppercase, and numbers',
'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\'\\\\:"|<>?,./`~':
'Letters, numbers, and symbols',
},
},
mailer_otp_exp: 'Email OTP Expiration (seconds)',
mailer_otp_length: 'Email OTP Length',
// Phone Provider
external_phone_enabled: 'Enable Phone Provider',
sms_provider: {
label: 'SMS Provider',
options: {
twilio: 'Twilio',
messagebird: 'MessageBird',
textlocal: 'TextLocal',
vonage: 'Vonage',
twilio_verify: 'Twilio Verify',
},
},
sms_twilio_account_sid: 'Twilio Account SID',
sms_twilio_auth_token: 'Twilio Auth Token',
sms_twilio_message_service_sid: 'Twilio Message Service SID',
sms_twilio_content_sid: 'Twilio Content SID',
sms_twilio_verify_account_sid: 'Twilio Verify Account SID',
sms_twilio_verify_auth_token: 'Twilio Verify Auth Token',
sms_twilio_verify_message_service_sid: 'Twilio Verify Message Service SID',
sms_messagebird_access_key: 'MessageBird Access Key',
sms_messagebird_originator: 'MessageBird Originator',
sms_textlocal_api_key: 'TextLocal API Key',
sms_textlocal_sender: 'TextLocal Sender',
sms_vonage_api_key: 'Vonage API Key',
sms_vonage_api_secret: 'Vonage API Secret',
sms_vonage_from: 'Vonage From Number',
sms_autoconfirm: 'Enable Phone Confirmations',
sms_otp_exp: 'SMS OTP Expiry (seconds)',
sms_otp_length: 'SMS OTP Length',
sms_template: 'SMS Message Template',
sms_test_otp: 'Test Phone Numbers and OTPs',
sms_test_otp_valid_until: 'Test OTPs Valid Until',
// Google Provider
external_google_enabled: 'Enable Google Sign-in',
external_google_client_id: 'Google Client ID(s)',
external_google_secret: 'Google OAuth Client Secret',
external_google_skip_nonce_check: 'Skip Nonce Check',
// Secrets
name: 'Secret Name',
value: 'Secret Value',
secretNames: 'Secret Names',
}
// New schemas for auth settings
export const authGeneralSettingsSchema = z.object({
disable_signup: z.boolean().optional().describe('This will prevent new users from signing up.'),
external_anonymous_users_enabled: z
.boolean()
.optional()
.describe('This will enable anonymous users to sign in to your application.'),
})
export type AuthGeneralSettingsSchema = z.infer<typeof authGeneralSettingsSchema>
const NO_REQUIRED_CHARACTERS = 'NO_REQUIRED_CHARS'
export const authEmailProviderSchema = z
.object({
external_email_enabled: z
.boolean()
.optional()
.describe('This will enable Email based signup and login for your application.'),
mailer_autoconfirm: z
.boolean()
.optional()
.describe(
'Users will need to confirm their email address before signing in for the first time.'
),
mailer_secure_email_change_enabled: z
.boolean()
.optional()
.describe(
'Users will be required to confirm any email change on both the old email address and new email address. If disabled, only the new email is required to confirm.'
),
security_update_password_require_reauthentication: z
.boolean()
.optional()
.describe(
'Users will need to be recently logged in to change their password without requiring reauthentication. (A user is considered recently logged in if the session was created within the last 24 hours.) If disabled, a user can change their password at any time.'
),
security_update_password_require_current_password: z
.boolean()
.optional()
.describe(
'Users will need to provide their current password in order to change their password. Works alongside security_update_password_require_reauthentication and both can be enabled at the same time.'
),
password_hibp_enabled: z
.boolean()
.optional()
.describe(
'Rejects the use of known or easy to guess passwords on sign up or password change. Powered by the HaveIBeenPwned.org Pwned Passwords API.'
),
password_min_length: z
.number()
.int()
.min(6, 'Must be greater or equal to 6.')
.describe(
'Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more.'
),
password_required_characters: z
.preprocess(
(val) => (val === '' || val === null || val === undefined ? NO_REQUIRED_CHARACTERS : val),
z.enum([
NO_REQUIRED_CHARACTERS,
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789',
'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789',
'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\'\\\\:"|<>?,./`~',
])
)
.optional()
.transform((val) => (val === NO_REQUIRED_CHARACTERS ? '' : val))
.describe('Passwords that do not have at least one of each will be rejected as weak.'),
mailer_otp_exp: z
.number()
.int()
.min(0, 'Must be more than 0')
.max(86400, 'Must be no more than 86400')
.describe('Duration before an email otp / link expires in seconds.'),
mailer_otp_length: z
.number()
.int()
.min(6, 'Must be at least 6')
.max(10, 'Must be no more than 10')
.optional()
.describe('Number of digits in the email OTP'),
})
.describe('Email provider settings.')
export type AuthEmailProviderSchema = z.infer<typeof authEmailProviderSchema>
export const authPhoneProviderSchema = z
.object({
external_phone_enabled: z
.boolean()
.optional()
.describe('This will enable phone based login for your application'),
sms_provider: z
.enum(['twilio', 'messagebird', 'textlocal', 'vonage', 'twilio_verify'])
.optional()
.describe('External provider that will handle sending SMS messages'),
// Twilio
sms_twilio_account_sid: z.string().optional(),
sms_twilio_auth_token: z.string().optional(),
sms_twilio_message_service_sid: z.string().optional(),
sms_twilio_content_sid: z
.string()
.optional()
.describe('Twilio Content SID (Optional, For WhatsApp Only)'),
// Twilio Verify
sms_twilio_verify_account_sid: z.string().optional(),
sms_twilio_verify_auth_token: z.string().optional(),
sms_twilio_verify_message_service_sid: z.string().optional(),
// Messagebird
sms_messagebird_access_key: z.string().optional(),
sms_messagebird_originator: z.string().optional(),
// Textlocal
sms_textlocal_api_key: z.string().optional(),
sms_textlocal_sender: z.string().optional(),
// Vonage
sms_vonage_api_key: z.string().optional(),
sms_vonage_api_secret: z.string().optional(),
sms_vonage_from: z.string().optional(),
// SMS Confirm settings
sms_autoconfirm: z
.boolean()
.optional()
.describe('Users will need to confirm their phone number before signing in.'),
sms_otp_exp: z
.number()
.int()
.optional()
.describe('Duration before an SMS OTP expires in seconds.'),
sms_otp_length: z.number().int().optional().describe('Number of digits in OTP.'),
sms_template: z.string().optional().describe('To format the OTP code use `{{ .Code }}`'),
sms_test_otp: z
.string()
.optional()
.describe(
'Register phone number and OTP combinations for testing as a comma separated list of <phone number>=<otp> pairs. Example: `18005550123=789012`'
),
sms_test_otp_valid_until: z
.string()
.datetime({ message: 'Invalid datetime string.' })
.optional()
.describe(
"Test phone number and OTP combinations won't be active past this date and time (local time zone)."
),
})
.describe('Phone provider settings.')
export type AuthPhoneProviderSchema = z.infer<typeof authPhoneProviderSchema>
export const authGoogleProviderObject = z.object({
external_google_enabled: z.boolean().optional().describe('Enable Sign in with Google'),
external_google_client_id: z
.string()
.regex(
/^([a-z0-9-]+\.[a-z0-9-]+(\.[a-z0-9-]+)*(,[a-z0-9-]+\.[a-z0-9-]+(\.[a-z0-9-]+)*)*)$/i,
'Google Client IDs should be a comma-separated list of domain-like strings.'
)
.optional(),
external_google_secret: z
.string()
.regex(
/^[a-z0-9.\/_-]*$/i,
'Google OAuth Client Secrets usually contain letters, numbers, dots, dashes, and underscores.'
)
.optional(),
external_google_skip_nonce_check: z
.boolean()
.optional()
.describe('Allows ID tokens with any nonce to be accepted, which is less secure.'),
})
export const authGoogleProviderSchema = authGoogleProviderObject
.superRefine((data, ctx) => {
if (data.external_google_enabled && !data.external_google_client_id) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['external_google_client_id'],
message: 'At least one Client ID is required when Google sign-in is enabled.',
})
}
if (data.external_google_client_id && data.external_google_client_id.includes(' ')) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['external_google_client_id'],
message: 'Client IDs should not contain spaces.',
})
}
})
.describe('Google provider settings.')
export type AuthGoogleProviderSchema = z.infer<typeof authGoogleProviderSchema>
export const authConfigUpdateSchema = authGeneralSettingsSchema
.merge(authEmailProviderSchema)
.merge(authPhoneProviderSchema)
.merge(authGoogleProviderObject)
export type AuthConfigUpdateSchema = z.infer<typeof authConfigUpdateSchema>
// A version used for partial updates (all fields optional)
export const authConfigUpdatePayloadSchema = authConfigUpdateSchema.partial()
registry/default/platform/platform-kit-nextjs/lib/schemas/secrets.ts
import z from 'zod'
export const secretsSchema = z
.object({
secrets: z.array(
z.object({
name: z
.string()
.min(1, 'Secret name is required.')
.regex(
/^[a-zA-Z_][a-zA-Z0-9_]*$/,
'Must contain letters, numbers, and underscores, starting with a letter or underscore.'
),
value: z.string().min(1, 'Secret value is required.'),
})
),
})
.describe('Secrets schema for managing environment variables.')
export type SecretsSchema = z.infer<typeof secretsSchema>
export const deleteSecretsSchema = z
.object({
secretNames: z
.array(z.string().min(1, 'Secret name cannot be empty.'))
.min(1, 'At least one secret name is required.'),
})
.describe('Schema for deleting secrets by name.')
export type DeleteSecretsSchema = z.infer<typeof deleteSecretsSchema>
registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext.tsx
'use client'
import { createContext, ReactNode, useCallback, useContext, useMemo, useState } from 'react'
type StackItem = {
title: string
component: ReactNode
}
type SheetNavigationContextType = {
stack: StackItem[]
push: (item: StackItem) => void
pop: () => void
popTo: (index: number) => void
reset: () => void
}
const SheetNavigationContext = createContext<SheetNavigationContextType | undefined>(undefined)
export function SheetNavigationProvider({
children,
onStackEmpty,
initialStack = [],
}: {
children: ReactNode
onStackEmpty?: () => void
initialStack?: StackItem[]
}) {
const [stack, setStack] = useState<StackItem[]>(initialStack)
const push = useCallback((item: StackItem) => {
setStack((prev) => [...prev, item])
}, [])
const pop = useCallback(() => {
setStack((prev) => {
const newStack = prev.slice(0, -1)
if (newStack.length === 0) {
onStackEmpty?.()
}
return newStack
})
}, [onStackEmpty])
const popTo = useCallback((index: number) => {
if (index < 0) return
setStack((prev) => prev.slice(0, index + 1))
}, [])
const reset = useCallback(() => {
setStack([])
onStackEmpty?.()
}, [onStackEmpty])
const value = useMemo(
() => ({ stack, push, pop, popTo, reset }),
[stack, push, pop, popTo, reset]
)
return <SheetNavigationContext.Provider value={value}>{children}</SheetNavigationContext.Provider>
}
export function useSheetNavigation() {
const context = useContext(SheetNavigationContext)
if (context === undefined) {
throw new Error('useSheetNavigation must be used within a SheetNavigationProvider')
}
return context
}