-
-
- )
-}
diff --git a/apps/database-new/app/[thread_id]/[message_id]/SchemaFlow.tsx b/apps/database-new/app/[thread_id]/[message_id]/SchemaFlow.tsx
deleted file mode 100644
index e53a476920a..00000000000
--- a/apps/database-new/app/[thread_id]/[message_id]/SchemaFlow.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import SchemaFlowHandler from '@/components/SchemaFlowHandler'
-
-export async function SchemaFlow({ promisedMessage }: { promisedMessage: Promise }) {
- const code = await promisedMessage
- const strippedCode = code.replace('```sql\n', '').replace('\n```', '')
-
- return
-}
diff --git a/apps/database-new/app/[thread_id]/[message_id]/getAssistantMessage.ts b/apps/database-new/app/[thread_id]/[message_id]/getAssistantMessage.ts
deleted file mode 100644
index 5d5de4d165a..00000000000
--- a/apps/database-new/app/[thread_id]/[message_id]/getAssistantMessage.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import { stripIndent } from 'common-tags'
-import { cookies } from 'next/headers'
-import OpenAI from 'openai'
-
-// import is weird, what's up with this?
-import { createClient } from '@/lib/supabase/server'
-import { ContextLengthError } from '../../../../../packages/ai-commands/src/errors'
-import { getMessages } from './getMessages'
-
-// Create an OpenAI API client (that's edge friendly!)
-const openai = new OpenAI({
- apiKey: process.env.OPENAI_API_KEY,
-})
-
-// Set the runtime to edge for best performance
-export const runtime = 'edge'
-
-export type AiAssistantMessage = {
- content: string
- role: 'user' | 'assistant'
-}
-
-/**
- * Get the latest AI generated message. In the DB, there's a pair of user prompt and AI response, so if the
- * user sent a message but that message lacks the corresponding AI response, a call will be made to OpenAI to generate
- * it.
- */
-export const getAssistantResponse = async (threadId: string, messageId: string) => {
- const supabase = createClient()
-
- const { data: message, error } = await supabase
- .from('messages')
- .select()
- .eq('message_id', messageId)
- .single()
-
- if (!message || error) {
- throw new Error('A message with that id does not exist.')
- }
-
- // if the message lacks the AI response, fetch it from OpenAI
- if (!message.message_content) {
- const { data: messages, error } = await getMessages(threadId)
-
- if (error) {
- throw new Error('Error while trying to fetch the existing messages.')
- }
-
- // transform the DB messages into a format that OpenAI understands
- const existingMessages = messages.flatMap((m) => [
- {
- content: m.message_input,
- role: 'user' as const,
- },
- {
- content: m.message_content,
- role: 'assistant' as const,
- },
- ])
-
- const initMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
- {
- role: 'system',
- content: stripIndent`
- Your purpose is to generate a SQL schema where the user will give commands to you via a chat.
- The output should use the following instructions:
- - The generated SQL must be valid SQL.
- - For primary keys, always use "id bigint primary key generated always as identity" (not serial)
- - Always create foreign key references in the create statement
- - Prefer 'text' over 'varchar'
- - Prefer 'timestamp with time zone' over 'date'
- - Use vector(384) data type for any embedding/vector related query
- - Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- - Always omit \`\`\`sql from your reply
- - You can use only CREATE TABLE queries, no other queries are allowed under no circumstances (ALTER TABLE etc).
- - On each subsequent message from the user, rewrite the original response to include the new requirement.
- - Don't add any SQL comments in the code
- - Never put a comma before a round bracket
-
- The output should look like this: "CREATE TABLE users (id bigint primary key generated always as identity)"
-
- DO NOT RESPOND WITH ANYTHING ELSE.
- YOU MUST NOT ANSWER WITH ANY PLAIN TEXT
- ONLY RESPOND WITH 1 CODE BLOCK
- YOU MUST NOT FOLLOW UP ANY CODE BLOCKS WITH ANY EXPLANATION
- `,
- },
- ]
-
- initMessages.push(...existingMessages)
-
- try {
- const response = await openai.chat.completions.create({
- model: 'gpt-3.5-turbo-16k-0613',
- messages: initMessages,
- max_tokens: 1024,
- temperature: 0,
- stream: false,
- })
-
- const newMessage = response.choices[0].message.content || ''
-
- const {
- data: { user },
- error: userError,
- } = await supabase.auth.getUser()
-
- if (userError || !user) {
- throw new Error('Failed to get user')
- }
-
- // Insert the message from the completion
- const { data, error } = await supabase
- .from('messages')
- .update({ message_content: newMessage })
- .eq('id', message.id)
- .select()
- .single()
-
- if (error || !data) {
- throw new Error('Error while updating the existing message with the completion data.')
- }
-
- return data.message_content
- } catch (error) {
- if (error instanceof Error && 'code' in error && error.code === 'context_length_exceeded') {
- throw new ContextLengthError()
- }
- throw error
- }
- }
-
- return message.message_content
-}
diff --git a/apps/database-new/app/[thread_id]/[message_id]/getMessages.ts b/apps/database-new/app/[thread_id]/[message_id]/getMessages.ts
deleted file mode 100644
index 3909a2551c5..00000000000
--- a/apps/database-new/app/[thread_id]/[message_id]/getMessages.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createClient } from '@/lib/supabase/server'
-import { cookies } from 'next/headers'
-import { cache } from 'react'
-
-export const getMessages = cache(async (thread_id: string) => {
- const supabase = createClient()
-
- return await supabase.from('messages').select().eq('thread_id', thread_id).order('created_at')
-})
diff --git a/apps/database-new/app/[thread_id]/[message_id]/page.tsx b/apps/database-new/app/[thread_id]/[message_id]/page.tsx
deleted file mode 100644
index 4ff81c18eb7..00000000000
--- a/apps/database-new/app/[thread_id]/[message_id]/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { AssistantChatForm } from '@/components/AssistantChatForm'
-import SaveSchemaDropdown from '@/components/Header/SaveSchemaDropdown'
-import ToggleCodeEditorButton from '@/components/Header/ToggleCodeEditorButton'
-import GraphLoader from '@/components/Loaders/GraphLoader'
-import SchemaLoader from '@/components/Loaders/SchemaLoader'
-import { cn } from '@ui/lib/utils/cn'
-import { Suspense } from 'react'
-import { CodeEditor } from './CodeEditor'
-import { Messages } from './Messages'
-import { SchemaFlow } from './SchemaFlow'
-import { getAssistantResponse } from './getAssistantMessage'
-
-export interface ThreadPageProps {
- params: {
- thread_id: string
- message_id: string
- }
-}
-
-export default async function ThreadPage({ params }: ThreadPageProps) {
- const promise = getAssistantResponse(params.thread_id, params.message_id)
-
- return (
-
-}
diff --git a/apps/database-new/app/profile/page.tsx b/apps/database-new/app/profile/page.tsx
deleted file mode 100644
index 96046ff681e..00000000000
--- a/apps/database-new/app/profile/page.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { createClient } from '@/lib/supabase/server'
-import { default as dayjs, default as relativeTime } from 'dayjs'
-import { cookies } from 'next/headers'
-import { redirect } from 'next/navigation'
-import Threads from './Threads'
-import { Suspense } from 'react'
-import ShimmeringLoader from '@/components/Shimmer'
-import type { Metadata } from 'next'
-
-dayjs.extend(relativeTime)
-export const metadata: Metadata = {
- title: 'database.design | Profile',
-}
-
-// revalidatePath not working in the create route handler
-// force-dynamic to refetch every time if needed
-// just a hack for now
-// export const dynamic = 'force-dynamic'
-
-const Profile = async () => {
- return (
-
-
-
- Loading user...}>
-
-
-
-
-
-
-
Past conversations
-
-
-
-
-
-
-
-
-
-
-
- }
- >
-
-
-
-
- )
-}
-
-async function ProfileDetails() {
- const supabase = createClient()
-
- const {
- data: { user },
- } = await supabase.auth.getUser()
-
- if (!user) redirect('/')
-
- return !user ? (
- // make this better!
-
'
+
+ return result
+}
+
+function examplesToHtml(fn: any) {
+ if (!fn.examples || fn.examples.length === 0) return ''
+
+ let result = '
Examples
'
+
+ result += fn.examples
+ .map((example) => `
${example.name ?? ''}
` + mdxToHtml(example.code ?? ''))
+ .join('')
+
+ return result
+}
diff --git a/apps/docs/app/api/revalidate/route.test.ts b/apps/docs/app/api/revalidate/route.test.ts
new file mode 100644
index 00000000000..befeabd43e7
--- /dev/null
+++ b/apps/docs/app/api/revalidate/route.test.ts
@@ -0,0 +1,188 @@
+/* eslint-disable turbo/no-undeclared-env-vars */
+
+import { createClient } from '@supabase/supabase-js'
+import { revalidateTag } from 'next/cache'
+import { headers } from 'next/headers'
+import { NextRequest } from 'next/server'
+import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest'
+
+import { _handleRevalidateRequest } from './route'
+
+// Mock Next.js modules
+vi.mock('next/cache', () => ({
+ revalidateTag: vi.fn(),
+}))
+
+vi.mock('next/headers', () => ({
+ headers: vi.fn(),
+}))
+
+// Mock Supabase client
+vi.mock('@supabase/supabase-js', () => ({
+ createClient: vi.fn(),
+}))
+
+describe('_handleRevalidateRequest', () => {
+ let mockDate: Date
+ let originalEnv: NodeJS.ProcessEnv
+ let mockSupabaseClient: {
+ rpc: Mock
+ from: Mock
+ }
+
+ beforeEach(() => {
+ // Store the original environment
+ originalEnv = { ...process.env }
+
+ // Mock environment variables
+ process.env.DOCS_REVALIDATION_KEYS = 'basic_key'
+ process.env.DOCS_REVALIDATION_OVERRIDE_KEYS = 'override_key,other_override_key'
+ process.env.NEXT_PUBLIC_SUPABASE_URL = 'http://localhost:3000'
+ process.env.SUPABASE_SECRET_KEY = 'secret_key'
+
+ // Mock current date
+ mockDate = new Date('2023-01-01T12:00:00Z')
+ vi.setSystemTime(mockDate)
+
+ // Setup mock Supabase client
+ mockSupabaseClient = {
+ rpc: vi.fn(),
+ from: vi.fn(() => ({
+ insert: vi.fn().mockResolvedValue({ error: null }),
+ })),
+ }
+ vi.mocked(createClient).mockReturnValue(mockSupabaseClient as any)
+ })
+
+ afterEach(() => {
+ process.env = originalEnv
+ vi.clearAllMocks()
+ vi.useRealTimers()
+ })
+
+ it('should return 401 if Authorization header is missing', async () => {
+ const request = new NextRequest('https://example.com', {
+ method: 'POST',
+ })
+
+ vi.mocked(headers).mockReturnValue(new Headers(request.headers))
+
+ const response = await _handleRevalidateRequest(request)
+ expect(response.status).toBe(401)
+ expect(await response.text()).toBe('Missing Authorization header')
+ })
+
+ it('should return 401 if Authorization header is invalid', async () => {
+ const request = new NextRequest('https://example.com', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer invalid_token',
+ },
+ })
+
+ vi.mocked(headers).mockReturnValue(new Headers(request.headers))
+
+ const response = await _handleRevalidateRequest(request)
+ expect(response.status).toBe(401)
+ expect(await response.text()).toBe('Invalid Authorization header')
+ })
+
+ it('should return 400 if request body is malformed', async () => {
+ const request = new NextRequest('https://example.com', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer basic_key',
+ },
+ body: JSON.stringify({ invalid: 'body' }),
+ })
+
+ vi.mocked(headers).mockReturnValue(new Headers(request.headers))
+
+ const response = await _handleRevalidateRequest(request)
+ expect(response.status).toBe(400)
+ expect(await response.text()).toContain('Malformed request body')
+ })
+
+ it('should revalidate tags if request is valid with basic permissions', async () => {
+ const request = new NextRequest('https://example.com', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer basic_key',
+ },
+ body: JSON.stringify({ tags: ['tag1', 'tag2'] }),
+ })
+
+ vi.mocked(headers).mockReturnValue(new Headers(request.headers))
+
+ mockSupabaseClient.rpc.mockResolvedValue({ data: [] })
+
+ const response = await _handleRevalidateRequest(request)
+ expect(response.status).toBe(204)
+ expect(revalidateTag).toHaveBeenCalledTimes(2)
+ expect(revalidateTag).toHaveBeenCalledWith('tag1')
+ expect(revalidateTag).toHaveBeenCalledWith('tag2')
+ })
+
+ it('should return 429 if last revalidation was less than 6 hours ago with basic permissions', async () => {
+ const request = new NextRequest('https://example.com', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer basic_key',
+ },
+ body: JSON.stringify({ tags: ['tag1'] }),
+ })
+
+ vi.mocked(headers).mockReturnValue(new Headers(request.headers))
+
+ const fiveHoursAgo = new Date(mockDate.getTime() - 5 * 60 * 60 * 1000)
+ mockSupabaseClient.rpc.mockResolvedValue({
+ data: [{ created_at: fiveHoursAgo.toISOString() }],
+ })
+
+ const response = await _handleRevalidateRequest(request)
+ expect(response.status).toBe(429)
+ expect(await response.text()).toContain('revalidated within the last 6 hours')
+ })
+
+ it('should revalidate if last revalidation was more than 6 hours ago with basic permissions', async () => {
+ const request = new NextRequest('https://example.com', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer basic_key',
+ },
+ body: JSON.stringify({ tags: ['tag1'] }),
+ })
+
+ vi.mocked(headers).mockReturnValue(new Headers(request.headers))
+
+ const sevenHoursAgo = new Date(mockDate.getTime() - 7 * 60 * 60 * 1000)
+ mockSupabaseClient.rpc.mockResolvedValue({
+ data: [{ created_at: sevenHoursAgo.toISOString() }],
+ })
+
+ const response = await _handleRevalidateRequest(request)
+ expect(response.status).toBe(204)
+ expect(revalidateTag).toHaveBeenCalledWith('tag1')
+ })
+
+ it('should revalidate regardless of last revalidation time with override permissions', async () => {
+ const request = new NextRequest('https://example.com', {
+ method: 'POST',
+ headers: {
+ Authorization: 'Bearer override_key',
+ },
+ body: JSON.stringify({ tags: ['tag1'] }),
+ })
+
+ vi.mocked(headers).mockReturnValue(new Headers(request.headers))
+
+ const oneHourAgo = new Date(mockDate.getTime() - 1 * 60 * 60 * 1000)
+ mockSupabaseClient.rpc.mockResolvedValue({
+ data: [{ created_at: oneHourAgo.toISOString() }],
+ })
+
+ const response = await _handleRevalidateRequest(request)
+ expect(response.status).toBe(204)
+ expect(revalidateTag).toHaveBeenCalledWith('tag1')
+ })
+})
diff --git a/apps/docs/app/api/revalidate/route.ts b/apps/docs/app/api/revalidate/route.ts
new file mode 100644
index 00000000000..1c751fb6512
--- /dev/null
+++ b/apps/docs/app/api/revalidate/route.ts
@@ -0,0 +1,119 @@
+import { createClient } from '@supabase/supabase-js'
+import { revalidateTag } from 'next/cache'
+import { headers } from 'next/headers'
+import { type NextRequest } from 'next/server'
+import { z } from 'zod'
+
+import { type Database } from 'common'
+
+enum AuthorizationLevel {
+ Unauthorized,
+ Basic,
+ Override,
+}
+
+const requestBodySchema = z.object({
+ tags: z.array(z.string()),
+})
+
+export const POST = handleError(_handleRevalidateRequest)
+
+export async function _handleRevalidateRequest(request: NextRequest) {
+ const requestHeaders = headers()
+ const authorization = requestHeaders.get('Authorization')
+ if (!authorization) {
+ return new Response('Missing Authorization header', { status: 401 })
+ }
+
+ const basicKeys = process.env.DOCS_REVALIDATION_KEYS?.split(/\s*,\s*/) ?? []
+ const overrideKeys = process.env.DOCS_REVALIDATION_OVERRIDE_KEYS?.split(/\s*,\s*/) ?? []
+ if (basicKeys.length === 0 && overrideKeys.length === 0) {
+ console.error('No keys configured for revalidation')
+ return new Response('Internal server error', {
+ status: 500,
+ })
+ }
+
+ let authorizationLevel = AuthorizationLevel.Unauthorized
+
+ const token = authorization.replace(/^Bearer\s+/, '')
+ if (overrideKeys.includes(token)) {
+ authorizationLevel = AuthorizationLevel.Override
+ } else if (basicKeys.includes(token)) {
+ authorizationLevel = AuthorizationLevel.Basic
+ }
+
+ if (authorizationLevel === AuthorizationLevel.Unauthorized) {
+ return new Response('Invalid Authorization header', { status: 401 })
+ }
+
+ let result: z.infer
+ try {
+ result = requestBodySchema.parse(await request.json())
+ } catch (error) {
+ console.error(error)
+ return new Response(
+ 'Malformed request body: should be a JSON object with a "tags" array of strings.',
+ { status: 400 }
+ )
+ }
+
+ const supabaseAdmin = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.SUPABASE_SECRET_KEY!
+ )
+
+ if (authorizationLevel === AuthorizationLevel.Basic) {
+ const { data: lastRevalidation, error } = await supabaseAdmin.rpc(
+ 'get_last_revalidation_for_tags',
+ {
+ tags: result.tags,
+ }
+ )
+ if (error) {
+ console.error(error)
+ return new Response('Internal server error', { status: 500 })
+ }
+
+ const sixHoursAgo = new Date()
+ sixHoursAgo.setHours(sixHoursAgo.getHours() - 6)
+ if (lastRevalidation?.some((revalidation) => new Date(revalidation.created_at) > sixHoursAgo)) {
+ return new Response(
+ 'Your request includes a tag that has been revalidated within the last 6 hours. You can override this limit by authenticating with Override permissions.',
+ {
+ status: 429,
+ }
+ )
+ }
+ }
+
+ const { error } = await supabaseAdmin
+ .from('validation_history')
+ .insert(result.tags.map((tag) => ({ tag })))
+ if (error) {
+ console.error('Failed to update revalidation table: %o', error)
+ }
+
+ result.tags.forEach((tag) => {
+ revalidateTag(tag)
+ })
+
+ return new Response(null, {
+ status: 204,
+ headers: {
+ 'Cache-Control': 'no-cache',
+ },
+ })
+}
+
+function handleError(handleRequest: (request: NextRequest) => Promise) {
+ return async function (request: NextRequest) {
+ try {
+ const response = await handleRequest(request)
+ return response
+ } catch (error) {
+ console.error(error)
+ return new Response('Internal server error', { status: 500 })
+ }
+ }
+}
diff --git a/apps/docs/app/contributing/ContributingToC.tsx b/apps/docs/app/contributing/ContributingToC.tsx
new file mode 100644
index 00000000000..e1d8423f89a
--- /dev/null
+++ b/apps/docs/app/contributing/ContributingToC.tsx
@@ -0,0 +1,82 @@
+'use client'
+
+import { Menu } from 'lucide-react'
+import type { HTMLAttributes } from 'react'
+import { useEffect, useState } from 'react'
+
+import { useBreakpoint } from 'common'
+import { cn, Popover_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_ } from 'ui'
+
+interface TocItem extends HTMLAttributes {
+ label: string
+ anchor: string
+}
+
+export function ContributingToc({ className }: { className?: string }) {
+ const mobileToc = useBreakpoint('xl')
+ const [tocItems, setTocItems] = useState>([])
+
+ useEffect(() => {
+ const headings = [
+ ...document.querySelectorAll('article.prose > h2,h3'),
+ ] as Array
+ const tocItems = headings
+ .filter((heading) => !!heading.id)
+ .map((heading) => ({
+ label: heading.textContent.substring(0, heading.textContent.length - 1), // Remove ending `#`
+ anchor: heading.id,
+ }))
+ setTocItems(tocItems)
+ }, [])
+
+ return mobileToc ? (
+
+ ) : (
+
+ )
+}
+
+function MobileToc({ items, className }: { items: Array; className?: string }) {
+ const [open, setOpen] = useState(false)
+
+ return (
+
+
+
+
+ {open ? 'Close table of contents' : 'Open table of contents'}
+
+
+
+
+
+
+ )
+}
+
+function TocBase({ items, className }: { items: Array; className?: string }) {
+ return (
+
+ )
+}
diff --git a/apps/docs/app/contributing/content.mdx b/apps/docs/app/contributing/content.mdx
new file mode 100644
index 00000000000..12cba239f87
--- /dev/null
+++ b/apps/docs/app/contributing/content.mdx
@@ -0,0 +1,439 @@
+# Contributing to Supabase Docs
+
+Thanks for contributing to Supabase Docs! Here are a few resources to help you get started.
+
+The code and content for our docs site are located in the main [Supabase GitHub repo](https://github.com/supabase/supabase), under the `apps/docs` directory.
+
+In the repo, you'll also find:
+
+- The [developers guide](https://github.com/supabase/supabase/blob/master/apps/docs/DEVELOPERS.md), which will help you set up your local machine to develop the docs site
+- The [contributing guide](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md), which goes over the content organization and some general guidelines for writing docs content
+
+## Components
+
+Our docs content is mainly written in MDX. Aside from standard GitHub-flavored Markdown, you can use the following helper components to help you organize and display your content:
+
+### Accordion
+
+For content that requires progressive disclosure:
+
+```mdx
+
+
+
+
+ Your content here.
+
+
+
+
+
+
+
+ More content here.
+
+
+
+
+
+```
+
+
+
+
+
+ Your content here.
+
+
+
+
+
+
+
+ More content here.
+
+
+
+
+
+
+### Admonition
+
+For extra information that doesn't fit into the main flow. There are 5 supported types of admonitions:
+
+- `danger` to warn the user about any missteps that could cause data loss or data leaks
+- `deprecation` to notify the user about features that are (or will soon be) deprecated
+- `caution` to warn about anything that could cause a bug or serious user inconvenience
+- `tip` to point out helpful but optional actions
+- `note` for anything else
+
+Leave a blank line between the admonition tag and the contained content. This will prevent Prettier from trying to break the lines within the content.
+
+```mdx
+
+
+This could lead to data loss!
+
+
+
+
+
+This feature is deprecated.
+
+
+
+
+
+You should make sure you don't set this up wrong.
+
+
+
+
+
+In certain cases, you may want to do this.
+
+
+
+
+
+Additional helpful information.
+
+
+```
+
+
+
+This could lead to data loss!
+
+
+
+
+
+This feature is deprecated.
+
+
+
+
+
+You should make sure you don't set this up wrong.
+
+
+
+
+
+In certain cases, you may want to do this.
+
+
+
+
+
+Additional helpful information.
+
+
+
+### Code Samples
+
+You can include code samples as normal in Markdown:
+
+````mdx
+```js
+const PI = 3.14
+```
+````
+
+Of, you can use the `<$CodeSample />` component to include code samples from a source code file.
+
+If the file is within the `supabase/supabase` repo's `examples` directory:
+
+```mdx
+<$CodeSample
+path="/relative/path/from/examples/directory.js"
+{/_ Array of [start, end] line numbers to include.
+Line numbers are 1-indexed and inclusive.
+-1 indicates the final line. _/}
+lines={[[1, 3], [5, -1]]}
+{/* Optional, displays as a file name on the code block */}
+meta="display/path.js"
+/>
+```
+
+If the file is within some other GitHub repo (note that the repo must be public):
+
+```mdx
+<$CodeSample
+external={true}
+org="supabase"
+repo="cli"
+commit="1623aa9b95ec90e21c5bae5a0d50dcf272abe92f"
+path="/relative/path/from/root.js"
+lines={[[1, 3], [5, -1]]}
+meta="display/path.js"
+/>
+```
+
+### Icons
+
+The following icons are available. They can be styled with [Tailwind](https://tailwindcss.com/) classes:
+
+```mdx
+
+
+
+```
+
+
+
+
+
+
+
+### Image
+
+You can include images with regular Markdown syntax:
+
+```mdx
+
+```
+
+
+
+If your image has alternate light and dark versions, or you want to make it zoomable, you can also use the image component:
+
+```mdx
+
+```
+
+
+
+### Project Variables
+
+Some guides and tutorials will require that users copy their Supabase project URL and anon key. You can provide those inline if the user is signed in:
+
+```mdx
+
+
+```
+
+
+
+
+### Step Hike
+
+For tutorials, which feature step-by-step instructions, often with accompanying code, we use the `StepHike` pattern:
+
+````mdx
+
+
+
+
+
+
+ Explanation of what to do first.
+
+
+
+
+
+ ```sql
+ select ...
+ ```
+
+
+
+
+
+
+
+
+
+ Explanation of what to do next. This stretches the full width of the section: Sweet tiramisu apple biscuit candy cake. Orange ipsum muffin cookie cake biscuit. Orange muffin vanilla sweet sugar candy. Sprinkles jelly sweet orange candy cream.
+
+
+
+
+
+
+````
+
+
+
+
+
+
+
+ Explanation of what to do first.
+
+
+
+
+
+ ```sql
+ select ...
+ ```
+
+
+
+
+
+
+
+
+
+ Explanation of what to do next. This stretches the full width of the section: Sweet tiramisu apple biscuit candy cake. Orange ipsum muffin cookie cake biscuit. Orange muffin vanilla sweet sugar candy. Sprinkles jelly sweet orange candy cream.
+
+
+
+
+
+
+
+
+### Tabs
+
+Use tabs when users can select between multiple versions of the content. For example, the content might differ based on language or package manager.
+
+If you include the `queryGroup` prop, the user's selection will sync with other tab groups. Leave out this prop to omit this behavior.
+
+````mdx
+
+
+
+```js
+const supabase = createSupabaseClient()
+```
+
+
+
+
+```dart
+void main() async {
+ Supabase.initialize();
+}
+```
+
+
+
+````
+
+
+
+
+```js
+const supabase = createSupabaseClient()
+```
+
+
+
+
+```dart
+void main() async {
+ Supabase.initialize();
+}
+```
+
+
+
+
+## Partials
+
+We incorporate content reuse in the docs to avoid duplication. If you find yourself writing the same content over and over, you can put it in a partial instead. Here are some examples of commonly used partials:
+
+
+
+
+
+ ```mdx
+
+ ```
+
+
+
+
+
+
+
+
+
+ ```mdx
+
+ ```
+
+
+
+
+
+
+
+
+To make a new partial:
+
+1. Make a new MDX file in `apps/docs/components/MDX`.
+1. Write your reusable content.
+1. Inside `apps/docs/components/MDX/partials.tsx`, import and re-export your partial.
+1. Inside `apps/docs/features/docs/mdx.shared.tsx`, import your partial and include it in the `components` object.
+1. You can now use your partial inside any other MDX file by using: ``.
diff --git a/apps/docs/app/contributing/page.tsx b/apps/docs/app/contributing/page.tsx
new file mode 100644
index 00000000000..851706f228a
--- /dev/null
+++ b/apps/docs/app/contributing/page.tsx
@@ -0,0 +1,26 @@
+import { readFile } from 'node:fs/promises'
+import { dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+import { ContributingToc } from '~/app/contributing/ContributingToC'
+import { MDXProviderGuides } from '~/features/docs/GuidesMdx.client'
+import { MDXRemoteBase } from '~/features/docs/MdxBase'
+import { SidebarSkeleton } from '~/layouts/MainSkeleton'
+
+export default async function ContributingPage() {
+ const contentFile = join(dirname(fileURLToPath(import.meta.url)), 'content.mdx')
+ const content = await readFile(contentFile, 'utf-8')
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/docs/pages/__dev-secret-auth.tsx b/apps/docs/app/dev-secret-auth/AuthForm.client.tsx
similarity index 68%
rename from apps/docs/pages/__dev-secret-auth.tsx
rename to apps/docs/app/dev-secret-auth/AuthForm.client.tsx
index de882c374f1..25cc4ed1faa 100644
--- a/apps/docs/pages/__dev-secret-auth.tsx
+++ b/apps/docs/app/dev-secret-auth/AuthForm.client.tsx
@@ -1,20 +1,12 @@
-import { useRouter } from 'next/router'
+'use client'
+
+import { useRouter } from 'next/navigation'
+
import { Button_Shadcn_ } from 'ui'
+
import { auth } from '~/lib/userAuth'
-export function getServerSideProps() {
- if (process.env.NEXT_PUBLIC_DEV_AUTH_PAGE === 'true') {
- return {
- props: {},
- }
- }
-
- return {
- notFound: true,
- }
-}
-
-export default function DevOnlySecretAuth() {
+export function DevSecretAuthForm() {
const router = useRouter()
function signInWithGitHub() {
@@ -37,7 +29,7 @@ export default function DevOnlySecretAuth() {
}
return (
-
+
Sign in
@@ -46,10 +38,10 @@ export default function DevOnlySecretAuth() {
the same domain.
diff --git a/apps/docs/app/dev-secret-auth/page.tsx b/apps/docs/app/dev-secret-auth/page.tsx
new file mode 100644
index 00000000000..54da5e984e9
--- /dev/null
+++ b/apps/docs/app/dev-secret-auth/page.tsx
@@ -0,0 +1,11 @@
+import { notFound } from 'next/navigation'
+
+import { DevSecretAuthForm } from './AuthForm.client'
+
+export default async function DevOnlySecretAuth() {
+ if (process.env.NEXT_PUBLIC_DEV_AUTH_PAGE !== 'true') {
+ throw notFound()
+ }
+
+ return
+}
diff --git a/apps/docs/app/error.tsx b/apps/docs/app/error.tsx
new file mode 100644
index 00000000000..3c721fbeb9d
--- /dev/null
+++ b/apps/docs/app/error.tsx
@@ -0,0 +1,22 @@
+'use client'
+
+import Link from 'next/link'
+import { Button } from 'ui'
+
+const ErrorPage = () => (
+
You can configure these resources using the Supabase Terraform provider:
{Object.keys(schema).map((resource) => (
-
+ Example usage{codeBlock`
resource "${resource}" "
{factors.map((factor) => (
@@ -270,6 +158,7 @@ export function UnenrollMFA() {
{factor.friendly_name}
{factor.factor_type}
{factor.status}
+
{factor.phone}
))}
@@ -280,164 +169,6 @@ export function UnenrollMFA() {
}
```
-### Add challenge step to login
-
-Once a user has logged in via their first factor (email+password, magic link, one time password, social login etc.) you need to perform a check if any additional factors need to be verified.
-
-This can be done by using the `supabase.auth.mfa.getAuthenticatorAssuranceLevel()` API. When the user signs in and is redirected back to your app, you should call this method to extract the user's current and next authenticator assurance level (AAL).
-
-Therefore if you receive a `currentLevel` which is `aal1` but a `nextLevel` of `aal2`, the user should be given the option to go through MFA.
-
-Below is a table that explains the combined meaning.
-
-| Current Level | Next Level | Meaning |
-| ------------: | :--------- | :------------------------------------------------------- |
-| `aal1` | `aal1` | User does not have MFA enrolled. |
-| `aal1` | `aal2` | User has an MFA factor enrolled but has not verified it. |
-| `aal2` | `aal2` | User has verified their MFA factor. |
-| `aal2` | `aal1` | User has disabled their MFA factor. (Stale JWT.) |
-
-#### Example: React
-
-Adding the challenge step to login depends heavily on the architecture of your app. However, a fairly common way to structure React apps is to have a large component (often named `App`) which contains most of the authenticated application logic.
-
-This example will wrap this component with logic that will show an MFA challenge screen if necessary, before showing the full application. This is illustrated in the `AppWithMFA` example below.
-
-```tsx
-function AppWithMFA() {
- const [readyToShow, setReadyToShow] = useState(false)
- const [showMFAScreen, setShowMFAScreen] = useState(false)
-
- useEffect(() => {
- ;(async () => {
- try {
- const { data, error } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
- if (error) {
- throw error
- }
-
- console.log(data)
-
- if (data.nextLevel === 'aal2' && data.nextLevel !== data.currentLevel) {
- setShowMFAScreen(true)
- }
- } finally {
- setReadyToShow(true)
- }
- })()
- }, [])
-
- if (readyToShow) {
- if (showMFAScreen) {
- return
- }
-
- return
- }
-
- return <>>
-}
-```
-
-- `supabase.auth.mfa.getAuthenticatorAssuranceLevel()` does return a promise.
- Don't worry, this is a very fast method (microseconds) as it rarely uses the
- network.
-- `readyToShow` only makes sure the AAL check completes before showing any
- application UI to the user.
-- If the current level can be upgraded to the next one, the MFA screen is
- shown.
-- Once the challenge is successful, the `App` component is finally rendered on
- screen.
-
-Below is the component that implements the challenge and verify logic.
-
-```tsx
-function AuthMFA() {
- const [verifyCode, setVerifyCode] = useState('')
- const [error, setError] = useState('')
-
- const onSubmitClicked = () => {
- setError('')
- ;(async () => {
- const factors = await supabase.auth.mfa.listFactors()
- if (factors.error) {
- throw factors.error
- }
-
- const totpFactor = factors.data.totp[0]
-
- if (!totpFactor) {
- throw new Error('No TOTP factors found!')
- }
-
- const factorId = totpFactor.id
-
- const challenge = await supabase.auth.mfa.challenge({ factorId })
- if (challenge.error) {
- setError(challenge.error.message)
- throw challenge.error
- }
-
- const challengeId = challenge.data.id
-
- const verify = await supabase.auth.mfa.verify({
- factorId,
- challengeId,
- code: verifyCode,
- })
- if (verify.error) {
- setError(verify.error.message)
- throw verify.error
- }
- })()
- }
-
- return (
- <>
-
Please enter the code from your authenticator app.
- {error &&
{error}
}
- setVerifyCode(e.target.value.trim())}
- />
-
- >
- )
-}
-```
-
-- You can extract the available MFA factors for the user by calling
- `supabase.auth.mfa.listFactors()`. Don't worry this method is also very quick
- and rarely uses the network.
-- If `listFactors()` returns more than one factor (or of a different type) you
- should present the user with a choice. For simplicity this is not shown in
- the example.
-- Each time the user presses the "Submit" button a new challenge is created for
- the chosen factor (in this case the first one) and it is immediately
- verified. Any errors are displayed to the user.
-- On successful verification, the client library will refresh the session in
- the background automatically and finally call the `onSuccess` callback, which
- will show the authenticated `App` component on screen.
-
-### Enforce rules for MFA logins
-
-Adding MFA to your app's UI does not in-and-of-itself offer a higher level of security to your users. You also need to enforce the MFA rules in your application's database, APIs, and server-side rendering.
-
-Depending on your application's needs, there are three ways you can choose to enforce MFA.
-
-1. **Enforce for all users (new and existing).**
- Any user account will have to enroll MFA to continue using your app.
- The application will not allow access without going through MFA first.
-2. **Enforce for new users only.**
- Only new users will be forced to enroll MFA, while old users will be encouraged
- to do so.
- The application will not allow access for new users without going through MFA
- first.
-3. **Enforce only for users that have opted-in.**
- Users that want MFA can enroll in it and the application will not allow access
- without going through MFA first.
-
#### Database
Your app should sufficiently deny or allow access to tables or rows based on the user's current and possible authenticator levels.
@@ -562,19 +293,19 @@ If your application uses the Supabase Database, Storage or Edge Functions, just
## Frequently asked questions
-### Why is there a challenge and verify API when challenge does not do much?
+
-TOTP is not going to be the only MFA factor Supabase Auth is going to support in the future. By separating out the challenge and verify steps, we're making the library forward compatible with new factors we may add in the future -- such as SMS or WebAuthn. For example, for SMS the `challenge` endpoint would actually send out the SMS with the authentication code. For convenience, you may use `challengeAndVerify` to create and verify a challenge in a single step.
-
-### What's inside the QR code?
-
-The TOTP QR code encodes a URI with the `otpauth` scheme. It was [initially introduced by Google Authenticator](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) but is now universally accepted by all authenticator apps.
-
-### How long is the TOTP code valid for?
-
-In our TOTP implementation, each generated code remains valid for one interval, which spans 30 seconds. To account for minor time discrepancies, we allow for a one-interval clock skew. This ensures that users can successfully authenticate within this timeframe, even if there are slight variations in system clocks.
-
-### How do I check _when_ a user went through MFA?
+How do I check when a user went through MFA?}
+id="how-do-i-check-when-a-user-went-through-mfa"
+>
Access tokens issued by Supabase Auth contain an `amr` (Authentication Methods Reference) claim. It is an array of objects that indicate what authentication methods the user has used so far.
@@ -620,6 +351,7 @@ Currently recognized authentication methods are:
link).
- `totp` - a TOTP additional factor.
- `sso/saml` - any Single Sign On (SAML) method.
+- `anonymous` - any anonymous sign in.
The following additional claims are available when using PKCE flow:
@@ -629,3 +361,7 @@ The following additional claims are available when using PKCE flow:
- `email_change` - any login resulting from a change in email.
More authentication methods will be added over time as we increase the number of authentication methods supported by Supabase.
+
+
+
+
diff --git a/apps/docs/content/guides/auth/auth-mfa/phone.mdx b/apps/docs/content/guides/auth/auth-mfa/phone.mdx
new file mode 100644
index 00000000000..62eb80f8fa9
--- /dev/null
+++ b/apps/docs/content/guides/auth/auth-mfa/phone.mdx
@@ -0,0 +1,313 @@
+---
+id: 'auth-mfa-phone'
+title: 'Multi-Factor Authentication (Phone)'
+description: 'Add an additional layer of security with phone (SMS or WhatsApp) multi-factor-authentication.'
+---
+
+## How does phone multi-factor-authentication work?
+
+Phone multi-factor authentication involves a shared code generated by Supabase Auth and the end user. The code is delivered via a messaging channel, such as SMS or WhatsApp, and the user uses the code to authenticate to Supabase Auth.
+
+The phone messaging configuration for MFA is shared with [phone auth login](/docs/guides/auth/phone-login). The same provider configuration that is used for phone login is used for MFA. You can also use the [Send SMS Hook](/docs/guides/auth/auth-hooks/send-sms-hook) if you need to use an MFA (Phone) messaging provider different from what is supported natively.
+
+Below is a flow chart illustrating how the Enrollment and Verify APIs work in the context of MFA (Phone).
+
+
+
+Phone MFA is part of the Auth Advanced MFA Add-on and costs an additional $75 per month for the first project in the organization and an additional $10 per month for additional projects.
+
+### Add enrollment flow
+
+An enrollment flow provides a UI for users to set up additional authentication factors. Most applications add the enrollment flow in two places within their app:
+
+1. Right after login or sign up.
+ This allows users quickly set up Multi Factor Authentication (MFA) post login or account creation. Where possible, encourage all users to set up MFA. Many applications offer this as an opt-in step in an
+ effort to reduce onboarding friction.
+2. From within a settings page.
+ Allows users to set up, disable or modify their MFA settings.
+
+As far as possible, maintain a generic flow that you can reuse in both cases with minor modifications.
+
+Enrolling a factor for use with MFA takes three steps for phone MFA:
+
+1. Call `supabase.auth.mfa.enroll()`.
+2. Calling the `supabase.auth.mfa.challenge()` API. This sends a code via SMS or WhatsApp and prepares Supabase Auth to accept a verification code from the user.
+3. Calling the `supabase.auth.mfa.verify()` API. `supabase.auth.mfa.challenge()` returns a challenge ID.
+ This verifies that the code issued by Supabase Auth matches the code input by the user. If the verification succeeds, the factor
+ immediately becomes active for the user account. If not, you should repeat
+ steps 2 and 3.
+
+#### Example: React
+
+Below is an example that creates a new `EnrollMFA` component that illustrates the important pieces of the MFA enrollment flow.
+
+- When the component appears on screen, the `supabase.auth.mfa.enroll()` API is
+ called once to start the process of enrolling a new factor for the current
+ user.
+- A challenge is created using the `supabase.auth.mfa.challenge()` API and the
+ code from the user is submitted for verification using the
+ `supabase.auth.mfa.verify()` challenge.
+- `onEnabled` is a callback that notifies the other components that enrollment
+ has completed.
+- `onCancelled` is a callback that notifies the other components that the user
+ has clicked the `Cancel` button.
+
+```tsx
+export function EnrollMFA({
+ onEnrolled,
+ onCancelled,
+}: {
+ onEnrolled: () => void
+ onCancelled: () => void
+}) {
+ const [phoneNumber, setPhoneNumber] = useState('')
+ const [factorId, setFactorId] = useState('')
+ const [verifyCode, setVerifyCode] = useState('')
+ const [error, setError] = useState('')
+ const [challengeId, setChallengeId] = useState('')
+
+ const onEnableClicked = () => {
+ setError('')
+ ;(async () => {
+ const verify = await auth.mfa.verify({
+ factorId,
+ challengeId,
+ code: verifyCode,
+ })
+ if (verify.error) {
+ setError(verify.error.message)
+ throw verify.error
+ }
+
+ onEnrolled()
+ })()
+ }
+ const onEnrollClicked = async () => {
+ setError('')
+ try {
+ const factor = await auth.mfa.enroll({
+ phone: phoneNumber,
+ factorType: 'phone',
+ })
+ if (factor.error) {
+ setError(factor.error.message)
+ throw factor.error
+ }
+
+ setFactorId(factor.data.id)
+ } catch (error) {
+ setError('Failed to Enroll the Factor.')
+ }
+ }
+
+ const onSendOTPClicked = async () => {
+ setError('')
+ try {
+ const challenge = await auth.mfa.challenge({ factorId })
+ if (challenge.error) {
+ setError(challenge.error.message)
+ throw challenge.error
+ }
+
+ setChallengeId(challenge.data.id)
+ } catch (error) {
+ setError('Failed to resend the code.')
+ }
+ }
+
+ return (
+ <>
+ {error &&
{error}
}
+ setPhoneNumber(e.target.value.trim())}
+ />
+ setVerifyCode(e.target.value.trim())}
+ />
+
+
+
+
+ >
+ )
+}
+```
+
+### Add a challenge step to login
+
+Once a user has logged in via their first factor (email+password, magic link, one time password, social login etc.) you need to perform a check if any additional factors need to be verified.
+
+This can be done by using the `supabase.auth.mfa.getAuthenticatorAssuranceLevel()` API. When the user signs in and is redirected back to your app, you should call this method to extract the user's current and next authenticator assurance level (AAL).
+
+Therefore if you receive a `currentLevel` which is `aal1` but a `nextLevel` of `aal2`, the user should be given the option to go through MFA.
+
+Below is a table that explains the combined meaning.
+
+| Current Level | Next Level | Meaning |
+| ------------: | :--------- | :------------------------------------------------------- |
+| `aal1` | `aal1` | User does not have MFA enrolled. |
+| `aal1` | `aal2` | User has an MFA factor enrolled but has not verified it. |
+| `aal2` | `aal2` | User has verified their MFA factor. |
+| `aal2` | `aal1` | User has disabled their MFA factor. (Stale JWT.) |
+
+#### Example: React
+
+Adding the challenge step to login depends heavily on the architecture of your app. However, a fairly common way to structure React apps is to have a large component (often named `App`) which contains most of the authenticated application logic.
+
+This example will wrap this component with logic that will show an MFA challenge screen if necessary, before showing the full application. This is illustrated in the `AppWithMFA` example below.
+
+```tsx
+function AppWithMFA() {
+ const [readyToShow, setReadyToShow] = useState(false)
+ const [showMFAScreen, setShowMFAScreen] = useState(false)
+
+ useEffect(() => {
+ ;(async () => {
+ try {
+ const { data, error } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
+ if (error) {
+ throw error
+ }
+
+ console.log(data)
+
+ if (data.nextLevel === 'aal2' && data.nextLevel !== data.currentLevel) {
+ setShowMFAScreen(true)
+ }
+ } finally {
+ setReadyToShow(true)
+ }
+ })()
+ }, [])
+
+ if (readyToShow) {
+ if (showMFAScreen) {
+ return
+ }
+
+ return
+ }
+
+ return <>>
+}
+```
+
+- `supabase.auth.mfa.getAuthenticatorAssuranceLevel()` does return a promise.
+ Don't worry, this is a very fast method (microseconds) as it rarely uses the
+ network.
+- `readyToShow` only makes sure the AAL check completes before showing any
+ application UI to the user.
+- If the current level can be upgraded to the next one, the MFA screen is
+ shown.
+- Once the challenge is successful, the `App` component is finally rendered on
+ screen.
+
+Below is the component that implements the challenge and verify logic.
+
+```tsx
+function AuthMFA() {
+ const [verifyCode, setVerifyCode] = useState('')
+ const [error, setError] = useState('')
+ const [factorId, setFactorId] = useState('')
+ const [challengeId, setChallengeId] = useState('')
+ const [phoneNumber, setPhoneNumber] = useState('')
+
+ const startChallenge = async () => {
+ setError('')
+ try {
+ const factors = await supabase.auth.mfa.listFactors()
+ if (factors.error) {
+ throw factors.error
+ }
+
+ const phoneFactor = factors.data.phone[0]
+
+ if (!phoneFactor) {
+ throw new Error('No phone factors found!')
+ }
+
+ const factorId = phoneFactor.id
+ setFactorId(factorId)
+ setPhoneNumber(phoneFactor.phone)
+
+ const challenge = await supabase.auth.mfa.challenge({ factorId })
+ if (challenge.error) {
+ setError(challenge.error.message)
+ throw challenge.error
+ }
+
+ setChallengeId(challenge.data.id)
+ } catch (error) {
+ setError(error.message)
+ }
+ }
+
+ const verifyCode = async () => {
+ setError('')
+ try {
+ const verify = await supabase.auth.mfa.verify({
+ factorId,
+ challengeId,
+ code: verifyCode,
+ })
+ if (verify.error) {
+ setError(verify.error.message)
+ throw verify.error
+ }
+ } catch (error) {
+ setError(error.message)
+ }
+ }
+
+ return (
+ <>
+
Please enter the code sent to your phone.
+ {phoneNumber &&
Phone number: {phoneNumber}
}
+ {error &&
{error}
}
+ setVerifyCode(e.target.value.trim())}
+ />
+ {!challengeId ? (
+
+ ) : (
+
+ )}
+ >
+ )
+}
+```
+
+- You can extract the available MFA factors for the user by calling
+ `supabase.auth.mfa.listFactors()`. Don't worry this method is also very quick
+ and rarely uses the network.
+- If `listFactors()` returns more than one factor (or of a different type) you
+ should present the user with a choice. For simplicity this is not shown in
+ the example.
+- Phone numbers are unique per user. Users can only have one verified phone factor with a given phone number.
+ Attempting to enroll a new phone factor alongside an existing verified factor with the same number will result in an error.
+- Each time the user presses the "Submit" button a new challenge is created for
+ the chosen factor (in this case the first one)
+- On successful verification, the client library will refresh the session in
+ the background automatically and finally call the `onSuccess` callback, which
+ will show the authenticated `App` component on screen.
+
+### Security Configuration
+
+Each code is valid for up to 5 minutes, after which a new one can be sent. Successive codes remain valid until expiry. When possible choose the longest code length acceptable to your use case, at a minimum of 6. This can be configured in the [Authentication Settings](/dashboard/project/_/settings/auth).
+
+Please be aware that Phone MFA is vulnerable to SIM swap attacks where an attacker will call a mobile provider and ask to port the target's phone number to a new SIM card and then use the said SIM card to intercept an MFA code. Please evaluate the your application's tolerance for such an attack. You can read more about SIM swapping attacks [here](https://en.wikipedia.org/wiki/SIM_swap_scam)
diff --git a/apps/docs/content/guides/auth/auth-mfa/totp.mdx b/apps/docs/content/guides/auth/auth-mfa/totp.mdx
new file mode 100644
index 00000000000..cc6eed42b88
--- /dev/null
+++ b/apps/docs/content/guides/auth/auth-mfa/totp.mdx
@@ -0,0 +1,320 @@
+---
+id: 'auth-mfa-totp'
+title: 'Multi-Factor Authentication (TOTP)'
+description: 'Add an additional layer of security to your apps with TOTP multi-factor authentication.'
+---
+
+## How does app authenticator multi-factor authentication work?
+
+App Authenticator (TOTP) multi-factor authentication involves a timed one-time password generated from an authenticator app in the control of users. It uses a QR Code which to transmit a shared secret used to generate a One Time Password. A user can scan a QR code with their phone to capture a shared secret required for subsequent authentication.
+
+The use of a QR code was [initially introduced by Google Authenticator](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) but is now universally accepted by all authenticator apps. The QR code has an alternate representation in URI form following the `otpauth` scheme such as: `otpauth://totp/supabase:alice@supabase.com?secret=&issuer=supabase` which a user can manually input in cases where there is difficulty rendering a QR Code.
+
+Below is a flow chart illustrating how the Enrollment, Challenge, and Verify APIs work in the context of MFA (TOTP).
+
+
+
+[TOTP MFA API](/docs/reference/javascript/auth-mfa-api) is free to use and is enabled on all Supabase projects by default.
+
+### Add enrollment flow
+
+An enrollment flow provides a UI for users to set up additional authentication factors. Most applications add the enrollment flow in two places within their app:
+
+1. Right after login or sign up.
+ This lets users quickly set up MFA immediately after they log in or create an
+ account. We recommend encouraging all users to set up MFA if that makes sense
+ for your application. Many applications offer this as an opt-in step in an
+ effort to reduce onboarding friction.
+2. From within a settings page.
+ Allows users to set up, disable or modify their MFA settings.
+
+Enrolling a factor for use with MFA takes three steps:
+
+1. Call `supabase.auth.mfa.enroll()`.
+ This method returns a QR code and a secret. Display the QR
+ code to the user and ask them to scan it with their authenticator application.
+ If they are unable to scan the QR code, show the secret in plain text which
+ they can type or paste into their authenticator app.
+2. Calling the `supabase.auth.mfa.challenge()` API.
+ This prepares Supabase Auth to accept a verification code from the user
+ and returns a challenge ID. In the case of Phone MFA this step also sends the verification code to the user.
+3. Calling the `supabase.auth.mfa.verify()` API.
+ This verifies that the user has indeed added the secret from step (1) into
+ their app and is working correctly. If the verification succeeds, the factor
+ immediately becomes active for the user account. If not, you should repeat
+ steps 2 and 3.
+
+#### Example: React
+
+Below is an example that creates a new `EnrollMFA` component that illustrates the important pieces of the MFA enrollment flow.
+
+- When the component appears on screen, the `supabase.auth.mfa.enroll()` API is
+ called once to start the process of enrolling a new factor for the current
+ user.
+- This API returns a QR code in the SVG format, which is shown on screen using
+ a normal `` tag by encoding the SVG as a data URL.
+- Once the user has scanned the QR code with their authenticator app, they
+ should enter the verification code within the `verifyCode` input field and
+ click on `Enable`.
+- A challenge is created using the `supabase.auth.mfa.challenge()` API and the
+ code from the user is submitted for verification using the
+ `supabase.auth.mfa.verify()` challenge.
+- `onEnabled` is a callback that notifies the other components that enrollment
+ has completed.
+- `onCancelled` is a callback that notifies the other components that the user
+ has clicked the `Cancel` button.
+
+```tsx
+/**
+ * EnrollMFA shows a simple enrollment dialog. When shown on screen it calls
+ * the `enroll` API. Each time a user clicks the Enable button it calls the
+ * `challenge` and `verify` APIs to check if the code provided by the user is
+ * valid.
+ * When enrollment is successful, it calls `onEnrolled`. When the user clicks
+ * Cancel the `onCancelled` callback is called.
+ */
+export function EnrollMFA({
+ onEnrolled,
+ onCancelled,
+}: {
+ onEnrolled: () => void
+ onCancelled: () => void
+}) {
+ const [factorId, setFactorId] = useState('')
+ const [qr, setQR] = useState('') // holds the QR code image SVG
+ const [verifyCode, setVerifyCode] = useState('') // contains the code entered by the user
+ const [error, setError] = useState('') // holds an error message
+
+ const onEnableClicked = () => {
+ setError('')
+ ;(async () => {
+ const challenge = await supabase.auth.mfa.challenge({ factorId })
+ if (challenge.error) {
+ setError(challenge.error.message)
+ throw challenge.error
+ }
+
+ const challengeId = challenge.data.id
+
+ const verify = await supabase.auth.mfa.verify({
+ factorId,
+ challengeId,
+ code: verifyCode,
+ })
+ if (verify.error) {
+ setError(verify.error.message)
+ throw verify.error
+ }
+
+ onEnrolled()
+ })()
+ }
+
+ useEffect(() => {
+ ;(async () => {
+ const { data, error } = await supabase.auth.mfa.enroll({
+ factorType: 'totp',
+ })
+ if (error) {
+ throw error
+ }
+
+ setFactorId(data.id)
+
+ // Supabase Auth returns an SVG QR code which you can convert into a data
+ // URL that you can place in an tag.
+ setQR(data.totp.qr_code)
+ })()
+ }, [])
+
+ return (
+ <>
+ {error &&
{error}
}
+
+ setVerifyCode(e.target.value.trim())}
+ />
+
+
+ >
+ )
+}
+```
+
+### Add a challenge step to login
+
+Once a user has logged in via their first factor (email+password, magic link, one time password, social login etc.) you need to perform a check if any additional factors need to be verified.
+
+This can be done by using the `supabase.auth.mfa.getAuthenticatorAssuranceLevel()` API. When the user signs in and is redirected back to your app, you should call this method to extract the user's current and next authenticator assurance level (AAL).
+
+Therefore if you receive a `currentLevel` which is `aal1` but a `nextLevel` of `aal2`, the user should be given the option to go through MFA.
+
+Below is a table that explains the combined meaning.
+
+| Current Level | Next Level | Meaning |
+| ------------: | :--------- | :------------------------------------------------------- |
+| `aal1` | `aal1` | User does not have MFA enrolled. |
+| `aal1` | `aal2` | User has an MFA factor enrolled but has not verified it. |
+| `aal2` | `aal2` | User has verified their MFA factor. |
+| `aal2` | `aal1` | User has disabled their MFA factor. (Stale JWT.) |
+
+#### Example: React
+
+Adding the challenge step to login depends heavily on the architecture of your app. However, a fairly common way to structure React apps is to have a large component (often named `App`) which contains most of the authenticated application logic.
+
+This example will wrap this component with logic that will show an MFA challenge screen if necessary, before showing the full application. This is illustrated in the `AppWithMFA` example below.
+
+```tsx
+function AppWithMFA() {
+ const [readyToShow, setReadyToShow] = useState(false)
+ const [showMFAScreen, setShowMFAScreen] = useState(false)
+
+ useEffect(() => {
+ ;(async () => {
+ try {
+ const { data, error } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
+ if (error) {
+ throw error
+ }
+
+ console.log(data)
+
+ if (data.nextLevel === 'aal2' && data.nextLevel !== data.currentLevel) {
+ setShowMFAScreen(true)
+ }
+ } finally {
+ setReadyToShow(true)
+ }
+ })()
+ }, [])
+
+ if (readyToShow) {
+ if (showMFAScreen) {
+ return
+ }
+
+ return
+ }
+
+ return <>>
+}
+```
+
+- `supabase.auth.mfa.getAuthenticatorAssuranceLevel()` does return a promise.
+ Don't worry, this is a very fast method (microseconds) as it rarely uses the
+ network.
+- `readyToShow` only makes sure the AAL check completes before showing any
+ application UI to the user.
+- If the current level can be upgraded to the next one, the MFA screen is
+ shown.
+- Once the challenge is successful, the `App` component is finally rendered on
+ screen.
+
+Below is the component that implements the challenge and verify logic.
+
+```tsx
+function AuthMFA() {
+ const [verifyCode, setVerifyCode] = useState('')
+ const [error, setError] = useState('')
+
+ const onSubmitClicked = () => {
+ setError('')
+ ;(async () => {
+ const factors = await supabase.auth.mfa.listFactors()
+ if (factors.error) {
+ throw factors.error
+ }
+
+ const totpFactor = factors.data.totp[0]
+
+ if (!totpFactor) {
+ throw new Error('No TOTP factors found!')
+ }
+
+ const factorId = totpFactor.id
+
+ const challenge = await supabase.auth.mfa.challenge({ factorId })
+ if (challenge.error) {
+ setError(challenge.error.message)
+ throw challenge.error
+ }
+
+ const challengeId = challenge.data.id
+
+ const verify = await supabase.auth.mfa.verify({
+ factorId,
+ challengeId,
+ code: verifyCode,
+ })
+ if (verify.error) {
+ setError(verify.error.message)
+ throw verify.error
+ }
+ })()
+ }
+
+ return (
+ <>
+
Please enter the code from your authenticator app.
+ {error &&
{error}
}
+ setVerifyCode(e.target.value.trim())}
+ />
+
+ >
+ )
+}
+```
+
+- You can extract the available MFA factors for the user by calling
+ `supabase.auth.mfa.listFactors()`. Don't worry this method is also very quick
+ and rarely uses the network.
+- If `listFactors()` returns more than one factor (or of a different type) you
+ should present the user with a choice. For simplicity this is not shown in
+ the example.
+- Each time the user presses the "Submit" button a new challenge is created for
+ the chosen factor (in this case the first one) and it is immediately
+ verified. Any errors are displayed to the user.
+- On successful verification, the client library will refresh the session in
+ the background automatically and finally call the `onSuccess` callback, which
+ will show the authenticated `App` component on screen.
+
+## Frequently asked questions
+
+
+
+What's inside the QR code?}
+id="what-is-inside-the-qr-code"
+>
+
+
+
+How long is the TOTP code valid for?}
+id="how-long-is-the-totp-code-valid-for"
+>
+
+In our TOTP implementation, each generated code remains valid for one interval, which spans 30 seconds. To account for minor time discrepancies, we allow for a one-interval clock skew. This ensures that users can successfully authenticate within this timeframe, even if there are slight variations in system clocks.
+
+
+
+
diff --git a/apps/docs/content/guides/auth/auth-smtp.mdx b/apps/docs/content/guides/auth/auth-smtp.mdx
index ed80f9780cd..cac7981f737 100644
--- a/apps/docs/content/guides/auth/auth-smtp.mdx
+++ b/apps/docs/content/guides/auth/auth-smtp.mdx
@@ -1,38 +1,142 @@
---
id: 'auth-smtp'
-title: 'Configure a Custom SMTP'
+title: 'Send emails with custom SMTP'
description: 'Moving towards production: Configuring a custom SMTP provider'
---
-## Auth SMTP
+If you're using Supabase Auth with the following configuration:
-At present, you can trial the Supabase platform by sending up to **3** emails per hour via the built-in service. The default email service as a whole is offered on a best effort basis: we will do our best to maintain it and will review usage of the service on a regular basis to see if the email service should be continued.
+- Email and password accounts
+- Passwordless accounts using one-time passwords or links sent over email (OTP, magic link, invites)
+- Email-based user invitations from the [Users page](/dashboard/project/_/auth/users) or from the Auth admin APIs
+- Social login with email confirmation
-As you progress toward production, you may find yourself wanting for a custom SMTP service in order to increase your limits. A custom SMTP server will allow you to set your own cap on the number of emails sent per hour.
+You will need to set up a custom SMTP server to handle the delivery of messages to your users.
-Beyond rate limits, an SMTP server might also help with:
+To get you started and let you explore and set up email message templates for your application, Supabase provides a simple SMTP server for all projects. This server imposes a few important restrictions and is not meant for production use.
-- Deliverability and Reputation Management
-- Scalability
-- Analytics and Tracking
-- Compliance and Anti Spam measures
+**Send messages only to pre-authorized addresses.**
-## How to set up SMTP
+Unless you configure a custom SMTP server for your project, Supabase Auth will refuse to deliver messages to addresses that are not part of the project's team. You can manage this in the [Team tab](/dashboard/org/_/team) of the organization's settings.
-Head over to [Auth Settings Page](https://supabase.com/dashboard/project/_/settings/auth) and hit "Enable Custom SMTP" under the SMTP Provider section.
+For example, if your project's organization has these member accounts `person-a@example.com`, `person-b@example.com` and `person-c@example.com` then Supabase Auth will only send messages to these addresses. All other addresses will fail with the error message _Email address not authorized._
-Fill in fields below with the relevant details obtained from your custom SMTP provider:
+**Significant rate-limits that can change over time.**
-
+To maintain the health and reputation of the default SMTP sending service, the number of messages your project can send is limited and can change without notice. Currently this value is set to auth.rate_limits.email.inbuilt_smtp_per_hour messages per hour.
-## How to update email rate limits
+**No SLA guarantee on message delivery or uptime for the default SMTP service.**
-After adding a custom SMTP provider, you can head over to [Auth > Rate Limits](https://supabase.com/dashboard/project/_/auth/rate-limits) to configure the rate limits for sending emails.
+The default SMTP service is provided as best-effort only and intended for the following non-production use cases:
-### SMTP providers
+- Exploring and getting started with Supabase Auth
+- Setting up and testing email templates with the members of the project's team
+- Building toy projects, demos or any non-mission-critical application
-You can use Supabase Auth with any major SMTP provider of your choosing. Some SMTP providers you could consider using are:
+We urge all customers to set up custom SMTP server for all other use cases.
-- [Twilio SendGrid](https://docs.sendgrid.com/for-developers/sending-email/integrating-with-the-smtp-api)
+## How to set up a custom SMTP server?
+
+Supabase Auth works with any email sending service that supports the SMTP protocol. First you will need to choose a service, create an account (if you already do not have one) and obtain the SMTP server settings and credentials for your account. These include: the SMTP server host, port, user and password. You will also need to choose a default From address, usually something like `no-reply@example.com`.
+
+A non-exhaustive list of services that work with Supabase Auth is:
+
+- [Resend](https://resend.com/docs/send-with-supabase-smtp)
- [AWS SES](https://docs.aws.amazon.com/ses/latest/dg/send-email-smtp.html)
-- [Resend](https://resend.com/docs/dashboard/emails/introduction)
+- [Postmark](https://postmarkapp.com/developer/user-guide/send-email-with-smtp)
+- [Twilio SendGrid](https://www.twilio.com/docs/sendgrid/for-developers/sending-email/getting-started-smtp)
+- [ZeptoMail](https://www.zoho.com/zeptomail/help/smtp-home.html)
+- [Brevo](https://help.brevo.com/hc/en-us/articles/7924908994450-Send-transactional-emails-using-Brevo-SMTP)
+
+Once you've set up your account with an email sending service, head to the [Authentication settings page](/dashboard/project/_/settings/auth) to enable and configure custom SMTP.
+
+Once you save these settings, your project's Auth server will send messages to all addresses. To protect the reputation of your newly set up service a low rate-limit of 30 messages per hour is imposed. To adjust this to an acceptable value for your use case head to the [Rate Limits configuration page](/dashboard/project/_/auth/rate-limits).
+
+## Dealing with abuse: How to maintain the sending reputation of your SMTP server?
+
+As you make your application known to the public and it grows in popularity, you can expect to see a few types of abuse that can negatively impact the reputation of your sending domain.
+
+A common source of abuse is bots or attackers signing up users to your application.
+
+They use lists of known email addresses to sign up users to your project with pre-determined passwords. These can vary in scale and intensity: sometimes the bots slowly send sign up requests over many months, or they send a lot of requests at once.
+
+Usually the goal for this behavior is:
+
+- To negatively affect your email sending reputation, after which they might ask for a ransom promising to stop the behavior.
+- To cause a short-term or even long-term Denial of Service attack on your service, by preventing new account creation, sign ins with magic links or one-time passwords, or to severely impact important security flows in your application (such as reset password or forgot password).
+- To force you to reduce the security posture of your project, such as by disabling email confirmations. At that point, they may target specific or a broad number of users by creating an account in their name. Then they can use social engineering techniques to trick them to use your application in such a way that both attacker and victim have access to the same account.
+
+Mitigation strategies:
+
+- [Configure CAPTCHA protection](/docs/guides/auth/auth-captcha) for your project, which is the most effective way to control bots in this scenario. You can use CAPTCHA services which provide invisible challenges where real users won't be asked to solve puzzles most of the time.
+- Prefer social login (OAuth) or SSO with SAML instead of email-based authentication flows in your apps.
+- Prefer passwordless authentication (one-time password) as this limits the attacker's value to gain from this behavior.
+- Do not disable email confirmations under pressure.
+
+### Additional best practices
+
+**Set up and maintain DKIM, DMARC and SPF configurations.**
+
+Work with your email sending service to configure [DKIM, DMARC and SPF](https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/) for your sending domain. This will significantly increase the deliverability of your messages.
+
+**Set up a custom domain.**
+
+Authentication messages often contain links to your project's Auth server. [Setting up a custom domain](/docs/guides/platform/custom-domains) will reduce the likelihood of your messages being picked up as spam due to another Supabase project's bad reputation.
+
+**Don't mix Auth emails with marketing emails.**
+
+Use separate services for Auth and marketing messages. If the reputation of one falls, it won't affect your whole application or operation.
+
+This includes:
+
+- Use a separate sending domain for authentication -- `auth.example.com` and a separate domain for marketing `marketing.example.com`.
+- Use a separate From address -- `no-reply@auth.example.com` vs `no-reply@marketing.example.com`.
+
+**Have another SMTP service set up on stand-by.**
+
+In case the primary SMTP service you're using is experiencing difficulty, or your account is under threat of being blocked due to spam, you have another service to quickly turn to.
+
+**Use consistent branding and focused content.**
+
+Make sure you've separated out authentication messages from marketing messages.
+
+- Don't include promotional content as part of authentication messages.
+- Avoid talking about what your application is inside authentication messages. This can be picked up by automated spam filters which will classify the message as marketing and increase its chances of being regarded as spam. This problem is especially apparent if your project is related to: Web3, Blockchain, AI, NFTs, Gambling, Pornography.
+- Avoid taglines or other short-form marketing material in authentication messages.
+- Reduce the number of links and call-to-actions in authentication messages.
+- Change the authentication messages templates infrequently. Prefer a single big change over multiple smaller changes.
+- Avoid A/B testing content in authentication messages.
+- Use a separate base template (HTML) from your marketing messages.
+- Avoid the use of email signatures in authentication messages. If you do, make sure the signatures are different in style and content from your marketing messages.
+- Use short and to-the-point subject lines. Avoid or reduce the number of emojis in subjects.
+- Reduce the number of images placed in authentication messages.
+- Avoid including user-provided data such as names, usernames, email addresses or salutations in authentication messages. If you do, make sure they are sanitized.
+
+**Prepare for large surges ahead of time.**
+
+If you are planning on having a large surge of users coming at a specific time, work with your email sending service to adjust the rate limits and their expectations accordingly. Most email sending services dislike spikes in the number of messages being sent, and this may affect your sending reputation.
+
+Consider implementing additional protections for such events:
+
+- Build a queuing or waitlist system instead of allowing direct sign-up, which will help you control the number of messages being sent from the email sending service.
+- Disable email-based sign ups for the event and use social login only. Alternatively you can deprioritize the email-based sign-up flows for the event by hiding them in the UI or making them harder to reach.
+
+**Use the Send Email Auth Hook for more control.**
+
+If you need more control over the sending process, instead of using a SMTP server you can use the [Send Email Auth Hook](/docs/guides/auth/auth-hooks/send-email-hook). This can be useful in advanced scenarios such as:
+
+- You want to use React or a different email templating engine.
+- You want to use an email sending service that does not provide an SMTP service, or the non-SMTP API is more powerful.
+- You want to queue up messages instead of sending them immediately, in an effort to smooth out spikes in email sending or do additional filtering (avoid repetitive messages).
+- You want to use multiple email sending services to increase reliability (if primary service is unavailable, use backup service automatically).
+- You want to use different email sending services based on the email address or user data (e.g. service A for users in the USA, service B for users in the EU, service C for users in China).
+- You want to add or include additional email headers in messages, for tracking or other reasons.
+- You want to add attachments to the messages (generally not recommended).
+- You want to add [S/MIME signatures](https://en.wikipedia.org/wiki/S/MIME) to messages.
+- You want to use an email server not open to the Internet, such as some corporate or government mail servers.
+
+**Increase the duration of user sessions.**
+
+Having short lived [user sessions](/docs/guides/auth/sessions) can be problematic for email sending, as it forces active users to sign-in frequently, increasing the number of messages needed to be sent. Consider increasing the maximum duration of user sessions. If you do see an unnecessary increase in logins without a clear cause, check your frontend application for bugs.
+
+If you are using a [SSR](/docs/guides/auth/server-side) framework on the frontend and are seeing an increased number of user logins without a clear cause, check your set up. Make sure to keep the `@supabase/ssr` package up to date and closely follow the guides we publish. Make sure that the middleware components of your SSR frontend works as intended and matches the guides we've published. Sometimes a misplaced `return` or conditional can cause early session termination.
diff --git a/apps/docs/content/guides/auth/debugging/error-codes.mdx b/apps/docs/content/guides/auth/debugging/error-codes.mdx
new file mode 100644
index 00000000000..92392b22fc0
--- /dev/null
+++ b/apps/docs/content/guides/auth/debugging/error-codes.mdx
@@ -0,0 +1,110 @@
+---
+id: 'auth-error-codes'
+title: 'Error Codes'
+description: 'Supabase Auth Error Codes'
+subtitle: 'Learn about the Auth error codes and how to resolve them'
+---
+
+## Auth Error Codes
+
+Supabase Auth can return various errors when using its API. This guide explains how to handle these errors effectively across different programming languages.
+
+## Error Types
+
+Supabase Auth errors are generally categorized into two main types:
+
+- API Errors: Originate from the Supabase Auth API.
+- Client Errors: Originate from the client library's state.
+
+Client errors differ by language so do refer to the appropriate section below:
+
+
+
+All errors originating from the `supabase.auth` namespace of the client library will be wrapped by the `AuthError` class.
+
+Error objects are split in a few classes:
+
+- `AuthApiError` -- errors which originate from the Supabase Auth API.
+ - Use `isAuthApiError` instead of `instanceof` checks to see if an error you caught is of this type.
+- `CustomAuthError` -- errors which generally originate from state in the client library.
+ - Use the `name` property on the error to identify the class of error received.
+
+Errors originating from the server API classed as `AuthApiError` always have a `code` property that can be used to identify the error returned by the server. The `status` property is also present, encoding the HTTP status code received in the response.
+
+
+
+All errors originating from the `supabase.auth` namespace of the client library will be wrapped by the `AuthException` class.
+
+Error objects are split in a few classes. `AuthApiException` is an exception which originates from the Supabase Auth API.
+
+Errors originating from the server API classed as `AuthApiException` always have a `code` property that can be used to identify the error returned by the server. The `statusCode` property is also present, encoding the HTTP status code received in the response.
+
+
+
+All errors originating from the `supabase.auth` namespace of the client library will be a case of the `AuthError` enum.
+
+The `api(message:errorCode:underlyingData:underlyingResponse:)` case is a special case for errors which originates from the Supabase Auth API, this error always have an `errorCode` property that can be used to identify the error returned by the server.
+
+
+
+All errors originating from the `supabase.auth` namespace of the client library will be wrapped by the `AuthError` class.
+
+Error objects are split in a few classes. `AuthApiError` is an error which originate from the Supabase Auth API.
+
+Errors originating from the server API classed as `AuthApiError` always have a `code` property that can be used to identify the error returned by the server. The `status` property is also present, encoding the HTTP status code received in the response.
+
+
+
+All errors originating from the `supabase.auth` namespace of the JavaScript client library will be wrapped by the `AuthError` class.
+
+Error objects are split in a few classes:
+
+- `AuthApiError` -- errors which originate from the Supabase Auth API.
+ - Use `isAuthApiError` instead of `instanceof` checks to see if an error you caught is of this type.
+- `CustomAuthError` -- errors which generally originate from state in the client library.
+ - Use the `name` property on the error to identify the class of error received.
+
+Errors originating from the server API classed as `AuthApiError` always have a `code` property that can be used to identify the error returned by the server. The `status` property is also present, encoding the HTTP status code received in the response.
+
+
+
+
+
+## HTTP Status Codes
+
+Below are the most common HTTP status codes you might encounter, along with their meanings in the context of Supabase Auth:
+
+### [403 Forbidden](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403)
+
+Sent out in rare situations where a certain Auth feature is not available for the user, and you as the developer are not checking a precondition whether that API is available for the user.
+
+### [422 Unprocessable Entity](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422)
+
+Sent out when the API request is accepted, but cannot be processed because the user or Auth server is in a state where it cannot satisfy the request.
+
+### [429 Too Many Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429)
+
+Sent out when rate-limits are breached for an API. You should handle this status code often, especially in functions that authenticate a user.
+
+### [500 Internal Server Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500)
+
+Indicate that the Auth server's service is degraded. Most often it points to issues in your database setup such as a misbehaving trigger on a schema, function, view or other database object.
+
+### [501 Not Implemented](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501)
+
+Sent out when a feature is not enabled on the Auth server, and you are trying to use an API which requires it.
+
+The following table provides a comprehensive list of error codes you may encounter when working with Supabase Auth. Each error code is associated with a specific issue and includes a description to help you understand and resolve the problem efficiently.
+
+## Auth Error Codes Table
+
+
+
+## Best Practices for Error Handling
+
+- Always use `error.code` and `error.name` to identify errors, not string matching on error messages.
+- Avoid relying solely on HTTP status codes, as they may change unexpectedly.
diff --git a/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx b/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx
index cea09ebc099..59a5c388ae4 100644
--- a/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx
+++ b/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx
@@ -117,7 +117,7 @@ create table organization_settings (
sso_provider_id uuid unique,
-- name of the organization
name text,
- -- billing plan (paid, free, enterprise)
+ -- billing plan (paid, Free, Enterprise)
billing_plan text
);
```
@@ -415,3 +415,9 @@ Should you run into this problem, it is most likely a misconfiguration issue **o
At this time it is not possible to extract the RSA private key used by your project's Supabase Auth server. This is done to keep the private key as secure as possible, given that SAML does not offer an easy way to rotate keys without disrupting service. (Please use a SAML 2.0 Metadata URL whenever possible for this reason!)
If you really need access to the key, please [open a support ticket](https://supabase.com/dashboard/support/new) and we'll try to support you as best as possible.
+
+### Is multi-tenant SSO with SAML supported?
+
+Yes, Supabase supports multi-tenant Single Sign-On (SSO) using SAML 2.0. While the dashboard displays only one SAML field, you can set up multiple SAML connections using the Supabase CLI.
+Each connection is assigned a unique `sso_provider_id`, which is included in the user's JWT and can be used in Row Level Security (RLS) policies. You can configure custom attribute mappings for each connection to include tenant-specific information, such as roles.
+This setup allows you to implement multi-tenant SSO for multiple clients or organizations within a single application. For example, if you have an app with multiple clients using different Azure Active Directories, you can create separate SAML connections for each and use the `sso_provider_id` to manage access and apply appropriate security policies.
diff --git a/apps/docs/content/guides/auth/identities.mdx b/apps/docs/content/guides/auth/identities.mdx
index 41f265a8767..6607837877d 100644
--- a/apps/docs/content/guides/auth/identities.mdx
+++ b/apps/docs/content/guides/auth/identities.mdx
@@ -17,11 +17,12 @@ The user identity object contains the following attributes:
| Attributes | Type | Description |
| --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| id | `string` | The provider id returned by the provider. If the provider is an OAuth provider, the id refers to the user's account with the OAuth provider. If the provider is `email` or `phone`, the id is the user's id from the `auth.users` table. |
+| provider_id | `string` | The provider id returned by the provider. If the provider is an OAuth provider, the id refers to the user's account with the OAuth provider. If the provider is `email` or `phone`, the id is the user's id from the `auth.users` table. |
| user_id | `string` | The user's id that the identity is linked to. |
| identity_data | `object` | The identity metadata. For OAuth and SAML identities, this contains information about the user from the provider. |
-| identity_id | `string` | The unique id of the identity. |
+| id | `string` | The unique id of the identity. |
| provider | `string` | The provider name. |
+| email | `string` | The email is a generated column that references the optional email property in the identity_data |
| created_at | `string` | The timestamp that the identity was created. |
| last_sign_in_at | `string` | The timestamp that the identity was last used to sign in. |
| updated_at | `string` | The timestamp that the identity was last updated. |
diff --git a/apps/docs/content/guides/auth/jwts.mdx b/apps/docs/content/guides/auth/jwts.mdx
index 300347bb7c7..1ae41b0170a 100644
--- a/apps/docs/content/guides/auth/jwts.mdx
+++ b/apps/docs/content/guides/auth/jwts.mdx
@@ -66,7 +66,7 @@ The first segment `eyJhbGciOiJIUzI1NiJ9` is known as the "header", and when deco
}
```
-The second segment `eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ` contains our original payload:
+The second segment eyJzdWIiOiIwMDAxIiwibmFtZSI6IlNhbSBWaW1lcyIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE4MjM5MDIyfQ contains our original payload:
```js
{
@@ -162,6 +162,14 @@ You'll notice that this token is quite a bit longer, since it contains informati
}
```
+If using the service role key, you'll need to pass it into both the `apikey` and `authorization` headers (again, only do this from a secure environment such as your own server):
+
+```bash
+curl "$YOUR_PROJECT_URL/rest/v1/colors?select=name" \
+ -H "apikey: $YOUR_SERVICE_ROLE_KEY" \
+ -H "authorization: Bearer $YOUR_SERVICE_ROLE_KEY"
+```
+
Now that you understand what JWTs are and where they're used in Supabase, you can explore how to use them in combination with Row Level Security to start restricting access to certain tables, rows, and columns in your Postgres database.
## Resources
diff --git a/apps/docs/content/guides/auth/passwords.mdx b/apps/docs/content/guides/auth/passwords.mdx
index cead55cc21a..c671ac333dc 100644
--- a/apps/docs/content/guides/auth/passwords.mdx
+++ b/apps/docs/content/guides/auth/passwords.mdx
@@ -112,6 +112,25 @@ suspend fun signUpNewUser() {
}
```
+
+
+
+To sign up the user, call [signUp()](/docs/reference/python/auth-signup) with their email address and password.
+
+You can optionally specify a URL to redirect to after the user clicks the confirmation link. This URL must be configured as a [Redirect URL](/docs/guides/auth/redirect-urls), which you can do in the [dashboard](/dashboard/project/_/auth/url-configuration) for hosted projects, or in the [configuration file](/docs/guides/cli/config#auth.additional_redirect_urls) for self-hosted projects.
+
+If you don't specify a redirect URL, the user is automatically redirected to your site URL. This defaults to `localhost:3000`, but you can also configure this.
+
+```python
+data = await supabase.auth.sign_up({
+ 'email': 'example@email.com',
+ 'password': 'example-password',
+ 'options': {
+ 'email_redirect_to': 'https://example.com/welcome',
+ },
+})
+```
+
@@ -142,16 +161,19 @@ Your signup email template should contain the following HTML:
Create an API endpoint at `/auth/confirm` to handle the token exchange.
+
+
Create a new file at `app/auth/confirm/route.ts` and populate with the following:
```ts app/auth/confirm/route.ts
-import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'
+// The client you created from the Server-Side Auth instructions
+import { createClient } from '@/utils/supabase/server'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
@@ -162,24 +184,7 @@ export async function GET(request: NextRequest) {
redirectTo.pathname = next
if (token_hash && type) {
- const cookieStore = cookies()
- const supabase = createServerClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
- {
- cookies: {
- get(name: string) {
- return cookieStore.get(name)?.value
- },
- set(name: string, value: string, options: CookieOptions) {
- cookieStore.set({ name, value, ...options })
- },
- remove(name: string, options: CookieOptions) {
- cookieStore.delete({ name, ...options })
- },
- },
- }
- )
+ const supabase = createClient()
const { error } = await supabase.auth.verifyOtp({
type,
@@ -214,15 +219,27 @@ export const GET = async (event) => {
const type = url.searchParams.get('type') as EmailOtpType | null
const next = url.searchParams.get('next') ?? '/'
+ /**
+ * Clean up the redirect URL by deleting the Auth flow parameters.
+ *
+ * `next` is preserved for now, because it's needed in the error case.
+ */
+ const redirectTo = new URL(url)
+ redirectTo.pathname = next
+ redirectTo.searchParams.delete('token_hash')
+ redirectTo.searchParams.delete('type')
+
if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({ token_hash, type })
if (!error) {
- throw redirect(303, `/${next.slice(1)}`)
+ redirectTo.searchParams.delete('next')
+ redirect(303, redirectTo)
}
}
// return the user to an error page with some instructions
- throw redirect(303, '/auth/auth-code-error')
+ redirectTo.pathname = '/auth/error'
+ redirect(303, redirectTo)
}
```
@@ -233,7 +250,7 @@ export const GET = async (event) => {
Create a new file at `src/pages/auth/confirm.ts` and populate with the following:
```ts src/pages/auth/confirm.ts
-import { createServerClient } from '@supabase/ssr'
+import { createServerClient, parseCookieHeader } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
import { type APIRoute } from 'astro'
@@ -249,14 +266,11 @@ export const GET: APIRoute = async ({ request, cookies, redirect }) => {
import.meta.env.PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
- get(key) {
- return cookies.get(key)?.value
+ getAll() {
+ return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
- set(key, value, options) {
- cookies.set(key, value, options)
- },
- remove(key, options) {
- cookies.delete(key, options)
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value, options }) => cookies.set(name, value, options))
},
},
}
@@ -285,7 +299,7 @@ Create a new file at `app/routes/auth.confirm.tsx` and populate with the followi
```ts app/routes/auth.confirm.tsx
import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
-import { createServerClient, parse, serialize } from '@supabase/ssr'
+import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
export async function loader({ request }: LoaderFunctionArgs) {
@@ -296,18 +310,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
const headers = new Headers()
if (token_hash && type) {
- const cookies = parse(request.headers.get('Cookie') ?? '')
-
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
cookies: {
- get(key) {
- return cookies[key]
+ getAll() {
+ return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
- set(key, value, options) {
- headers.append('Set-Cookie', serialize(key, value, options))
- },
- remove(key, options) {
- headers.append('Set-Cookie', serialize(key, '', options))
+ setAll(key, value, options) {
+ headers.append('Set-Cookie', serializeCookieHeader(key, value, options))
},
},
})
@@ -333,6 +342,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
Create a new route in your express app and populate with the following:
```js app.js
+// The client you created from the Server-Side Auth instructions
+const { createClient } = require("./lib/supabase")
...
app.get("/auth/confirm", async function (req, res) {
const token_hash = req.query.token_hash
@@ -419,6 +430,18 @@ suspend fun signUpNewUser() {
}
```
+
+
+
+To sign up the user, call [signUp()](/docs/reference/python/auth-signup) with their email address and password:
+
+```python
+data = supabase.auth.sign_up({
+ 'email': 'example@email.com',
+ 'password': 'example-password',
+})
+```
+
@@ -487,11 +510,31 @@ suspend fun signInWithEmail() {
}
```
+
+
+
+When your user signs in, call [sign_in_with_password()](/docs/reference/python/auth-signinwithpassword) with their email address and password:
+
+```python
+data = client.auth.sign_in_with_password({
+ 'email': 'example@email.com',
+ 'password': 'example-password',
+})
+```
+
### Resetting a password
+
+
+
#### Step 1: Create a reset password page
Create a **reset password** page. This page should be publicly accessible.
@@ -535,6 +578,16 @@ supabase.gotrue.sendRecoveryEmail(
If you are on one of the Kotlin targets that have built-in support for redirect URL handling, such as Android, see [OAuth and OTP link verification](https://supabase.com/docs/reference/kotlin/initializing).
+
+
+
+```python
+client.auth.reset_password_email(
+ 'hello@example.com',
+ {'redirect_to':'http://example.com/account/update-password'}
+)
+```
+
@@ -542,31 +595,285 @@ If you are on one of the Kotlin targets that have built-in support for redirect
Create a **change password** page at the URL you specified in the previous step. This page should be accessible only to authenticated users.
-
-
-
Collect the user's new password and call `updateUser` to update their password.
-In the PKCE flow, the reset password link only works on the device or browser where the original request was made.
+The PKCE flow allows for server-side authentication. Unlike the implicit flow, which directly provides your app with the access token after the user clicks the confirmation link, the PKCE flow requires an intermediate token exchange step before you can get the access token.
-When the user clicks the link and is redirected to the change password page, their URL contains a `code` query param. If you aren't using the official Supabase libraries, extract the `code` from the URL and exchange it for a session. For example, in JavaScript, call [`supabase.auth.exchangeCodeForSession`](/docs/reference/javascript/auth-exchangecodeforsession). If you're using an official library, the code exchange is handled for you.
+##### Step 1: Update reset password email
-
+Update your reset password email template to send the token hash. See [Email Templates](/docs/guides/auth/auth-email-templates) for how to configure your email templates.
-If your page is server-rendered and the code isn't being automatically exchanged, check whether the Supabase client is being initialized eagerly. Your server-rendered page might be lazily initializing the Supabase client after user interaction, which means the automatic code exchange can't run.
+Your signup email template should contain the following HTML:
-You can either exchange the code yourself or initialize the Supabase client eagerly.
+```html
+
Reset Password
-
+
Follow this link to reset the password for your user:
+```
+
+##### Step 2: Create token exchange endpoint
+
+Create an API endpoint at `/auth/confirm` to handle the token exchange.
+
+
+
+
+
+
+Create a new file at `app/auth/confirm/route.ts` and populate with the following:
+
+```ts app/auth/confirm/route.ts
+import { type EmailOtpType } from '@supabase/supabase-js'
+import { cookies } from 'next/headers'
+import { NextRequest, NextResponse } from 'next/server'
+// The client you created from the Server-Side Auth instructions
+import { createClient } from '@/utils/supabase/server'
+
+export async function GET(request: NextRequest) {
+ const { searchParams } = new URL(request.url)
+ const token_hash = searchParams.get('token_hash')
+ const type = searchParams.get('type') as EmailOtpType | null
+ const next = searchParams.get('next') ?? '/'
+ const redirectTo = request.nextUrl.clone()
+ redirectTo.pathname = next
+
+ if (token_hash && type) {
+ const supabase = createClient()
+
+ const { error } = await supabase.auth.verifyOtp({
+ type,
+ token_hash,
+ })
+ if (!error) {
+ return NextResponse.redirect(redirectTo)
+ }
+ }
+
+ // return the user to an error page with some instructions
+ redirectTo.pathname = '/auth/auth-code-error'
+ return NextResponse.redirect(redirectTo)
+}
+```
+
+
+
+
+Create a new file at `src/routes/auth/confirm/+server.ts` and populate with the following:
+
+```ts src/routes/auth/confirm/+server.ts
+import { redirect } from '@sveltejs/kit'
+import { type EmailOtpType } from '@supabase/supabase-js'
+
+export const GET = async (event) => {
+ const {
+ url,
+ locals: { supabase },
+ } = event
+ const token_hash = url.searchParams.get('token_hash') as string
+ const type = url.searchParams.get('type') as EmailOtpType | null
+ const next = url.searchParams.get('next') ?? '/'
+
+ /**
+ * Clean up the redirect URL by deleting the Auth flow parameters.
+ *
+ * `next` is preserved for now, because it's needed in the error case.
+ */
+ const redirectTo = new URL(url)
+ redirectTo.pathname = next
+ redirectTo.searchParams.delete('token_hash')
+ redirectTo.searchParams.delete('type')
+
+ if (token_hash && type) {
+ const { error } = await supabase.auth.verifyOtp({ token_hash, type })
+ if (!error) {
+ redirectTo.searchParams.delete('next')
+ redirect(303, redirectTo)
+ }
+ }
+
+ // return the user to an error page with some instructions
+ redirectTo.pathname = '/auth/error'
+ redirect(303, redirectTo)
+}
+```
+
+
+
+
+
+Create a new file at `src/pages/auth/confirm.ts` and populate with the following:
+
+```ts src/pages/auth/confirm.ts
+import { createServerClient, parseCookieHeader } from '@supabase/ssr'
+import { type EmailOtpType } from '@supabase/supabase-js'
+import { type APIRoute } from 'astro'
+
+export const GET: APIRoute = async ({ request, cookies, redirect }) => {
+ const requestUrl = new URL(request.url)
+ const token_hash = requestUrl.searchParams.get('token_hash')
+ const type = requestUrl.searchParams.get('type') as EmailOtpType | null
+ const next = requestUrl.searchParams.get('next') || '/'
+
+ if (token_hash && type) {
+ const supabase = createServerClient(
+ import.meta.env.PUBLIC_SUPABASE_URL,
+ import.meta.env.PUBLIC_SUPABASE_ANON_KEY,
+ {
+ cookies: {
+ getAll() {
+ return parseCookieHeader(request.headers.get('Cookie') ?? '')
+ },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value, options }) => cookies.set(name, value, options))
+ },
+ },
+ }
+ )
+
+ const { error } = await supabase.auth.verifyOtp({
+ type,
+ token_hash,
+ })
+
+ if (!error) {
+ return redirect(next)
+ }
+ }
+
+ // return the user to an error page with some instructions
+ return redirect('/auth/auth-code-error')
+}
+```
+
+
+
+
+
+Create a new file at `app/routes/auth.confirm.tsx` and populate with the following:
+
+```ts app/routes/auth.confirm.tsx
+import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
+import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
+import { type EmailOtpType } from '@supabase/supabase-js'
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ const requestUrl = new URL(request.url)
+ const token_hash = requestUrl.searchParams.get('token_hash')
+ const type = requestUrl.searchParams.get('type') as EmailOtpType | null
+ const next = requestUrl.searchParams.get('next') || '/'
+ const headers = new Headers()
+
+ if (token_hash && type) {
+ const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
+ cookies: {
+ getAll() {
+ return parseCookieHeader(request.headers.get('Cookie') ?? '')
+ },
+ setAll(key, value, options) {
+ headers.append('Set-Cookie', serializeCookieHeader(key, value, options))
+ },
+ },
+ })
+
+ const { error } = await supabase.auth.verifyOtp({
+ type,
+ token_hash,
+ })
+
+ if (!error) {
+ return redirect(next, { headers })
+ }
+ }
+
+ // return the user to an error page with instructions
+ return redirect('/auth/auth-code-error', { headers })
+}
+```
+
+
+
+
+Create a new route in your express app and populate with the following:
+
+```js app.js
+// The client you created from the Server-Side Auth instructions
+const { createClient } = require("./lib/supabase")
+...
+app.get("/auth/confirm", async function (req, res) {
+ const token_hash = req.query.token_hash
+ const type = req.query.type
+ const next = req.query.next ?? "/"
+
+ if (token_hash && type) {
+ const supabase = createClient({ req, res })
+ const { error } = await supabase.auth.verifyOtp({
+ type,
+ token_hash,
+ })
+ if (!error) {
+ res.redirect(303, `/${next.slice(1)}`)
+ }
+ }
+
+ // return the user to an error page with some instructions
+ res.redirect(303, '/auth/auth-code-error')
+})
+```
+
+
+
+
+##### Step 3: Call the reset password by email function to initiate the flow
+
+
+
+
+```js
+async function signUpNewUser() {
+ const { data, error } = await supabase.auth.resetPasswordForEmail(email)
+}
+```
+
+
+
+
+```swift
+try await supabase.auth.resetPasswordForEmail("hello@example.com")
+```
+
+
+
+
+```kotlin
+supabase.gotrue.sendRecoveryEmail(
+ email = "hello@example.com",
+)
+```
+
+
+
+
+```python
+supabase.auth.reset_password_email('hello@example.com')
+```
+
+
+
Once you have a session, collect the user's new password and call `updateUser` to update their password.
@@ -603,6 +910,13 @@ supabase.auth.updateUser {
}
```
+
+
+
+```python
+supabase.auth.update_user({'password': 'new_password'})
+```
+
@@ -622,7 +936,7 @@ See the [Custom SMTP guide](/docs/guides/auth/auth-smtp) for instructions.
#### Local development with Inbucket
-You can test email flows on your local machine. The Supabase CLI automatically captures emails sent locally by using [Inbucket](https://github.com/inbucket/bucket).
+You can test email flows on your local machine. The Supabase CLI automatically captures emails sent locally by using [Inbucket](https://github.com/inbucket/inbucket).
In your terminal, run `supabase status` to get the Inbucket URL. Go to this URL in your browser, and follow the instructions to find your emails.
@@ -689,6 +1003,16 @@ supabase.auth.signUpWith(Phone) {
}
```
+
+
+
+```python
+supabase.auth.sign_up({
+ 'phone': "+13334445555",
+ 'password': "some-password"
+})
+```
+
@@ -755,6 +1079,19 @@ supabase.auth.verifyPhoneOtp(
)
```
+
+
+
+You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verify_otp`:
+
+```python
+supabase.auth.verify_otp({
+ 'phone': "+13334445555",
+ 'token': "123456",
+ 'type': "sms"
+})
+```
+
@@ -812,6 +1149,16 @@ supabase.auth.signInWith(Phone) {
}
```
+
+
+
+```python
+supabase.auth.sign_in_with_password({
+ 'phone': "+13334445555"
+ 'password': "some-password"
+})
+```
+
diff --git a/apps/docs/content/guides/auth/phone-login.mdx b/apps/docs/content/guides/auth/phone-login.mdx
index ae637af9aba..b42005f47c1 100644
--- a/apps/docs/content/guides/auth/phone-login.mdx
+++ b/apps/docs/content/guides/auth/phone-login.mdx
@@ -64,6 +64,15 @@ supabase.auth.signInWith(OTP) {
}
```
+
+
+
+```python
+response = supabase.auth.sign_in_with_otp({
+ 'phone': '+13334445555',
+})
+```
+
@@ -101,7 +110,7 @@ const {
data: { session },
error,
} = await supabase.auth.verifyOtp({
- phone: '+13334445555',
+ phone: '13334445555',
token: '123456',
type: 'sms',
})
@@ -133,6 +142,19 @@ supabase.auth.verifyPhoneOtp(
)
```
+
+
+
+You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verify_otp`:
+
+```python
+response = supabase.auth.verify_otp({
+ 'phone': '13334445555',
+ 'token': '123456',
+ 'type': 'sms',
+})
+```
+
@@ -193,7 +215,17 @@ try await supabase.auth.updateUser(
)
```
+
+
+
+```python
+response = supabase.auth.update_user({
+ 'phone': '123456789',
+})
+```
+
The user receives an SMS with a 6-digit pin that you must [verify](#verifying-a-phone-otp) within 60 seconds.
+Use the `phone_change` type when calling `verifyOTP` to update a user’s phone number.
diff --git a/apps/docs/content/guides/auth/quickstarts/nextjs.mdx b/apps/docs/content/guides/auth/quickstarts/nextjs.mdx
index 33bb93927d5..9a7117247af 100644
--- a/apps/docs/content/guides/auth/quickstarts/nextjs.mdx
+++ b/apps/docs/content/guides/auth/quickstarts/nextjs.mdx
@@ -35,6 +35,8 @@ hideToc: true
- [TypeScript](https://www.typescriptlang.org/)
- [Tailwind CSS](https://tailwindcss.com/)
+ [See GitHub repo](https://github.com/vercel/next.js/tree/canary/examples/with-supabase)
+
diff --git a/apps/docs/content/guides/auth/quickstarts/react.mdx b/apps/docs/content/guides/auth/quickstarts/react.mdx
index ca3e45a75bd..84cdf62d4bf 100644
--- a/apps/docs/content/guides/auth/quickstarts/react.mdx
+++ b/apps/docs/content/guides/auth/quickstarts/react.mdx
@@ -66,7 +66,7 @@ hideToc: true
- In `index.js`, create a Supabase client using your [Project URL and public API (anon) key](https://supabase.com/dashboard/project/_/settings/api).
+ In `App.js`, create a Supabase client using your [Project URL and public API (anon) key](https://supabase.com/dashboard/project/_/settings/api).
You can configure the Auth component to display whenever there is no session inside `supabase.auth.getSession()`
@@ -74,7 +74,7 @@ hideToc: true
- ```js src/index.js
+ ```js src/App.js
import './index.css'
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
diff --git a/apps/docs/content/guides/auth/redirect-urls.mdx b/apps/docs/content/guides/auth/redirect-urls.mdx
index dbdb1a94e5e..92b225af67e 100644
--- a/apps/docs/content/guides/auth/redirect-urls.mdx
+++ b/apps/docs/content/guides/auth/redirect-urls.mdx
@@ -53,7 +53,7 @@ For deployments with Netlify, set the `SITE_URL` to your official site URL. Add
For deployments with Vercel, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
- `http://localhost:3000/**`
-- `https://*-username.vercel.app/**`
+- `https://*-.vercel.app/**`
Vercel provides an environment variable for the URL of the deployment called `NEXT_PUBLIC_VERCEL_URL`. See the [Vercel docs](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) for more details. You can use this variable to dynamically redirect depending on the environment. You should also set the value of the environment variable called NEXT_PUBLIC_SITE_URL, this should be set to your site URL in production environment to ensure that redirects function correctly.
diff --git a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
index b213d2cae37..b9e5d752c51 100644
--- a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
+++ b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
@@ -1,25 +1,53 @@
---
title: 'Creating a Supabase client for SSR'
-description: 'Configure Supabase client to use Cookies'
-sidebar_label: 'Creating a client'
+subtitle: 'Configure your Supabase client to use cookies'
---
-### Install Supabase packages
+To use Server-Side Rendering (SSR) with Supabase, you need to configure your Supabase client to use cookies. The `@supabase/ssr` package helps you do this for JavaScript/TypeScript applications.
-The `ssr` package configures Supabase to use Cookies, which is required for server-side languages and frameworks.
+## Install
+
+Install the `@supabase/ssr` and `@supabase/supabase-js` packages:
+
+
+
+
```bash
npm install @supabase/ssr @supabase/supabase-js
```
-### Set environment variables
+
+
+
+
+```bash
+yarn add @supabase/ssr @supabase/supabase-js
+```
+
+
+
+
+
+```bash
+pnpm add @supabase/ssr @supabase/supabase-js
+```
+
+
+
+
+
+## Set environment variables
+
+In your environment variables file, set your Supabase URL and Supabase Anon Key:
+
+
+
-Create a `.env.local` file in your project root directory. You can get your `SUPABASE_URL` and `SUPABASE_ANON_KEY` from inside [your Supabase project's dashboard](https://supabase.com/dashboard/project/_/settings/api).
-
```bash .env.local
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
@@ -28,8 +56,6 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
-Create a `.env.local` file in your project root directory. You can get your `SUPABASE_URL` and `SUPABASE_ANON_KEY` from inside [your Supabase project's dashboard](https://supabase.com/dashboard/project/_/settings/api).
-
```bash .env.local
PUBLIC_SUPABASE_URL=your_supabase_project_url
PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
@@ -38,8 +64,6 @@ PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
-Create a `.env` file in your project root directory. You can get your `SUPABASE_URL` and `SUPABASE_ANON_KEY` from inside [your Supabase project's dashboard](https://supabase.com/dashboard/project/_/settings/api).
-
```bash .env
PUBLIC_SUPABASE_URL=your_supabase_project_url
PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
@@ -48,8 +72,6 @@ PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
-Create a `.env` file in your project root directory. You can get your `SUPABASE_URL` and `SUPABASE_ANON_KEY` from inside [your Supabase project's dashboard](https://supabase.com/dashboard/project/_/settings/api).
-
```bash .env
SUPABASE_URL=your_supabase_project_url
SUPABASE_ANON_KEY=your_supabase_anon_key
@@ -58,32 +80,55 @@ SUPABASE_ANON_KEY=your_supabase_anon_key
-Create a `.env` file in your project root directory. You can get your `SUPABASE_URL` and `SUPABASE_ANON_KEY` from inside [your Supabase project's dashboard](https://supabase.com/dashboard/project/_/settings/api).
-
```bash .env
SUPABASE_URL=your_supabase_project_url
SUPABASE_ANON_KEY=your_supabase_anon_key
```
-Install [dotenv](https://www.npmjs.com/package/dotenv)
+Install [dotenv](https://www.npmjs.com/package/dotenv):
```bash
npm i dotenv
```
-and initialize it
+And initialize it:
-```js app.js
-const express = require("express")
-const dotenv = require("dotenv")
-dotenv.config()
-...
+
+
+
+
+```bash
+npm install dotenv
```
+
+
+
+
+```bash
+yarn add dotenv
+```
+
+
+
+
+
+```bash
+pnpm add dotenv
+```
+
+
+
+
+
-### Creating a client
+## Create a client
+
+You'll need some one-time setup code to configure your Supabase client to use cookies. Once your utility code is set up, you can use your new `createClient` utility functions to get a properly configured Supabase client.
+
+Use the browser client in code that runs on the browser, and the server client in code that runs on the server.
-Creating a Supabase client with the `ssr` package automatically configures it to use cookies. This means your user's session is available throughout the entire Next.js stack - client, server, App Router, Pages Router. It just works!
-
-
-
-For the client utility functions, it is recommended to create them in a separate `/utils/supabase` folder. That way, you can import them as `client` into any component or page that needs access to Supabase. For further information, you can look at the [Next.js Auth Guide](/docs/guides/auth/server-side/nextjs).
-
-
+The following code samples are for App Router. For help with Pages Router, see the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/nextjs?queryGroups=router&router=pages).
-
+
-```ts page.tsx
-"use client";
+```ts utils/supabase/client.tsx
+'use client'
import { createBrowserClient } from '@supabase/ssr'
-export default function Page () {
- const supabase = createBrowserClient(
+export function createClient() {
+ return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
-
- return ...
}
```
-
+
-
-
-Server Components only have read access to cookies. Check the `Middleware` tab for an example of refreshing expired sessions before loading a Server Component route.
-
-
-
-```ts page.tsx
-import { createServerClient, type CookieOptions } from '@supabase/ssr'
+```ts utils/supabase/server.tsx
+import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
-export default async function Page () {
+export function createClient() {
const cookieStore = cookies()
- const supabase = createServerClient(
+ return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
- get(name: string) {
- return cookieStore.get(name)?.value
+ getAll() {
+ return cookieStore.getAll()
+ },
+ setAll(cookiesToSet) {
+ try {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ cookieStore.set(name, value, options)
+ )
+ } catch {
+ // The `setAll` method was called from a Server Component.
+ // This can be ignored if you have middleware refreshing
+ // user sessions.
+ }
},
},
}
)
-
- return ...
}
```
@@ -163,72 +203,14 @@ export default async function Page () {
-
-
-Server Components only have read access to cookies. This `Middleware` example can be used to refresh expired sessions before loading Server Component routes.
-
-
+In Next.js, because Server Components cannot set cookies, you'll also need a middleware client to handle cookie refreshes. The middleware should run before every route that needs access to Supabase, or that is protected by Supabase Auth.
```ts middleware.ts
-import { createServerClient, type CookieOptions } from '@supabase/ssr'
-import { NextResponse, type NextRequest } from 'next/server'
+import { type NextRequest } from 'next/server'
+import { updateSession } from '@/utils/supabase/middleware'
export async function middleware(request: NextRequest) {
- let response = NextResponse.next({
- request: {
- headers: request.headers,
- },
- })
-
- const supabase = createServerClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
- {
- cookies: {
- get(name: string) {
- return request.cookies.get(name)?.value
- },
- set(name: string, value: string, options: CookieOptions) {
- request.cookies.set({
- name,
- value,
- ...options,
- })
- response = NextResponse.next({
- request: {
- headers: request.headers,
- },
- })
- response.cookies.set({
- name,
- value,
- ...options,
- })
- },
- remove(name: string, options: CookieOptions) {
- request.cookies.set({
- name,
- value: '',
- ...options,
- })
- response = NextResponse.next({
- request: {
- headers: request.headers,
- },
- })
- response.cookies.set({
- name,
- value: '',
- ...options,
- })
- },
- },
- }
- )
-
- await supabase.auth.getUser()
-
- return response
+ return await updateSession(request)
}
export const config = {
@@ -245,179 +227,69 @@ export const config = {
}
```
-
+```ts utils/supabase/middleware.ts
+import { createServerClient } from '@supabase/ssr'
+import { NextResponse, type NextRequest } from 'next/server'
-
-
-```ts route.ts
-import { createServerClient, type CookieOptions } from '@supabase/ssr'
-import { cookies } from 'next/headers'
-
-export async function POST(request: Request) {
- const cookieStore = cookies()
+export async function updateSession(request: NextRequest) {
+ let supabaseResponse = NextResponse.next({
+ request,
+ })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
- get(name: string) {
- return cookieStore.get(name)?.value
+ getAll() {
+ return request.cookies.getAll()
},
- set(name: string, value: string, options: CookieOptions) {
- cookieStore.set({ name, value, ...options })
- },
- remove(name: string, options: CookieOptions) {
- cookieStore.set({ name, value: '', ...options })
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
+ supabaseResponse = NextResponse.next({
+ request,
+ })
+ cookiesToSet.forEach(({ name, value, options }) =>
+ supabaseResponse.cookies.set(name, value, options)
+ )
},
},
}
)
- return ...
-}
-```
+ // IMPORTANT: Avoid writing any logic between createServerClient and
+ // supabase.auth.getUser(). A simple mistake could make it very hard to debug
+ // issues with users being randomly logged out.
-
+ const {
+ data: { user },
+ } = await supabase.auth.getUser()
-
-
-```ts page.tsx
-import { createServerClient, type CookieOptions } from '@supabase/ssr'
-import { cookies } from 'next/headers'
-
-export default async function Page () {
- const serverAction = async () => {
- 'use server'
-
- const cookieStore = cookies()
-
- const supabase = createServerClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
- {
- cookies: {
- get(name: string) {
- return cookieStore.get(name)?.value
- },
- set(name: string, value: string, options: CookieOptions) {
- cookieStore.set({ name, value, ...options })
- },
- remove(name: string, options: CookieOptions) {
- cookieStore.set({ name, value: '', ...options })
- },
- },
- }
- )
+ if (
+ !user &&
+ !request.nextUrl.pathname.startsWith('/login') &&
+ !request.nextUrl.pathname.startsWith('/auth')
+ ) {
+ // no user, potentially respond by redirecting the user to the login page
+ const url = request.nextUrl.clone()
+ url.pathname = '/login'
+ return NextResponse.redirect(url)
}
- return ...
-}
-```
+ // IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
+ // creating a new response object with NextResponse.next() make sure to:
+ // 1. Pass the request in it, like so:
+ // const myNewResponse = NextResponse.next({ request })
+ // 2. Copy over the cookies, like so:
+ // myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
+ // 3. Change the myNewResponse object to fit your needs, but avoid changing
+ // the cookies!
+ // 4. Finally:
+ // return myNewResponse
+ // If this is not done, you may be causing the browser and server to go out
+ // of sync and terminate the user's session prematurely!
-
-
-
-
-```ts index.tsx
-import { type GetServerSidePropsContext } from 'next'
-import { createServerClient, type CookieOptions, serialize } from '@supabase/ssr'
-
-export const getServerSideProps = async (context: GetServerSidePropsContext) => {
- const supabase = createServerClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
- {
- cookies: {
- get(name: string) {
- return context.req.cookies[name];
- },
- set(name: string, value: string, options: CookieOptions) {
- context.res.appendHeader(
- "Set-Cookie",
- serialize(name, value, options)
- );
- },
- remove(name: string, options: CookieOptions) {
- context.res.appendHeader("Set-Cookie", serialize(name, "", options));
- },
- },
- }
- )
-
- return {...}
-}
-```
-
-
-
-
-
-The `getStaticProps` function gets executed at build time, rather than per request. At build time,
-there is no user, session or cookies. Therefore, we can use the standard `createClient` function
-from `supabase-js`.
-
-```ts page.tsx
-import { createClient } from '@supabase/supabase-js'
-
-export const getStaticProps = async () => {
- const supabase = createClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
- )
-
- return {...}
-}
-```
-
-
-
-
-
-```ts index.tsx
-import { createServerClient, type CookieOptions, serialize } from "@supabase/ssr"
-import { type NextApiRequest, type NextApiResponse } from "next"
-
-export default async function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
- const supabase = createServerClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
- {
- cookies: {
- get(name: string) {
- return req.cookies[name];
- },
- set(name: string, value: string, options: CookieOptions) {
- res.setHeader("Set-Cookie", serialize(name, value, options));
- },
- remove(name: string, options: CookieOptions) {
- res.setHeader("Set-Cookie", serialize(name, "", options));
- },
- },
- }
- )
-
- res.send(...)
-}
-```
-
-
-
-
-
-```ts index.tsx
-import { createBrowserClient } from "@supabase/ssr"
-
-export default function Index () {
- const supabase = createBrowserClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
- )
-
- return ...
+ return supabaseResponse
}
```
@@ -427,8 +299,6 @@ export default function Index () {
-Creating a Supabase client with the `ssr` package automatically configures it to use Cookies. This means your user's session is available throughout the entire SvelteKit stack - page, layout, server, hooks.
-
-
-
-API routes, server layout load and form actions can now access the supabase client from the `event` object due to this hook.
-
-
-
```ts hooks.server.ts
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createServerClient } from '@supabase/ssr'
@@ -452,18 +316,19 @@ import type { Handle } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
cookies: {
- get: (key) => event.cookies.get(key),
- /**
- * Note: You have to add the `path` variable to the
- * set and remove method due to sveltekit's cookie API
- * requiring this to be set, setting the path to an empty string
- * will replicate previous/standard behaviour (https://kit.svelte.dev/docs/types#public-types-cookies)
- */
- set: (key, value, options) => {
- event.cookies.set(key, value, { ...options, path: '/' })
+ getAll() {
+ return event.cookies.getAll()
},
- remove: (key, options) => {
- event.cookies.delete(key, { ...options, path: '/' })
+ setAll(cookiesToSet) {
+ /**
+ * Note: You have to add the `path` variable to the
+ * set and remove method due to sveltekit's cookie API
+ * requiring this to be set, setting the path to an empty string
+ * will replicate previous/standard behavior (https://kit.svelte.dev/docs/types#public-types-cookies)
+ */
+ cookiesToSet.forEach(({ name, value, options }) =>
+ event.cookies.set(name, value, { ...options, path: '/' })
+ )
},
},
})
@@ -505,16 +370,12 @@ export const handle: Handle = async ({ event, resolve }) => {
-
-
Page components can get access to the supabase client from the `data` object due to this load function.
-
-
```ts +layout.ts
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import type { LayoutLoad } from './$types'
-import { createBrowserClient, createServerClient, isBrowser, parse } from '@supabase/ssr'
+import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr'
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends('supabase:auth')
@@ -524,20 +385,14 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
global: {
fetch,
},
- cookies: {
- get(key) {
- const cookie = parse(document.cookie)
- return cookie[key]
- },
- },
})
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch,
},
cookies: {
- get() {
- return JSON.stringify(data.session)
+ getAll() {
+ return data.cookies
},
},
})
@@ -562,64 +417,19 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
```ts +layout.server.ts
import type { LayoutServerLoad } from './$types'
-export const load: LayoutServerLoad = async ({ locals: { safeGetSession } }) => {
+export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => {
const { session, user } = await safeGetSession()
return {
session,
user,
+ cookies: cookies.getAll(),
}
}
```
-
-
-```svelte +page.svelte
-
-
-....
-```
-
-
-
-
-
-```ts +server.ts
-import { redirect } from '@sveltejs/kit';
-
-export const GET = async (event) => {
- const { url, locals: { supabase } } = event
- ...
-}
-```
-
-
-
-
-
-```ts +page.server.ts
-import type { Actions } from './$types'
-
-export const actions: Actions = {
- default: async (event) => {
- const { request, url, locals: { supabase } } = event
- const formData = await request.formData()
- const email = formData.get('email') as string
- const password = formData.get('password') as string
- ...
- }
-}
-```
-
-
@@ -646,21 +456,19 @@ export default defineConfig({
```ts index.astro
---
-import { createServerClient, type CookieOptions } from "@supabase/ssr";
+import { createServerClient, parseCookieHeader } from "@supabase/ssr";
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
- get(key: string) {
- return Astro.cookies.get(key)?.value;
+ getAll() {
+ return parseCookieHeader(Astro.request.headers.get('Cookie') ?? '')
},
- set(key: string, value: string, options: CookieOptions) {
- Astro.cookies.set(key, value, options);
- },
- remove(key: string, options) {
- Astro.cookies.delete(key, options);
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ Astro.cookies.set(name, value, options))
},
},
}
@@ -674,6 +482,8 @@ const supabase = createServerClient(
```html index.astro
User Management
-
+
+
```
-Create a `src/routes/+page.server.ts` file that will return our website URL to be used in our `redirectTo` above.
+Create a `src/routes/+page.server.ts` file that will handle our magic link form when submitted.
-```ts
+```ts src/routes/+page.server.ts
// src/routes/+page.server.ts
-import { redirect } from '@sveltejs/kit'
-import type { PageServerLoad } from './$types'
+import { fail, redirect } from '@sveltejs/kit'
+import type { Actions, PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ url, locals: { safeGetSession } }) => {
const { session } = await safeGetSession()
@@ -295,62 +316,109 @@ export const load: PageServerLoad = async ({ url, locals: { safeGetSession } })
return { url: url.origin }
}
+
+export const actions: Actions = {
+ default: async (event) => {
+ const {
+ url,
+ request,
+ locals: { supabase },
+ } = event
+ const formData = await request.formData()
+ const email = formData.get('email') as string
+ const validEmail = /^[\w-\.+]+@([\w-]+\.)+[\w-]{2,8}$/.test(email)
+
+ if (!validEmail) {
+ return fail(400, { errors: { email: 'Please enter a valid email address' }, email })
+ }
+
+ const { error } = await supabase.auth.signInWithOtp({ email })
+
+ if (error) {
+ return fail(400, {
+ success: false,
+ email,
+ message: `There was an issue, Please contact support.`,
+ })
+ }
+
+ return {
+ success: true,
+ message: 'Please check your email for a magic link to log into the website.',
+ }
+ },
+}
```
-### Proof Key for Code Exchange (PKCE)
+### Email template
-As we are employing Proof Key for Code Exchange (PKCE) in our authentication flow, it is necessary to create a server endpoint responsible for exchanging the code for a session.
+Change the email template to support a server-side authentication flow.
+
+Before we proceed, let's change the email template to support sending a token hash:
+
+- Go to the [Auth templates](/dashboard/project/_/auth/templates) page in your dashboard.
+- Select `Confirm signup` template.
+- Change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email`.
+- Repeat the previous step for `Magic link` template.
+
+
+
+Did you know? You could also customize emails sent out to new users, including the email's looks, content, and query parameters. Check out the [settings of your project](/dashboard/project/_/auth/templates).
+
+
+
+### Confirmation endpoint
+
+As we are working in a server-side rendering (SSR) environment, it is necessary to create a server endpoint responsible for exchanging the `token_hash` for a session.
In the following code snippet, we perform the following steps:
-- Retrieve the code sent back from the Supabase Auth server using the `code` query parameter.
-- Exchange this code for a session, which we store in our chosen storage mechanism (in this case, cookies).
-- Finally, we redirect the user to the `account` page.
+- Retrieve the `token_hash` sent back from the Supabase Auth server using the `token_hash` query parameter.
+- Exchange this `token_hash` for a session, which we store in storage (in this case, cookies).
+- Finally, the user is redirected to the `account` page or the `error` page.
-
-
-
-```js title=src/routes/auth/callback/+server.js
-// src/routes/auth/callback/+server.js
+```ts src/routes/auth/confirm/+server.ts
+// src/routes/auth/confirm/+server.ts
+import type { EmailOtpType } from '@supabase/supabase-js'
import { redirect } from '@sveltejs/kit'
-export const GET = async ({ url, locals: { supabase } }) => {
- const code = url.searchParams.get('code')
-
- if (code) {
- await supabase.auth.exchangeCodeForSession(code)
- }
-
- redirect(303, '/account')
-}
-```
-
-
-
-
-```ts title=src/routes/auth/callback/+server.ts
-// src/routes/auth/callback/+server.ts
-import { redirect, type RequestHandler } from '@sveltejs/kit'
+import type { RequestHandler } from './$types'
export const GET: RequestHandler = async ({ url, locals: { supabase } }) => {
- const code = url.searchParams.get('code')
+ const token_hash = url.searchParams.get('token_hash')
+ const type = url.searchParams.get('type') as EmailOtpType | null
+ const next = url.searchParams.get('next') ?? '/account'
- if (code) {
- await supabase.auth.exchangeCodeForSession(code)
+ /**
+ * Clean up the redirect URL by deleting the Auth flow parameters.
+ *
+ * `next` is preserved for now, because it's needed in the error case.
+ */
+ const redirectTo = new URL(url)
+ redirectTo.pathname = next
+ redirectTo.searchParams.delete('token_hash')
+ redirectTo.searchParams.delete('type')
+
+ if (token_hash && type) {
+ const { error } = await supabase.auth.verifyOtp({ type, token_hash })
+ if (!error) {
+ redirectTo.searchParams.delete('next')
+ redirect(303, redirectTo)
+ }
}
- redirect(303, '/account')
+ redirectTo.pathname = '/auth/error'
+ redirect(303, redirectTo)
}
```
-
-
+### Authentication error page
+
+If there is an error with confirming the token you will be redirect to this error page.
+
+```svelte src/routes/auth/error/+page.svelte
+
Login error
+```
### Account page
diff --git a/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx b/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx
index e26045321ce..33e597d1210 100644
--- a/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx
+++ b/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx
@@ -128,6 +128,8 @@ After a user is signed in, you can allow them to edit their profile details and
Create a new view for that called `ProfileView.swift`.
```swift ProfileView.swift
+import SwiftUI
+
struct ProfileView: View {
@State var username = ""
@State var fullName = ""
@@ -180,7 +182,7 @@ struct ProfileView: View {
do {
let currentUser = try await supabase.auth.session.user
- let profile: Profile = try await supabase.database
+ let profile: Profile = try await supabase
.from("profiles")
.select()
.eq("id", value: currentUser.id)
@@ -204,7 +206,7 @@ struct ProfileView: View {
do {
let currentUser = try await supabase.auth.session.user
- try await supabase.database
+ try await supabase
.from("profiles")
.update(
UpdateProfileParams(
@@ -260,6 +262,8 @@ Now that you've created all the views, add an entry point for the application. T
Add a new `AppView.swift` file.
```swift AppView.swift
+import SwiftUI
+
struct AppView: View {
@State var isAuthenticated = false
@@ -272,7 +276,7 @@ struct AppView: View {
}
}
.task {
- for await state in await supabase.auth.authStateChanges {
+ for await state in supabase.auth.authStateChanges {
if [.initialSession, .signedIn, .signedOut].contains(state.event) {
isAuthenticated = state.session != nil
}
diff --git a/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx b/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx
index 27778b2a6b2..09f6924a8c8 100644
--- a/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx
+++ b/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx
@@ -324,7 +324,7 @@ const uploadAvatar = async (evt) => {
}
}
-watch(path, () => {
+watchEffect(() => {
if (path.value) downloadImage()
})
@@ -365,6 +365,9 @@ And then we can add the widget to the Account page in `src/components/Account.vu
diff --git a/apps/docs/content/guides/integrations.mdx b/apps/docs/content/guides/integrations.mdx
new file mode 100644
index 00000000000..579b0fdcd36
--- /dev/null
+++ b/apps/docs/content/guides/integrations.mdx
@@ -0,0 +1,13 @@
+---
+title: Integrations
+---
+
+Supabase integrates with many of your favorite third-party services.
+
+## Vercel Marketplace
+
+Create and manage your Supabase projects directly through Vercel. [Get started with Vercel](/docs/guides/integrations/vercel-marketplace).
+
+## Supabase Marketplace
+
+Browse tools for extending your Supabase project. [Browse the Supabase Marketplace](/partners/integrations).
diff --git a/apps/docs/content/guides/platform/oauth-apps/build-a-supabase-integration.mdx b/apps/docs/content/guides/integrations/build-a-supabase-integration.mdx
similarity index 91%
rename from apps/docs/content/guides/platform/oauth-apps/build-a-supabase-integration.mdx
rename to apps/docs/content/guides/integrations/build-a-supabase-integration.mdx
index 99ed57356c7..6a09e7dbd5b 100644
--- a/apps/docs/content/guides/platform/oauth-apps/build-a-supabase-integration.mdx
+++ b/apps/docs/content/guides/integrations/build-a-supabase-integration.mdx
@@ -65,6 +65,12 @@ Exchange the authorization code for an access and refresh token by calling [`POS
- `redirect_uri`: This must be exactly the same URL used in the first step.
- (Recommended) `code_verifier`: If you used the PKCE flow in the first step, include the code verifier as `code_verifier`.
+
+
+If your application need to support dynamically generated Redirect URLs, check out [Handling Dynamic Redirect URLs](#handling-dynamic-redirect-urls) section below.
+
+
+
As per OAuth2 spec, provide the client id and client secret as basic auth header:
- `client_id`: The unique client ID identifying your OAuth App.
@@ -141,9 +147,9 @@ postgresql://postgres:[DB-PASSWORD]@db.[REF].supabase.co:5432/postgres
Note that you cannot retrieve the database password via the management API, so for the user's existing projects you will need to collect their database password in your UI.
-### Create new organizations and projects
+### Create new projects
-If you don't need access to the user's existing project, we recommend creating a new project under a new organization. Use the [`/v1/organizations` endpoint](https://api.supabase.com/api/v1#/organizations/createOrganization) to create a new organization named after your integration, afterward use the [`/v1/projects` endpoint](https://api.supabase.com/api/v1#/projects/createProject) to create a new project.
+Use the [`/v1/projects` endpoint](https://api.supabase.com/api/v1#/projects/createProject) to create a new project.
When creating a new project, you can either ask the user to provide a database password, or you can generate a secure password for them. In any case, make sure to securely store the database password on your end which will allow you to construct the Postgres URI.
@@ -151,6 +157,11 @@ When creating a new project, you can either ask the user to provide a database p
You can configure the user's [custom SMTP settings](https://supabase.com/docs/guides/auth/auth-smtp) using the [`/config/auth` endpoint](https://api.supabase.com/api/v1#/projects%20config/updateV1AuthConfig).
+### Handling Dynamic Redirect URLs
+
+To handle multiple, dynamically generated redirect URLs within the same OAuth app, you can leverage the `state` query parameter. When starting the OAuth process, include the desired, encoded redirect URL in the `state` parameter.
+Once authorization is complete, we will sends the `state` value back to your app. You can then verify its integrity and extract the correct redirect URL, decoding it and redirecting the user to the correct URL.
+
## Current limitations
Only some features are available until we roll out fine-grained access control. If you need full database access, you will need to prompt the user for their database password.
diff --git a/apps/docs/content/guides/platform/oauth-apps/oauth-scopes.mdx b/apps/docs/content/guides/integrations/build-a-supabase-integration/oauth-scopes.mdx
similarity index 100%
rename from apps/docs/content/guides/platform/oauth-apps/oauth-scopes.mdx
rename to apps/docs/content/guides/integrations/build-a-supabase-integration/oauth-scopes.mdx
diff --git a/apps/docs/content/guides/platform/marketplace.mdx b/apps/docs/content/guides/integrations/supabase-marketplace.mdx
similarity index 100%
rename from apps/docs/content/guides/platform/marketplace.mdx
rename to apps/docs/content/guides/integrations/supabase-marketplace.mdx
diff --git a/apps/docs/content/guides/integrations/vercel-marketplace.mdx b/apps/docs/content/guides/integrations/vercel-marketplace.mdx
new file mode 100644
index 00000000000..be0ef42121c
--- /dev/null
+++ b/apps/docs/content/guides/integrations/vercel-marketplace.mdx
@@ -0,0 +1,100 @@
+---
+id: 'vercel-marketplace'
+title: 'Vercel Marketplace'
+description: 'Manage your Supabase projects directly through Vercel'
+---
+
+## Overview
+
+The Vercel Marketplace is a feature that allows you to manage third-party resources, such as Supabase, directly from the Vercel platform. This integration offers a seamless experience with unified billing, streamlined authentication, and easy access management for your team.
+
+When you create an organization and projects through Vercel Marketplace, they function just like those created directly within Supabase. However, the billing is handled through your Vercel account, and you can manage your resources directly from the Vercel dashboard or CLI. Additionally, environment variables are automatically synchronized, making them immediately available for your connected projects.
+
+For more information, see [Introducing the Vercel Marketplace](https://vercel.com/blog/share/introducing-the-vercel-marketplace) blog post.
+
+
+
+Vercel Marketplace is currently in Public Alpha. If you encounter any issues or have feature requests, [contact support](/dashboard/support/new).
+
+
+
+## Quickstart
+
+### Via Template
+
+
+
Deploy a Next.js app with Supabase Vercel Storage now
+
+### Via Vercel Marketplace
+
+Details coming soon..
+
+### Connecting to Supabase Project
+
+Supabase Projects created via Vercel Marketplace are automatically synchronized with connected Vercel projects. This synchronization includes setting essential environment variables, such as:
+
+```
+POSTGRES_URL
+POSTGRES_PRISMA_URL
+POSTGRES_URL_NON_POOLING
+POSTGRES_USER
+POSTGRES_HOST
+POSTGRES_PASSWORD
+POSTGRES_DATABASE
+SUPABASE_SERVICE_ROLE_KEY
+SUPABASE_ANON_KEY
+SUPABASE_URL
+SUPABASE_JWT_SECRET
+NEXT_PUBLIC_SUPABASE_ANON_KEY
+NEXT_PUBLIC_SUPABASE_URL
+```
+
+These variables ensure your applications can connect securely to the database and interact with Supabase APIs.
+
+## Studio support
+
+Accessing Supabase Studio is simple through the Vercel dashboard. You can open Supabase Studio from either the Integration installation page or the Vercel Storage page.
+Depending on your entry point, you'll either land on the Supabase dashboard homepage or be redirected to the corresponding Supabase Project.
+
+Supabase Studio provides tools such as:
+
+- **SQL Editor:** Run SQL queries against your database.
+- **Table Editor:** Create, edit, and delete tables and columns.
+- **Log Explorer:** Inspect real-time logs for your database.
+- **Postgres Upgrades:** Upgrade your Postgres instance to the latest version.
+- **Compute Upgrades:** Scale the compute resources allocated to your database.
+
+## Permissions
+
+There is a direct one-to-one relationship between a Supabase Organization and a Vercel team. Installing the integration or launching your first Supabase Project through Vercel triggers the creation of a corresponding Supabase Organization if one doesn’t already exist.
+
+When Vercel users interact with Supabase, they are automatically assigned Supabase accounts. New users get a Supabase account linked to their primary email, while existing users have their Vercel and Supabase accounts linked.
+
+- The user who initiates the creation of a Vercel Storage database is assigned the `owner` role in the new Supabase organization.
+- Subsequent users are assigned roles based on their Vercel role, such as `developer` for `member` and `owner` for `owner`.
+
+Role management is handled directly in the Vercel dashboard, and changes are synchronized with Supabase.
+
+Note: you can invite non-Vercel users to your Supabase Organization, but their permissions won't be synchronized with Vercel.
+
+## Pricing
+
+Pricing for databases created through Vercel Marketplace is identical to those created directly within Supabase. Detailed pricing information is available on the [Supabase pricing page](https://supabase.com/pricing).
+
+The [usage page](https://supabase.com/dashboard/org/_/usage) tracks the usage of your Vercel databases, with this information sent to Vercel for billing, which appears on your Vercel invoice.
+
+Note: Supabase Organization billing cycle is separate from Vercel's. Plan changes will reset the billing cycle to the day of the change, with the initial billing cycle starting the day you install the integration.
+
+## Limitations
+
+When using Vercel Marketplace, the following limitations apply:
+
+- Projects can only be created or removed via the Vercel dashboard.
+- Organizations cannot be removed manually; they are removed only if you uninstall the Vercel Marketplace Integration.
+- Owners cannot be added manually within the Supabase dashboard.
+- Invoices and payments must be managed through the Vercel dashboard, not the Supabase dashboard.
diff --git a/apps/docs/content/guides/local-development.mdx b/apps/docs/content/guides/local-development.mdx
new file mode 100644
index 00000000000..06ac6119d8e
--- /dev/null
+++ b/apps/docs/content/guides/local-development.mdx
@@ -0,0 +1,126 @@
+---
+title: Local Development & CLI
+subtitle: Learn how to develop locally and use the Supabase CLI
+---
+
+Develop locally while running the Supabase stack on your machine.
+
+## Quickstart
+
+1. Install the Supabase CLI:
+
+
+
+ ```sh
+ npm install supabase --save-dev
+ ```
+
+
+
+ ```sh
+ yarn add supabase --dev
+ ```
+
+
+
+ ```sh
+ pnpm add supabase --save-dev
+ ```
+
+
+
+ ```sh
+ brew install supabase/tap/supabase
+ ```
+
+
+
+2. In your repo, initialize the Supabase project:
+
+
+
+ ```sh
+ npx supabase init
+ ```
+
+
+
+ ```sh
+ yarn supabase init
+ ```
+
+
+
+ ```sh
+ pnpm supabase init
+ ```
+
+
+
+ ```sh
+ supabase init
+ ```
+
+
+
+
+
+3. Start the Supabase stack:
+
+
+
+ ```sh
+ npx supabase start
+ ```
+
+
+
+ ```sh
+ yarn supabase start
+ ```
+
+
+
+ ```sh
+ pnpm supabase start
+ ```
+
+
+
+ ```sh
+ supabase start
+ ```
+
+
+
+
+
+4. View your local Supabase instance at [http://localhost:54323](http://localhost:54323).
+
+## Local Development
+
+Local development with Supabase allows you to work on your projects in a self-contained environment on your local machine. Working locally has several advantages:
+
+1. Faster development: You can make changes and see results instantly without waiting for remote deployments.
+2. Offline work: You can continue development even without an internet connection.
+3. Cost-effective: Local development is free and doesn't consume your project's quota.
+4. Enhanced privacy: Sensitive data remains on your local machine during development.
+5. Easy testing: You can experiment with different configurations and features without affecting your production environment.
+
+To get started with local development, you'll need to install the [Supabase CLI](#cli) and Docker. The Supabase CLI allows you to start and manage your local Supabase stack, while Docker is used to run the necessary services.
+
+Once set up, you can initialize a new Supabase project, start the local stack, and begin developing your application using local Supabase services. This includes access to a local PostgreSQL database, Auth, Storage, and other Supabase features.
+
+## CLI
+
+The Supabase CLI is a powerful tool that enables developers to manage their Supabase projects directly from the terminal. It provides a suite of commands for various tasks, including:
+
+- Setting up and managing local development environments
+- Generating TypeScript types for your database schema
+- Handling database migrations
+- Managing environment variables and secrets
+- Deploying your project to the Supabase platform
+
+With the CLI, you can streamline your development workflow, automate repetitive tasks, and maintain consistency across different environments. It's an essential tool for both local development and CI/CD pipelines.
+
+See the [CLI Getting Started guide](/docs/guides/local-development/cli/getting-started) for more information.
diff --git a/apps/docs/content/guides/local-development/cli/getting-started.mdx b/apps/docs/content/guides/local-development/cli/getting-started.mdx
new file mode 100644
index 00000000000..6319a53ddf5
--- /dev/null
+++ b/apps/docs/content/guides/local-development/cli/getting-started.mdx
@@ -0,0 +1,314 @@
+---
+title: 'Supabase CLI'
+description: 'The Supabase CLI provides tools to develop your project locally, deploy to the Supabase Platform, and set up CI/CD workflows.'
+subtitle: 'Develop locally, deploy to the Supabase Platform, and set up CI/CD workflows'
+---
+
+The Supabase CLI enables you to run the entire Supabase stack locally, on your machine or in a CI environment. With just two commands, you can set up and start a new local project:
+
+1. `supabase init` to create a new local project
+2. `supabase start` to launch the Supabase services
+
+## Installing the Supabase CLI
+
+
+
+
+Install the CLI with [Homebrew](https://brew.sh):
+
+```sh
+brew install supabase/tap/supabase
+```
+
+
+
+
+Install the CLI with [Scoop](https://scoop.sh):
+
+```powershell
+scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
+scoop install supabase
+```
+
+
+
+
+The CLI is available through [Homebrew](https://brew.sh) and Linux packages.
+
+#### Homebrew
+
+```sh
+brew install supabase/tap/supabase
+```
+
+#### Linux packages
+
+Linux packages are provided in [Releases](https://github.com/supabase/cli/releases).
+To install, download the `.apk`/`.deb`/`.rpm` file depending on your package manager
+and run one of the following:
+
+- `sudo apk add --allow-untrusted <...>.apk`
+- `sudo dpkg -i <...>.deb`
+- `sudo rpm -i <...>.rpm`
+
+
+
+
+Run the CLI by prefixing each command with `npx`:
+
+```sh
+npx supabase
+```
+
+Or with `bunx`:
+
+```sh
+bunx supabase
+```
+
+You can also install the CLI as dev dependency via [npm](https://www.npmjs.com/package/supabase):
+
+```sh
+npm install supabase --save-dev
+```
+
+
+
+
+## Updating the Supabase CLI
+
+When a new [version](https://github.com/supabase/cli/releases) is released, you can update the CLI using the same methods.
+
+
+
+
+```sh
+brew upgrade supabase
+```
+
+
+
+
+```powershell
+scoop update supabase
+```
+
+
+
+
+```sh
+brew upgrade supabase
+```
+
+
+
+
+If you have installed the CLI as dev dependency via [npm](https://www.npmjs.com/package/supabase), you can update it with:
+
+```sh
+npm update supabase --save-dev
+```
+
+
+
+
+If you have any Supabase containers running locally, stop them and delete their data volumes before proceeding with the upgrade. This ensures that Supabase managed services can apply new migrations on a clean state of the local database.
+
+
+
+Remember to save any local schema and data changes before stopping because the `--no-backup` flag will delete them.
+
+```sh
+supabase db diff my_schema
+supabase db dump --local --data-only > supabase/seed.sql
+supabase stop --no-backup
+```
+
+
+
+## Running Supabase locally
+
+The Supabase CLI uses Docker containers to manage the local development stack. Follow the official guide to install and configure [Docker Desktop](https://docs.docker.com/desktop):
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Alternately, you can use a different container tool that offers Docker compatible APIs.
+
+- [Rancher Desktop](https://rancherdesktop.io/) (macOS, Windows, Linux)
+- [Podman](https://podman.io/) (macOS, Windows, Linux)
+- [OrbStack](https://orbstack.dev/) (macOS)
+- [colima](https://github.com/abiosoft/colima) (macOS)
+
+
+
+Inside the folder where you want to create your project, run:
+
+```bash
+supabase init
+```
+
+This will create a new `supabase` folder. It's safe to commit this folder to your version control system.
+
+Now, to start the Supabase stack, run:
+
+```bash
+supabase start
+```
+
+This takes time on your first run because the CLI needs to download the Docker images to your local machine. The CLI includes the entire Supabase toolset, and a few additional images that are useful for local development (like a local SMTP server and a database diff tool).
+
+## Access your project's services
+
+Once all of the Supabase services are running, you'll see output containing your local Supabase credentials. It should look like this, with urls and keys that you'll use in your local project:
+
+```
+
+Started supabase local development setup.
+
+ API URL: http://localhost:54321
+ DB URL: postgresql://postgres:postgres@localhost:54322/postgres
+ Studio URL: http://localhost:54323
+ Inbucket URL: http://localhost:54324
+ anon key: eyJh......
+service_role key: eyJh......
+
+```
+
+
+
+
+```sh
+# Default URL:
+http://localhost:54323
+```
+
+The local development environment includes Supabase Studio, a graphical interface for working with your database.
+
+
+
+
+
+
+```sh
+# Default URL:
+postgresql://postgres:postgres@localhost:54322/postgres
+```
+
+The local Postgres instance can be accessed through [`psql`](https://www.postgresql.org/docs/current/app-psql.html) or any other Postgres client, such as [pgadmin](https://www.pgadmin.org/). For example:
+
+```bash
+psql 'postgresql://postgres:postgres@localhost:54322/postgres'
+```
+
+
+
+To access the database from an edge function in your local Supabase setup, replace `localhost` with `host.docker.internal`.
+
+
+
+
+
+
+```sh
+# Default URL:
+http://localhost:54321
+```
+
+If you are accessing these services without the client libraries, you may need to pass the client keys as an `Authorization` header. Learn more about [JWT headers](/docs/learn/auth-deep-dive/auth-deep-dive-jwts).
+
+```sh
+curl 'http://localhost:54321/rest/v1/' \
+ -H "apikey: " \
+ -H "Authorization: Bearer "
+
+http://localhost:54321/rest/v1/ # REST (PostgREST)
+http://localhost:54321/realtime/v1/ # Realtime
+http://localhost:54321/storage/v1/ # Storage
+http://localhost:54321/auth/v1/ # Auth (GoTrue)
+```
+
+
+
+`` is provided when you run the command `supabase start`.
+
+
+
+
+
+
+Local logs rely on the Supabase Analytics Server which accesses the docker logging driver by either volume mounting `/var/run/docker.sock` domain socket on Linux and macOS, or exposing `tcp://localhost:2375` daemon socket on Windows. These settings must be configured manually after [installing](/docs/guides/cli/getting-started#installing-the-supabase-cli) the Supabase CLI.
+
+
+
+For advanced logs analysis using the Logs Explorer, it is advised to use the BigQuery backend instead of the default Postgres backend. Read about the steps [here](/docs/reference/self-hosting-analytics/introduction#bigquery).
+
+
+
+All logs will be stored in the local database under the `_analytics` schema.
+
+
+
+
+## Stopping local services
+
+When you are finished working on your Supabase project, you can stop the stack (without resetting your local database):
+
+```bash
+supabase stop
+```
+
+## Learn more
+
+- [CLI configuration](/docs/guides/local-development/cli/config)
+- [CLI reference](/docs/reference/cli)
diff --git a/apps/docs/content/guides/cli/testing-and-linting.mdx b/apps/docs/content/guides/local-development/cli/testing-and-linting.mdx
similarity index 100%
rename from apps/docs/content/guides/cli/testing-and-linting.mdx
rename to apps/docs/content/guides/local-development/cli/testing-and-linting.mdx
diff --git a/apps/docs/content/guides/cli/customizing-email-templates.mdx b/apps/docs/content/guides/local-development/customizing-email-templates.mdx
similarity index 100%
rename from apps/docs/content/guides/cli/customizing-email-templates.mdx
rename to apps/docs/content/guides/local-development/customizing-email-templates.mdx
diff --git a/apps/docs/content/guides/cli/managing-config.mdx b/apps/docs/content/guides/local-development/managing-config.mdx
similarity index 86%
rename from apps/docs/content/guides/cli/managing-config.mdx
rename to apps/docs/content/guides/local-development/managing-config.mdx
index 3b6ec9ef3eb..7ac8d864c72 100644
--- a/apps/docs/content/guides/cli/managing-config.mdx
+++ b/apps/docs/content/guides/local-development/managing-config.mdx
@@ -26,6 +26,20 @@ redirect_uri = "" # Overrides the default auth redirectUrl.
You can reference environment variables within the `config.toml` file using the `env()` function. This will detect any values stored in an `.env` file at the root of your project directory. This is particularly useful for storing sensitive information like API keys, and any other values that you don't want to check into version control.
+```
+.
+├── .env
+├── .env.example
+└── supabase
+ └── config.toml
+```
+
+
+
+Do NOT commit your `.env` into git. Be sure to configure your `.gitignore` to exclude this file.
+
+
+
For example, if your `.env` contained the following values:
```bash
diff --git a/apps/docs/content/guides/cli/local-development.mdx b/apps/docs/content/guides/local-development/overview.mdx
similarity index 59%
rename from apps/docs/content/guides/cli/local-development.mdx
rename to apps/docs/content/guides/local-development/overview.mdx
index 5e7ff222041..5b1d5912158 100644
--- a/apps/docs/content/guides/cli/local-development.mdx
+++ b/apps/docs/content/guides/local-development/overview.mdx
@@ -1,169 +1,17 @@
---
-id: 'local-development'
-title: 'Local Development'
-description: 'How to use Supabase on your local development machine.'
-subtitle: 'How to use Supabase on your local development machine.'
+id: 'overview'
+title: 'Local development with schema migrations'
+description: 'Develop locally with the Supabase CLI and schema migrations.'
+subtitle: 'Develop locally with the Supabase CLI and schema migrations.'
video: 'https://www.youtube-nocookie.com/v/vyHyYpvjaks'
tocVideo: 'vyHyYpvjaks'
---
Supabase is a flexible platform that lets you decide how you want to build your projects. You can use the Dashboard directly to get up and running quickly, or use a proper local setup. We suggest you work locally and deploy your changes to a linked project on the [Supabase Platform](https://app.supabase.io/).
-Doing things directly on the platform via the [Dashboard](https://app.supabase.io/) is fine when you're getting started, but it's a good idea to move to a proper local workflow before you get too far. Working locally, generating migrations as you change your tables, and applying those migrations to a linked project on the [Platform](https://app.supabase.io/) keeps everything nicely organized as you grow.
+Develop locally using the CLI to run a local Supabase stack. You can use the integrated Studio Dashboard to make changes, then capture your changes in schema migration files, which can be saved in version control.
-## Why develop locally?
-
-The Dashboard provides a wide range of features for setting up your project: creating tables, adding columns, changing existing columns, creating views, setting up RLS policies, and more. Given all of the Dashboard's capabilities, you might question the need to work locally. Here's a few advantages to working this way:
-
-1. **Faster Development**: Developing locally allows you to work without any network latency or internet disruptions.
-
-2. **Easier Collaboration**: Developing locally can make it easier to collaborate with others on the same project.
-
-3. **Cost-Effective**: Supabase provides a generous free plan and gives you two free projects to get started. But what if you need more than two? When you develop locally, you can spin up unlimited local projects and link them with live projects when you're ready to launch.
-
-4. **Configuration in code**: If you directly change your tables via the Dashboard, none of that gets captured in code. If you follow these local development practices, you'll store all of your table schemas in code.
-
-5. **Work offline**: Need to work from a train? A plane? An automobile? No problem. Developing your project locally allows you to work offline.
-
-## Log in to the Supabase CLI
-
-Log in if you are planning to deploy your project to the Supabase Platform. This step is optional and is not required to use the Supabase CLI.
-
-
-
-```bash Terminal
-supabase login
-```
-
-```bash NPX
-npx supabase login
-```
-
-
-
-## Initialize your project
-
-Create a new folder for your project and start a new git repository:
-
-```bash
-# create your project folder
-mkdir your-project
-
-# move into the new folder
-cd your-project
-
-# start a new git repository — important, don't skip this step
-git init
-```
-
-## Start Supabase services
-
-[Initialize](/docs/reference/cli/usage#supabase-init) Supabase to set up the configuration for developing your project locally:
-
-```bash
-supabase init
-```
-
-Make sure Docker is running.
-
-
-
-There are a number of different projects available to download Docker from:
-
-- [Docker Desktop](https://docs.docker.com/get-docker/) (macOS, Windows, Linux)
-- [Rancher Desktop](https://rancherdesktop.io/) (macOS, Windows, Linux)
-- [OrbStack](https://orbstack.dev/) (macOS)
-- [colima](https://github.com/abiosoft/colima) (macOS)
-
-
-
-The [start](/docs/reference/cli/usage#supabase-start) command uses Docker to start the Supabase [services](/docs/guides/getting-started/architecture).
-This command may take a while to run if this is the first time using the CLI.
-
-```bash
-supabase start
-```
-
-Once all of the Supabase services are running, you'll see output containing your local Supabase credentials. It should look like this, with urls and keys that you'll use in your local project:
-
-```
-
-Started supabase local development setup.
-
- API URL: http://localhost:54321
- DB URL: postgresql://postgres:postgres@localhost:54322/postgres
- Studio URL: http://localhost:54323
- Inbucket URL: http://localhost:54324
- anon key: eyJh......
-service_role key: eyJh......
-
-```
-
-You can use the [`supabase stop`](/docs/reference/cli/usage#supabase-stop) command at any time to stop all services (without resetting your local database). Use `supabase stop --no-backup` to stop all services and reset your local database.
-
-## Access your project's services
-
-You can now visit your local Dashboard at [http://localhost:54323](http://localhost:54323), and access the database directly with any Postgres client via `postgresql://postgres:postgres@localhost:54322/postgres`.
-
-
-
-
-```sh
-# Default URL:
-postgresql://postgres:postgres@localhost:54322/postgres
-```
-
-The local Postgres instance can be accessed through [`psql`](https://www.postgresql.org/docs/current/app-psql.html)
-or any other Postgres client, such as [pgadmin](https://www.pgadmin.org/).
-
-For example:
-
-```bash
-psql 'postgresql://postgres:postgres@localhost:54322/postgres'
-```
-
-
-
-To access the database from an edge function in your local Supabase setup, replace `localhost` with `host.docker.internal`.
-
-
-
-
-
-
-```sh
-# Default URL:
-http://localhost:54321
-```
-
-If you are accessing these services without the client libraries, you may need to pass the client keys as an `Authorization` header.
-Learn more about [JWT headers](/docs/learn/auth-deep-dive/auth-deep-dive-jwts).
-
-```sh
-curl 'http://localhost:54321/rest/v1/' \
- -H "apikey: " \
- -H "Authorization: Bearer "
-
-http://localhost:54321/rest/v1/ # REST (PostgREST)
-http://localhost:54321/realtime/v1/ # Realtime
-http://localhost:54321/storage/v1/ # Storage
-http://localhost:54321/auth/v1/ # Auth (GoTrue)
-```
-
-
-
-`` is provided when you run the command `supabase start`.
-
-
-
-
-
+Alternatively, if you're comfortable with migration files and SQL, you can write your own migrations and push them to the local database for testing before sharing your changes.
## Database migrations
@@ -296,7 +144,7 @@ alter table
Now that you are managing your database with migrations scripts, it would be great have some seed data to use every time you reset the database.
-For this, you can use the seed script in `supabase/seed.sql`. This file was automatically created when you ran [`supabase init`](/docs/reference/cli/usage#supabase-init)) at the beginning.
+For this, you can create a seed script in `supabase/seed.sql`.
@@ -372,6 +220,20 @@ The last step is deploying these changes to a live Supabase project.
You've been developing your project locally, making changes to your tables via migrations. It's time to deploy your project to the Supabase Platform and start scaling up to millions of users! Head over to [Supabase](https://supabase.com/dashboard) and create a new project to deploy to.
+### Log in to the Supabase CLI
+
+
+
+```bash Terminal
+supabase login
+```
+
+```bash npx
+npx supabase login
+```
+
+
+
### Link your project
Associate your project with your remote project using [`supabase link`](/docs/reference/cli/usage#supabase-link).
@@ -457,29 +319,21 @@ Your RLS policies on storage buckets can be pulled locally by specifying `storag
supabase db pull --schema storage
```
-The buckets and objects themselves are rows in the storage schema so they won't be pulled automatically.
-
-### Local logging
-
-Local logs rely on the Supabase Analytics Server, and are not enabled by default. To enable this, edit your `supabase/config.toml` file and set `enabled` to `true`.
+The buckets and objects themselves are rows in the storage tables so they won't appear in your schema. You can instead define them via `supabase/config.toml` file. For example,
```bash supabase/config.toml
-[analytics]
-# Set enabled to true to see logs in the Logs Explorer
-enabled = true
-port = 54327
-vector_port = 54328
-# Configure one of the supported backends: `postgres`, `bigquery`.
-backend = "postgres"
+[storage.buckets.images]
+public = false
+file_size_limit = "50MiB"
+allowed_mime_types = ["image/png", "image/jpeg"]
+objects_path = "./images"
```
-
+This will upload files from `supabase/images` directory to a bucket named `images` in your project with one command.
-For advanced logs analysis using the Logs Explorer, it is advised to use the BigQuery backend instead of the default Postgres backend. Read about the steps [here](/docs/reference/self-hosting-analytics/introduction#bigquery).
-
-
-
-Logs will be directed to the Analytics server instead of the docker logging driver. All logs will be stored in the local database under the `_analytics` schema.
+```bash
+supabase seed buckets
+```
## Limitations and considerations
diff --git a/apps/docs/content/guides/local-development/seeding-your-database.mdx b/apps/docs/content/guides/local-development/seeding-your-database.mdx
new file mode 100644
index 00000000000..ac10caeda56
--- /dev/null
+++ b/apps/docs/content/guides/local-development/seeding-your-database.mdx
@@ -0,0 +1,178 @@
+---
+id: 'seeding-your-database'
+title: 'Seeding your database'
+description: 'Populate your database with initial data for reproducible environments across local and testing.'
+subtitle: 'Populate your database with initial data for reproducible environments across local and testing.'
+---
+
+## What is seed data?
+
+Seeding is the process of populating a database with initial data, typically used to provide sample or default records for testing and development purposes. You can use this to create "reproducible environments" for local development, staging, and production.
+
+## Using seed files
+
+Seed files are executed every time you run `supabase start` or `supabase db reset`. Seeding occurs _after_ all database migrations have been completed. As a best practice, only include data insertions in your seed files, and avoid adding schema statements.
+
+By default, if no specific configuration is provided, the system will look for a seed file matching the pattern `supabase/seed.sql`. This maintains backward compatibility with earlier versions, where the seed file was placed in the `supabase` folder.
+
+You can add any SQL statements to this file. For example:
+
+```sql
+insert into countries
+ (name, code)
+values
+ ('United States', 'US'),
+ ('Canada', 'CA'),
+ ('Mexico', 'MX');
+```
+
+If you want to manage multiple seed files or organize them across different folders, you can configure additional paths or glob patterns in your `config.toml` (see the [next section](#splitting-up-your-seed-data) for details).
+
+### Splitting up your seed file
+
+For better modularity and maintainability, you can split your seed data into multiple files. For example, you can organize your seeds by table and include files such as `countries.sql` and `cities.sql`. Configure them in `config.toml` like so:
+
+```toml supabase/config.toml
+[db.seed]
+enabled = true
+sql_paths = ['./countries.sql', './cities.sql']
+```
+
+Or to include all `.sql` files under a specific folder you can do:
+
+```toml supabase/config.toml
+[db.seed]
+enabled = true
+sql_paths = ['./seeds/*.sql']
+```
+
+
+
+The CLI processes seed files in the order they are declared in the `sql_paths` array. If a glob pattern is used and matches multiple files, those files are sorted in lexicographic order to ensure consistent execution. Additionally:
+
+- The base folder for the pattern matching is `supabase` so `./countries.sql` will search for `supabase/countries.sql`
+- Files matched by multiple patterns will be deduplicated to prevent redundant seeding.
+- If a pattern does not match any files, a warning will be logged to help you troubleshoot potential configuration issues.
+
+
+
+## Generating seed data
+
+You can generate seed data for local development using [Snaplet](https://github.com/snaplet/seed).
+
+
+
+To use Snaplet, you need to have Node.js and npm installed. You can add Node.js to your project by running `npm init -y` in your project directory.
+
+
+
+If this is your first time using Snaplet to seed your project, you'll need to set up Snaplet with the following command:
+
+```bash
+npx @snaplet/seed init
+```
+
+This command will analyze your database and its structure, and then generate a JavaScript client which can be used to define exactly how your data should be generated using code. The `init` command generates a configuration file, `seed.config.ts` and an example script, `seed.ts`, as a starting point.
+
+
+
+During `init` if you are not using an Object Relational Mapper (ORM) or your ORM is not in the supported list, choose `node-postgres`.
+
+
+
+In most cases you only want to generate data for specific schemas or tables. This is defined with `select`. Here is an example `seed.config.ts` configuration file:
+
+```ts
+export default defineConfig({
+ adapter: async () => {
+ const client = new Client({
+ connectionString: 'postgresql://postgres:postgres@localhost:54322/postgres',
+ })
+ await client.connect()
+ return new SeedPg(client)
+ },
+ // We only want to generate data for the public schema
+ select: ['!*', 'public.*'],
+})
+```
+
+Suppose you have a database with the following schema:
+
+
+
+You can use the seed script example generated by Snaplet `seed.ts` to define the values you want to generate. For example:
+
+- A `Post` with the title `"There is a lot of snow around here!"`
+- The `Post.createdBy` user with an email address ending in `"@acme.org"`
+- Three `Post.comments` from three different users.
+
+```ts seed.ts
+import { createSeedClient } from '@snaplet/seed'
+import { copycat } from '@snaplet/copycat'
+
+async function main() {
+ const seed = await createSeedClient({ dryRun: true })
+
+ await seed.Post([
+ {
+ title: 'There is a lot of snow around here!',
+ createdBy: {
+ email: (ctx) =>
+ copycat.email(ctx.seed, {
+ domain: 'acme.org',
+ }),
+ },
+ Comment: (x) => x(3),
+ },
+ ])
+
+ process.exit()
+}
+
+main()
+```
+
+Running `npx tsx seed.ts > supabase/seed.sql` generates the relevant SQL statements inside your `supabase/seed.sql` file:
+
+```sql
+-- The `Post.createdBy` user with an email address ending in `"@acme.org"`
+INSERT INTO "User" (name, email) VALUES ("John Snow", "snow@acme.org")
+
+--- A `Post` with the title `"There is a lot of snow around here!"`
+INSERT INTO "Post" (title, content, createdBy) VALUES (
+ "There is a lot of snow around here!",
+ "Lorem ipsum dolar",
+ 1)
+
+--- Three `Post.Comment` from three different users.
+INSERT INTO "User" (name, email) VALUES ("Stephanie Shadow", "shadow@domain.com")
+INSERT INTO "Comment" (text, userId, postId) VALUES ("I love cheese", 2, 1)
+
+INSERT INTO "User" (name, email) VALUES ("John Rambo", "rambo@trymore.dev")
+INSERT INTO "Comment" (text, userId, postId) VALUES ("Lorem ipsum dolar sit", 3, 1)
+
+INSERT INTO "User" (name, email) VALUES ("Steven Plank", "s@plank.org")
+INSERT INTO "Comment" (text, userId, postId) VALUES ("Actually, that's not correct...", 4, 1)
+```
+
+Whenever your database structure changes, you will need to regenerate `@snaplet/seed` to keep it in sync with the new structure. You can do this by running:
+
+```bash
+npx @snaplet/seed sync
+```
+
+You can further enhance your seed script by using Large Language Models to generate more realistic data. To enable this feature, set one of the following environment variables in your `.env` file:
+
+```plaintext
+OPENAI_API_KEY=
+GROQ_API_KEY=
+```
+
+After setting the environment variables, run the following commands to sync and generate the seed data:
+
+```bash
+npx @snaplet/seed sync
+npx tsx seed.ts > supabase/seed.sql
+```
+
+For more information, check out Snaplet's [seed documentation](https://snaplet-seed.netlify.app/seed/integrations/supabase)
diff --git a/apps/docs/content/guides/monitoring-troubleshooting.mdx b/apps/docs/content/guides/monitoring-troubleshooting.mdx
new file mode 100644
index 00000000000..bbe769214af
--- /dev/null
+++ b/apps/docs/content/guides/monitoring-troubleshooting.mdx
@@ -0,0 +1,9 @@
+---
+title: Monitoring & Troubleshooting
+---
+
+Monitoring your project can help you identify issues and fix them quickly. This section provides guidance on:
+
+- Monitoring errors
+- Working with logs in Supabase
+- Troubleshooting common issues with the platform
diff --git a/apps/docs/content/guides/monitoring-troubleshooting/advanced-log-filtering.mdx b/apps/docs/content/guides/monitoring-troubleshooting/advanced-log-filtering.mdx
new file mode 100644
index 00000000000..670f5f958d6
--- /dev/null
+++ b/apps/docs/content/guides/monitoring-troubleshooting/advanced-log-filtering.mdx
@@ -0,0 +1,193 @@
+---
+title: 'Advanced Log Filtering'
+description: 'Filter logs with regular expressions'
+---
+
+# Querying the logs
+
+## Understanding field references
+
+The log tables are queried with subset of BigQuery SQL syntax. They all have three columns: `event_message`, `timestamp`, and `metadata`.
+
+| column | description |
+| ------------- | --------------------------- |
+| timestamp | time event was recorded |
+| event_message | the log's message |
+| metadata | information about the event |
+
+The `metadata` column is an array of JSON objects that stores important details about each recorded event. For example, in the postgres table, the `metadata.parsed.error_severity` field indicates the error level of an event. To work with its values, you need to `unnest` them using a `cross join`.
+
+This approach is commonly used with JSON and array columns, so it might look a bit unfamiliar if you're not used to working with these data types.
+
+```sql
+select
+ event_message,
+ parsed.error_severity,
+ parsed.user_name
+from
+ postgres_logs
+ -- extract first layer
+ cross join unnest(postgres_logs.metadata) as metadata
+ -- extract second layer
+ cross join unnest(metadata.parsed) as parsed;
+```
+
+## Expanding results
+
+Logs returned by queries may be difficult to read in table format. A row can be double-clicked to expand the results into more readable JSON:
+
+
+
+## Filtering with [regular expressions](https://en.wikipedia.org/wiki/Regular_expression)
+
+The Logs use BigQuery Style regular expressions with the [regexp_contains function](https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#regexp_contains). In its most basic form, it will check if a string is present in a specified column.
+
+```sql
+select
+ cast(timestamp as datetime) as timestamp,
+ event_message,
+ metadata
+from postgres_logs
+where regexp_contains(event_message, 'is present');
+```
+
+There are multiple operators that you should consider using:
+
+### Find messages that start with a phrase
+
+`^` only looks for values at the start of a string
+
+```sql
+-- find only messages that start with connection
+regexp_contains(event_message, '^connection')
+```
+
+### Find messages that end with a phrase:
+
+`$` only looks for values at the end of the string
+
+```sql
+-- find only messages that ends with port=12345
+regexp_contains(event_message, '$port=12345')
+```
+
+### Ignore case sensitivity:
+
+`(?i)` ignores capitalization for all proceeding characters
+
+```sql
+-- find all event_messages with the word "connection"
+regexp_contains(event_message, '(?i)COnnecTion')
+```
+
+### Wildcards:
+
+`.` can represent any string of characters
+
+```sql
+-- find event_messages like "helloworld"
+regexp_contains(event_message, 'hello.world')
+```
+
+### Alphanumeric ranges:
+
+`[1-9a-zA-Z]` finds any strings with only numbers and letters
+
+```sql
+-- find event_messages that contain a number between 1 and 5 (inclusive)
+regexp_contains(event_message, '[1-5]')
+```
+
+### Repeated values:
+
+`x*` zero or more x
+`x+` one or more x
+`x?` zero or one x
+`x{4,}` four or more x
+`x{3}` exactly 3 x
+
+```sql
+-- find event_messages that contains any sequence of 3 digits
+regexp_contains(event_message, '[0-9]{3}')
+```
+
+### Escaping reserved characters:
+
+`\.` interpreted as period `.` instead of as a wildcard
+
+```sql
+-- escapes .
+regexp_contains(event_message, 'hello world\.')
+```
+
+### `or` statements:
+
+`x|y` any string with `x` or `y` present
+
+```sql
+-- find event_messages that have the word 'started' followed by either the word "host" or "authenticated"
+regexp_contains(event_message, 'started host|authenticated')
+```
+
+### and/or/not statements in SQL:
+
+`and`, `or`, and `not` are all native terms in SQL and can be used in conjunction with regular expressions to filter results
+
+```sql
+select
+ cast(timestamp as datetime) as timestamp,
+ event_message,
+ metadata
+from postgres_logs
+where
+ (regexp_contains(event_message, 'connection') and regexp_contains(event_message, 'host'))
+ or not regexp_contains(event_message, 'received');
+```
+
+### Filtering and unnesting example
+
+**Filter for Postgres**
+
+```sql
+select
+ cast(postgres_logs.timestamp as datetime) as timestamp,
+ parsed.error_severity,
+ parsed.user_name,
+ event_message
+from
+ postgres_logs
+ cross join unnest(metadata) as metadata
+ cross join unnest(metadata.parsed) as parsed
+where regexp_contains(parsed.error_severity, 'ERROR|FATAL|PANIC')
+order by timestamp desc
+limit 100;
+```
+
+## Limitations
+
+### Log tables cannot be joined together
+
+Each product table operates independently without the ability to join with other log tables. This may change in the future.
+
+### The `with` keyword and subqueries are not supported
+
+The parser does not yet support `with` and subquery statements.
+
+### The `ilike` and `similar to` keywords are not supported
+
+Although `like` and other comparison operators can be used, `ilike` and `similar to` are incompatible with BigQuery's variant of SQL. `regexp_contains` can be used as an alternative.
+
+### The wildcard operator `*` to select columns is not supported
+
+The log parser is not able to parse the `*` operator for column selection. Instead, you can access all fields from the `metadata` column:
+
+```sql
+select
+ cast(postgres_logs.timestamp as datetime) as timestamp,
+ event_message,
+ metadata
+from
+
+order by timestamp desc
+limit 100;
+```
diff --git a/apps/docs/content/guides/platform/exhaust-cpu.mdx b/apps/docs/content/guides/monitoring-troubleshooting/exhaust-cpu.mdx
similarity index 93%
rename from apps/docs/content/guides/platform/exhaust-cpu.mdx
rename to apps/docs/content/guides/monitoring-troubleshooting/exhaust-cpu.mdx
index b17673d7cdd..b056ec150d2 100644
--- a/apps/docs/content/guides/platform/exhaust-cpu.mdx
+++ b/apps/docs/content/guides/monitoring-troubleshooting/exhaust-cpu.mdx
@@ -32,7 +32,7 @@ Everything you do with your Supabase project requires compute. Hence, there can
- **Query performance:** Queries that take a long time to complete (>1 second) as well as excessive amounts of querying can put a strain on the CPU. Check our guide on [examining query performance](https://supabase.com/docs/guides/platform/performance#examining-query-performance).
- **Missing indexes:** Your database might have to scan through a large amount of data to find the information it needs. Creating indexes helps your database find data faster. Learn more about indexes [here](https://supabase.com/docs/guides/database/postgres/indexes).
-- **Unsuitable compute:** The instance size of your Supabase project might not be suitable for your application as you might have more traffic or run resource-intensive operations.
+- **Unsuitable compute:** The compute size of your Supabase project might not be suitable for your application as you might have more traffic or run resource-intensive operations.
## Solving high CPU usage
diff --git a/apps/docs/content/guides/platform/exhaust-disk-io.mdx b/apps/docs/content/guides/monitoring-troubleshooting/exhaust-disk-io.mdx
similarity index 100%
rename from apps/docs/content/guides/platform/exhaust-disk-io.mdx
rename to apps/docs/content/guides/monitoring-troubleshooting/exhaust-disk-io.mdx
diff --git a/apps/docs/content/guides/platform/exhaust-ram.mdx b/apps/docs/content/guides/monitoring-troubleshooting/exhaust-ram.mdx
similarity index 100%
rename from apps/docs/content/guides/platform/exhaust-ram.mdx
rename to apps/docs/content/guides/monitoring-troubleshooting/exhaust-ram.mdx
diff --git a/apps/docs/pages/guides/platform/exhaust-swap.mdx b/apps/docs/content/guides/monitoring-troubleshooting/exhaust-swap.mdx
similarity index 89%
rename from apps/docs/pages/guides/platform/exhaust-swap.mdx
rename to apps/docs/content/guides/monitoring-troubleshooting/exhaust-swap.mdx
index adfb7a7a5ea..247e9466d36 100644
--- a/apps/docs/pages/guides/platform/exhaust-swap.mdx
+++ b/apps/docs/content/guides/monitoring-troubleshooting/exhaust-swap.mdx
@@ -1,11 +1,8 @@
-import Layout from '~/layouts/DefaultGuideLayout'
-
-export const meta = {
- id: 'exhaust-swap',
- title: 'High swap usage',
- description:
- 'Learn what high Swap usage could mean for your Supabase instance and what could have caused it.',
-}
+---
+id: 'exhaust-swap'
+title: 'High swap usage'
+description: 'Learn what high Swap usage could mean for your Supabase instance and what could have caused it.'
+---
Learn what high Swap usage means, what can cause it, and how to solve it.
@@ -54,7 +51,7 @@ Everything you do with your Supabase project requires compute. Hence, there can
- **Query performance:** You might have high read traffic or queries that process a large amount of data on disk (this can happen even if you are returning a small amount of data).
- **Missing indexes:** Your database might have to scan through a large amount of data to find the information it needs. Creating indexes helps your database find data faster. See the [indexes guide](https://supabase.com/docs/guides/database/postgres/indexes) to learn more.
-- **Unsuitable compute:** The instance size of your Supabase project might not be suitable for your application as you might have more traffic or run resource-intensive operations.
+- **Unsuitable compute:** The compute size of your Supabase project might not be suitable for your application as you might have more traffic or run resource-intensive operations.
- **Workload style:** The usage pattern of your Supabase project might be more read heavy, or involve large amounts of data.
- **Extensions:** You might be using extensions that perform intensive operations on large datasets. This increases resource usage.
@@ -65,7 +62,3 @@ If you find that your RAM and Swap usage are high, you have three options:
1. **Optimize performance:** Get more out of your instance's resources by optimizing your usage. See the [performance tuning guide](https://supabase.com/docs/guides/platform/performance#examining-query-performance) and our [production readiness guide](https://supabase.com/docs/guides/platform/going-into-prod#performance).
2. **Upgrade your compute:** You can get a Compute Add-on for your project. Navigate to [the **Addons** section in project settings](https://supabase.com/dashboard/project/_/settings/addons?panel=computeInstance) to see your upgrade options.
3. **Read Replicas:** You can spread the load on your Supabase project by creating a Read Replica. See [the read replicas guide](https://supabase.com/docs/guides/platform/read-replicas) for more information.
-
-export const Page = ({ children }) =>
-
-export default Page
diff --git a/apps/docs/content/guides/platform/http-status-codes.mdx b/apps/docs/content/guides/monitoring-troubleshooting/http-status-codes.mdx
similarity index 71%
rename from apps/docs/content/guides/platform/http-status-codes.mdx
rename to apps/docs/content/guides/monitoring-troubleshooting/http-status-codes.mdx
index fb560070b7c..5dcfec6e635 100644
--- a/apps/docs/content/guides/platform/http-status-codes.mdx
+++ b/apps/docs/content/guides/monitoring-troubleshooting/http-status-codes.mdx
@@ -18,6 +18,17 @@ The Supabase platform offers several HTTP APIs for each project. These APIs can
4XX status codes indicate an issue on the client's end with the request being made. These could include missing or invalid auth information, a malformed request, making too many requests in too short a time period ("rate limiting"), or a network issue on the client's end.
+### 402 service restriction
+
+In case the Fair Use Policy is applied, the projects of your organization may be restricted from processing requests. If service restrictions are applied, projects will return a 402 status code together with a description of why the project is restricted. The description can contain one or more of the following codes:
+
+- `exceeded_*` indicates that the project has continued to exceeded the usage limits of its quota, e.g. `exceed_egress_quota`, `exceed_db_size_quota`.
+- `overdue_payment` indicates that the organization has overdue bills.
+
+You will still have access to your data through the Supabase dashboard when the Fair Use Policy is applied.
+
+See the [Fair Use Policy](/docs/guides/platform/billing-faq#fair-use-policy) section to learn more about the restrictions and how to remove them.
+
## 5XX server or project errors
5XX status codes indicate that the project was unable to process the request successfully, but not because of an issue with the client's request.
@@ -32,7 +43,7 @@ The Supabase platform offers several HTTP APIs for each project. These APIs can
The project the request was being made against has been paused. The project cannot process requests until it is un-paused by the owner.
-Free-plan projects may be paused due to inactivity, on request by the owner, or in rare instances, due to abuse.
+Free Plan projects may be paused due to inactivity, on request by the owner, or in rare instances, due to abuse.
#### 544 project API gateway timeout
diff --git a/apps/docs/content/guides/monitoring-troubleshooting/log-drains.mdx b/apps/docs/content/guides/monitoring-troubleshooting/log-drains.mdx
new file mode 100644
index 00000000000..f2f508f581e
--- /dev/null
+++ b/apps/docs/content/guides/monitoring-troubleshooting/log-drains.mdx
@@ -0,0 +1,182 @@
+---
+id: 'log-drains'
+title: 'Log Drains'
+description: 'Getting started with Supabase Log Drains'
+---
+
+Log drains will send all logs of the Supabase stack to one or more desired destinations. It is only available for customers on Team and Enterprise Plans. Log drains is available in the dashboard under [Project Settings > Log Drains](https://supabase.com/dashboard/project/_/settings/log-drains).
+
+You can read about the initial announcement [here](https://supabase.com/blog/log-drains) and vote for your preferred drains in [this discussion](https://github.com/orgs/supabase/discussions/28324?sort=top).
+
+# Supported Destinations
+
+The following table lists the supported destinations and the required setup configuration:
+
+| Destination | Transport Method | Configuration |
+| --------------------- | ---------------- | ------------------------------------------------- |
+| Generic HTTP endpoint | HTTP | URL HTTP Version Gzip Headers |
+| Datadog | HTTP | API Key Region |
+
+HTTP requests are batched with a max of 250 logs or 1 second intervals, whichever happens first. Logs are compressed via Gzip if the destination supports it.
+
+## Generic HTTP Endpoint
+
+Logs are sent as a POST request with a JSON body. Both HTTP/1 and HTTP/2 protocols are supported.
+Custom headers can optionally be configured for all requests.
+
+Note that requests are **unsigned**.
+
+
+ Unsigned requests to HTTP endpoints are temporary and all requests will signed in the near future.
+
+
+
+
+
+1. Create and deploy the edge function
+
+Generate a new edge function template and update it to log out the received JSON payload. For simplicity, we will accept any request with an Anon Key.
+
+```bash
+supabase functions new hello-world
+```
+
+You can use this example snippet as an illustration of how the received request will be like.
+
+```ts
+import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
+
+Deno.serve(async (req) => {
+ const data = await req.json()
+
+ console.log(`Received ${data.length} logs, first log:\n ${JSON.stringify(data[0])}`)
+ return new Response(JSON.stringify({ message: 'ok' }), {
+ headers: { 'Content-Type': 'application/json' },
+ })
+})
+```
+
+And then deploy it with:
+
+```bash
+supabase functions deploy hello-world --project-ref [PROJECT REF]
+```
+
+
+
+This will create an infinite loop, as we are generating an additional log event that will eventually trigger a new request to this edge function. However, due to the batching nature of how Log Drain events are dispatched, the rate of edge function triggers will not increase greatly and will have an upper bound.
+
+
+
+2. Configure the HTTP Drain
+
+Create a HTTP drain under the [Project Settings > Log Drains](https://supabase.com/dashboard/project/_/settings/log-drains).
+
+- Disable the Gzip, as we want to receive the payload without compression.
+- Under URL, set it to your edge function URL `https://[PROJECT REF].supabase.co/functions/v1/hello-world`
+- Under Headers, set the `Authorization: Bearer [ANON KEY]`
+
+
+
+
+
+
+
+Gzip payloads can be decompressed using native in-built APIs. Refer to the Edge Function [compression guide](https://supabase.com/docs/guides/functions/compression)
+
+```ts
+import { gunzipSync } from 'node:zlib'
+
+Deno.serve(async (req) => {
+ try {
+ // Check if the request body is gzip compressed
+ const contentEncoding = req.headers.get('content-encoding')
+ if (contentEncoding !== 'gzip') {
+ return new Response('Request body is not gzip compressed', {
+ status: 400,
+ })
+ }
+
+ // Read the compressed body
+ const compressedBody = await req.arrayBuffer()
+
+ // Decompress the body
+ const decompressedBody = gunzipSync(new Uint8Array(compressedBody))
+
+ // Convert the decompressed body to a string
+ const decompressedString = new TextDecoder().decode(decompressedBody)
+ const data = JSON.parse(decompressedString)
+ // Process the decompressed body as needed
+ console.log(`Received: ${data.length} logs.`)
+
+ return new Response('ok', {
+ headers: { 'Content-Type': 'text/plain' },
+ })
+ } catch (error) {
+ console.error('Error:', error)
+ return new Response('Error processing request', { status: 500 })
+ }
+})
+```
+
+
+
+
+
+## Datadog Logs
+
+Logs sent to Datadog have the name of the log source set on the `service` field of the event and the source set to `Supabase`. Logs are gzipped before they are sent to Datadog.
+
+The payload message is a JSON string of the raw log event, prefixed with the event timestamp.
+
+To setup Datadog log drain, generate a Datadog API key [here](https://app.datadoghq.com/organization-settings/api-keys) and the location of your Datadog site.
+
+
+
+
+ 1. Generate API Key in [Datadog dashboard](https://app.datadoghq.com/organization-settings/api-keys)
+ 2. Create log drain in [Supabase dashboard](https://supabase.com/dashboard/project/_/settings/log-drains)
+ 3. Watch for events in the [Datadog Logs page](https://app.datadoghq.com/logs)
+
+
+
+
+
+ [Grok parser](https://docs.datadoghq.com/service_management/events/pipelines_and_processors/grok_parser?tab=matchers) matcher for extracting the timestamp to a `date` field
+ ```
+ %{date("yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZ"):date}
+ ```
+
+ [Grok parser](https://docs.datadoghq.com/service_management/events/pipelines_and_processors/grok_parser?tab=matchers) matcher for converting stringified JSON to structured JSON on the `json` field.
+ ```
+ %{data::json}
+ ```
+
+ [Remapper](https://docs.datadoghq.com/service_management/events/pipelines_and_processors/remapper) for setting the log level.
+ ```
+ metadata.parsed.error_severity, metadata.level
+ ```
+
+
+
+
+
+If you are interested in other log drains, upvote them [here](https://github.com/orgs/supabase/discussions/28324)
diff --git a/apps/docs/content/guides/platform/logs.mdx b/apps/docs/content/guides/monitoring-troubleshooting/logs.mdx
similarity index 86%
rename from apps/docs/content/guides/platform/logs.mdx
rename to apps/docs/content/guides/monitoring-troubleshooting/logs.mdx
index de7b42234f4..d1d408dab4a 100644
--- a/apps/docs/content/guides/platform/logs.mdx
+++ b/apps/docs/content/guides/monitoring-troubleshooting/logs.mdx
@@ -87,14 +87,69 @@ Edge Function log messages have a max length of 10,000 characters. If you try to
---
+## Working with API logs
+
+[API logs](https://supabase.com/dashboard/project/_/logs/edge-logs) run through the Cloudflare edge servers and will have attached Cloudflare metadata under the `metadata.request.cf.*` fields.
+
+### Allowed headers
+
+A strict list of request and response headers are permitted in the API logs. Request and response headers will still be received by the server(s) and client(s), but will not be attached to the API logs generated.
+
+Request headers:
+
+- `accept`
+- `cf-connecting-ip`
+- `cf-ipcountry`
+- `host`
+- `user-agent`
+- `x-forwarded-proto`
+- `referer`
+- `content-length`
+- `x-real-ip`
+- `x-client-info`
+- `x-forwarded-user-agent`
+- `range`
+- `prefer`
+
+Response headers:
+
+- `cf-cache-status`
+- `cf-ray`
+- `content-location`
+- `content-range`
+- `content-type`
+- `content-length`
+- `date`
+- `transfer-encoding`
+- `x-kong-proxy-latency`
+- `x-kong-upstream-latency`
+- `sb-gateway-mode`
+- `sb-gateway-version`
+
+### Additional request metadata
+
+To attach additional metadata to a request, it is recommended to use the `User-Agent` header for purposes such as device or version identification.
+
+For example:
+
+```
+node MyApp/1.2.3 (device-id:abc123)
+Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 MyApp/1.2.3 (Foo v1.3.2; Bar v2.2.2)
+```
+
+
+
+Do not log Personal Identifiable Information (PII) within the `User-Agent` header, to avoid infringing data protection privacy laws. Overly fine-grained and detailed user agents may allow fingerprinting and identification of the end user through PII.
+
+
+
## Logging Postgres queries
To enable query logs for other categories of statements:
1. [Enable the pgAudit extension](https://supabase.com/dashboard/project/_/database/extensions).
-2. [Restart your project](https://supabase.com/dashboard/project/_/settings/general) using the **Fast database reboot** option.
-3. Configure `pgaudit.log` (see below). Perform a fast reboot if needed.
-4. View your query logs under [Logs > Postgres Logs](https://supabase.com/dashboard/project/_/logs/postgres-logs).
+2. Configure `pgaudit.log` (see below). Perform a fast reboot if needed.
+3. View your query logs under [Logs > Postgres Logs](https://supabase.com/dashboard/project/_/logs/postgres-logs).
### Configuring `pgaudit.log`
@@ -155,7 +210,9 @@ import { createClient } from '@supabase/supabase-js'
const options = {
realtime: {
- log_level: 'info',
+ params: {
+ log_level: 'info',
+ },
},
}
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', options)
@@ -285,7 +342,7 @@ from
where h.x_real_ip is not null and r.method = "GET";
```
-### Log source reference
+### Logs field reference
Refer to the full field reference for each available source below. Do note that in order to access each nested key, you would need to perform the [necessary unnesting joins](#unnesting-arrays)
@@ -293,11 +350,13 @@ Refer to the full field reference for each available source below. Do note that
{(logConstants) => (
{logConstants.schemas.map((schema) => (
-
+
-
Path
-
Type
+
+
Path
+
Type
+
{schema.fields
diff --git a/apps/docs/content/guides/monitoring-troubleshooting/metrics.mdx b/apps/docs/content/guides/monitoring-troubleshooting/metrics.mdx
new file mode 100644
index 00000000000..19823135bbe
--- /dev/null
+++ b/apps/docs/content/guides/monitoring-troubleshooting/metrics.mdx
@@ -0,0 +1,294 @@
+---
+id: 'metrics'
+title: 'Metrics'
+description: 'Observability for your Supabase project'
+---
+
+In addition to the reports and charts built in to the Supabase dashboard, each project hosted on the Supabase platform comes with a [Prometheus](https://prometheus.io/)-compatible metrics endpoint, which can be used to gather insight into the health and status of your project.
+
+You can use this endpoint to ingest data into your own monitoring and alerting infrastructure, as long as it is capable of scraping Prometheus-compatible endpoints, in order to set up custom rules beyond those supported by the Supabase dashboard.
+
+
+
+The endpoint discussed in this article is in beta, and the metrics returned by it might evolve or be changed in the future to increase its utility.
+
+
+
+
+
+The endpoint discussed in this article is not available on self-hosted.
+
+
+
+## Accessing the metrics endpoint
+
+Your project's metrics endpoint is accessible at `https://.supabase.co/customer/v1/privileged/metrics`. Access to the endpoint is secured via HTTP Basic Auth; the username is `service_role`, while the password is the service role JWT available through the Supabase dashboard.
+
+```shell
+> curl https://.supabase.co/customer/v1/privileged/metrics --user 'service_role:'
+```
+
+## Configuring Prometheus
+
+For self-hosted Prometheus, modify your `prometheus.yaml` file to add a Supabase job, and set the `metrics_path`, `scheme`, `basic_auth` and `targets` parameters. For example:
+
+```yaml
+scrape_configs:
+ - job_name: "MyJob"
+ metrics_path: "/customer/v1/privileged/metrics"
+ scheme: https
+ basic_auth:
+ username: "service_role"
+ password: ""
+ static_configs:
+ - targets: [
+ ".supabase.co:443"
+ ]
+ labels:
+ group: "MyGroupLabel"
+```
+
+Additionally, we [maintain a repo](https://github.com/supabase/supabase-grafana) for a scraping agent to work with Grafana Cloud.
+
+## Deploying Supabase Grafana
+
+The pre-configured Supabase Grafana Dashboard is an advanced version of the [Dashboard's Database Reports](https://supabase.com/dashboard/project/_/reports/database). It visualizes over 200 database performance and health metrics and is updated every minute.
+
+
+
+### Deploying locally
+
+Prerequisites:
+
+- Must have [Docker](https://www.docker.com/products/docker-desktop/) or a Docker compatible runtime on your machine
+
+
+
+Supabase Grafana only tracks metrics while it's active. For monitoring historical trends, it's better to use a dedicated server that can record data continuously.
+
+
+
+
+
+
+
+
+ Download the code from the [Github repo](https://github.com/supabase/supabase-grafana/tree/main)
+
+
+
+
+ ```sh
+ git clone https://github.com/supabase/supabase-grafana.git
+ ```
+
+
+
+
+
+
+
+ copy the `example.env` into a new `.env`
+
+
+
+ ```sh
+ cp .env.example .env
+ ```
+
+
+
+
+
+
+
+ - To monitor a single project, add your service_role key from the [API Settings](https://supabase.com/dashboard/project/_/settings/api) and Reference ID from the [General Settings](https://supabase.com/dashboard/project/_/settings/general).
+ - To monitor multiple projects, create an [account access token](https://supabase.com/dashboard/account/tokens) and add it to your `.env` file.
+
+
+
+
+
+
+
+
+ - Add the values to your `.env` file:
+ ```text .env
+ SUPABASE_PROJECT_REF="your_project_reference"
+ SUPABASE_SERVICE_ROLE_KEY="your_service_role"
+ ```
+
+
+
+
+ ```text .env
+ SUPABASE_ACCESS_TOKEN="your_access_token"
+ ```
+
+
+
+
+
+
+
+
+ For security, consider using a [password generator](https://bitwarden.com/password-generator/)
+
+
+
+
+
+ ```text .env
+ GRAFANA_PASSWORD="your password"
+ ```
+
+
+
+
+
+
+
+ Deploy the server. Once it is active, you can go to localhost:8000/login in the browser
+
+
+
+ ```text
+ docker compose up
+ ```
+
+
+
+
+
+
+ Login with your username (default is `admin`) and the password you created in step 4.
+
+
+ 
+
+
+
+
+
+### Deploying to Grafana cloud
+
+
+
+
+
+
+ Go to Grafana\'s [registration form](https://grafana.com/auth/sign-up/create-user?pg=login) and create an account
+
+
+ 
+
+
+
+
+
+
+
+ Grafana will prompt you to create a stack. Give it a name and continue.
+
+
+ 
+
+
+
+
+
+
+
+ Create a new Dashboard
+
+
+ 
+
+
+
+
+
+
+
+
+ 
+
+
+
+
+
+
+
+ Add your service_role key from the [API Settings](https://supabase.com/dashboard/project/_/settings/api) and Reference ID from the [General Settings](https://supabase.com/dashboard/project/_/settings/general) to your Grafana credentials. Then install your dashboard.
+
+
+ 
+
+
+
+
+
+
+### Deploying to fly.io
+
+
+ This section explains deploying to Fly.io using the included configuration file, but deployment to
+ other hosts is also possible.
+
+
+
+
+
+
+
+ Follow steps 1 through 4 for [local setups](#deploying-locally)
+
+
+
+
+
+
+ If you do not already have one, create a [fly.io account](https://fly.io/app/sign-up).
+
+ Go through the [onboarding process](https://fly.io/dashboard/personal/new-launch?onboarding=1), including downloading the [Fly CLI tool](https://fly.io/docs/flyctl/install/)
+
+
+ 
+
+
+
+
+
+
+
+
+ Log into your Fly account through the CLI:
+ ```sh login
+ fly auth login
+ ```
+
+ Deploy your project with the Fly CLI
+ ```sh deploy_to_fly
+ fly launch --copy-config
+ fly secrets import < .env
+ ```
+
+
+
+
+
+
+
+ - Go to your [Fly.io Dashboard](https://fly.io/dashboard) and select your project.
+ - It may take a few minutes for the project to spin up. Once it's ready, click on the project's link to view your Grafana Dashboard.
+ - When prompted, enter the username admin and the password you created earlier.
+
+
+
+
+
diff --git a/apps/docs/content/guides/platform/sentry-monitoring.mdx b/apps/docs/content/guides/monitoring-troubleshooting/sentry-monitoring.mdx
similarity index 96%
rename from apps/docs/content/guides/platform/sentry-monitoring.mdx
rename to apps/docs/content/guides/monitoring-troubleshooting/sentry-monitoring.mdx
index 1f0673908cf..567955f92c1 100644
--- a/apps/docs/content/guides/platform/sentry-monitoring.mdx
+++ b/apps/docs/content/guides/monitoring-troubleshooting/sentry-monitoring.mdx
@@ -174,10 +174,9 @@ Sentry.init({
breadcrumbs: true,
errors: true,
}),
- Sentry.nativeNodeFetchIntegration({
- ignoreOutgoingRequests: (url) => {
- console.log('server', `${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`, url)
- return url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`)
+ Sentry.browserTracingIntegration({
+ shouldCreateSpanForRequest: (url) => {
+ return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`)
},
}),
],
@@ -207,10 +206,10 @@ Sentry.init({
breadcrumbs: true,
errors: true,
}),
- Sentry.winterCGFetchIntegration({
+ Sentry.nativeNodeFetchIntegration({
breadcrumbs: true,
- shouldCreateSpanForRequest: (url) => {
- return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`)
+ ignoreOutgoingRequests: (url) => {
+ return url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`)
},
}),
],
@@ -229,17 +228,17 @@ Sentry.init({
```js sentry.edge.config.ts
import * as Sentry from '@sentry/nextjs'
import { SupabaseClient } from '@supabase/supabase-js'
-import { SupabaseIntegration } from '@supabase/sentry-js-integration'
+import { supabaseIntegration } from '@supabase/sentry-js-integration'
Sentry.init({
dsn: SENTRY_DSN,
integrations: [
- new SupabaseIntegration(SupabaseClient, {
+ supabaseIntegration(SupabaseClient, Sentry, {
tracing: true,
breadcrumbs: true,
errors: true,
}),
- new Sentry.Integrations.WinterCGFetch({
+ Sentry.winterCGFetchIntegration({
breadcrumbs: true,
shouldCreateSpanForRequest: (url) => {
return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`)
diff --git a/apps/docs/content/guides/platform/troubleshooting.mdx b/apps/docs/content/guides/monitoring-troubleshooting/troubleshooting.mdx
similarity index 100%
rename from apps/docs/content/guides/platform/troubleshooting.mdx
rename to apps/docs/content/guides/monitoring-troubleshooting/troubleshooting.mdx
diff --git a/apps/docs/content/guides/platform/access-control.mdx b/apps/docs/content/guides/platform/access-control.mdx
index 02c5acca2a8..20c604ec8b7 100644
--- a/apps/docs/content/guides/platform/access-control.mdx
+++ b/apps/docs/content/guides/platform/access-control.mdx
@@ -1,85 +1,264 @@
---
title: 'Access Control'
-description: 'Roles and permissions at the organization level'
+description: 'Roles and permissions at the organization and project levels'
---
-Supabase provides granular access controls to manage permissions across your organizations.
-For each organization, a member can have one of the following roles:
+Supabase provides granular access controls to manage permissions across your organizations and projects.
-- Owner
-- Administrator
-- Developer
+For each organization and project, a member can have one of the following roles:
+
+- **Owner**: full access to everything in organization and project resources.
+- **Administrator**: full access to everything in organization and project resources **except** updating organization settings, transferring projects outside of the organization, and adding new owners.
+- **Developer**: read-only access to organization resources and content access to project resources but cannot change any project settings.
+- **Read-Only**: read-only access to organization and project resources.
-Additional roles (Read-only and Billing) are available on the [team and enterprise plans](https://supabase.com/pricing).
+Read-Only role is only available on the [Team and Enterprise plans](https://supabase.com/pricing).
-A default organization is created when you first sign in, at which point you'll be assigned the **Owner** role. Keep in mind that members can access all projects within the organization and that project level access control is not available at this time. Create a separate organization if you need to restrict access to certain projects.
+When you first create an account, a default organization is created for you and you'll be assigned as the **Owner**. Any organizations you create will assign you as **Owner** as well.
-## Manage team members
+## Manage organization members
-To invite others to collaborate, visit your organization's team [settings](/dashboard/org/_/team) to send an invite link to another user's email. The invite expires after 24 hours.
+To invite others to collaborate, visit your organization's team [settings](/dashboard/org/_/team) to send an invite link to another user's email. The invite is valid for 24 hours. For project scoped roles, you may only assign a role to a single project for the user when sending the invite. You can assign roles to multiple projects after the user accepts the invite.
-Invites sent from a SAML SSO account can only be accepted by another SAML SSO account from the same identity provider. This is a security measure to prevent accidental invites to accounts not managed by your enterprise's identity provider.
+Invites sent from a SAML SSO account can only be accepted by another SAML SSO account from the same identity provider.
+
+This is a security measure to prevent accidental invites to accounts not managed by your enterprise's identity provider.
### Transferring ownership of an organization
-Each Supabase organization can have one or more owners. If you no longer want be an owner of an organization, click **Leave team** in your organization's team [settings](/dashboard/org/_/team). You can only leave an organization if there is _at least one other owner_. If you are transferring ownership of your organization to someone else, you will need to invite the new member with the **Owner** role. You can leave the organization after they've accepted the invitation.
+Each Supabase organization must have at least one owner. If your organization has other owners then you can relinquish ownership and leave the organization by clicking **Leave team** in your organization's team [settings](/dashboard/org/_/team).
-### Permissions across roles [#permission-across-roles]
+Otherwise, you'll need to invite a user as **Owner**, and they need to accept the invitation, or promote an existing organization member to **Owner** before you can leave the organization.
-The table below shows the corresponding permissions for each available role you can assign a team member in the Dashboard.
+### Organization Scoped Roles vs Project Scoped Roles
-| Permissions | Owner | Administrator | Developer | Read only [^2] |
-| ------------------------ | ----------------------- | ----------------------- | ----------------------- | ----------------------------------------------- |
-| **Organization** |
-| Change organization name | | | |
-| Delete organization | | | |
-| **Members** |
-| Add an Owner | | | |
-| Remove an Owner | | | |
-| Add an Administrator | | | |
-| Remove an Administrator | | | |
-| Add a Developer | | | |
-| Remove a Developer | | | |
-| Revoke an invite | | | |
-| Resend an invite | | | |
-| Accept an invite[^1] | | | |
-| **Billing** |
-| Read invoices | | | |
-| Read billing email | | | |
-| Change billing email | | | |
-| View subscription | | | |
-| Update subscription | | | |
-| Read billing address | | | |
-| Update billing address | | | |
-| Read tax codes | | | |
-| Update tax codes | | | |
-| Read payment methods | | | |
-| Update payment methods | | | |
-| **Projects** |
-| Create a project | | | |
-| Delete a project | | | |
-| Update a project | | | |
-| Pause a project | | | |
-| Resume a project | | | |
-| Restart a project | | | |
-| Manage tables | | | |
-| View Data | | | | [^3] |
+
-[^1]:
- Invites sent from a SSO account can only be accepted by another SSO account
- coming from the same identity provider. This is a security measure that
- prevents accidental invites to accounts not managed by your company's
- enterprise systems.
+Project scoped roles are only available on the [Enterprise Plan](https://supabase.com/pricing)
-[^2]: Available on the Teams and Enterprise Plans.
-[^3]:
- Only available on projects using PostgreSQL 14 and above. You can upgrade
- your project through [infrastructure settings](https://supabase.com/dashboard/project/_/settings/infrastructure).
+
+
+Each member in the organization can be assigned a role scoped to the organization or to specific projects. If the member has a role at the organization level, they will have the equivalent permissions for that role across all current and future projects in the organization.
+
+With project scoped permissions, you can assign members to roles scoped to specific projects.
+
+### Organization permissions across roles
+
+The table below shows the actions each role can take on the resources belonging to the organization.
+
+| Resource | Action | Owner | Administrator | Developer | Read-Only[^1] |
+| ----------------------------------------------------------------------------------------------------------- | ---------- | :-------------------------------------: | :-------------------------------------: | :-------------------------------------: | :-------------------------------------: |
+| **Organization** | | | | | |
+| Organization Management | Update | | | | |
+| | Delete | | | | |
+| **Members** | | | | | |
+| Organization Members | List | | | | |
+| Owner | Add | | | | |
+| | Remove | | | | |
+| Administrator | Add | | | | |
+| | Remove | | | | |
+| Developer | Add | | | | |
+| | Remove | | | | |
+| Owner (Project-Scoped) | Add | | | | |
+| | Remove | | | | |
+| Administrator (Project-Scoped) | Add | | | | |
+| | Remove | | | | |
+| Developer (Project-Scoped) | Add | | | | |
+| | Remove | | | | |
+| Invite | Revoke | | | | |
+| | Resend | | | | |
+| | Accept[^2] | | | | |
+| **Billing** | | | | | |
+| Invoices | List | | | | |
+| Billing Email | View | | | | |
+| | Update | | | | |
+| Subscription | View | | | | |
+| | Update | | | | |
+| Billing Address | View | | | | |
+| | Update | | | | |
+| Tax Codes | View | | | | |
+| | Update | | | | |
+| Payment Methods | View | | | | |
+| | Update | | | | |
+| Usage | View | | | | |
+| **Integrations (Org Settings)** | | | | | |
+| Authorize GitHub | - | | | | |
+| Add GitHub Repositories | - | | | | |
+| GitHub Connections | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| Vercel Connections | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| **OAuth Apps** | | | | | |
+| OAuth Apps | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | List | | | | |
+| **Audit Logs** | | | | | |
+| View Audit logs | - | | | | |
+| **Legal Documents** | | | | | |
+| SOC2 Type 2 Report | Download | | | | |
+| Security Questionnaire | Download | | | | |
+
+### Project permissions across roles
+
+The table below shows the actions each role can take on the resources belonging to the project.
+
+| Resource | Action | Owner | Admin | Developer | Read-Only [^3] |
+| ------------------------------------------------------------------------------------------------------ | ---------------------- | :-------------------------------------: | :-------------------------------------: | :-------------------------------------: | :-------------------------------------------------------------: |
+| **Project** | | | | | |
+| Project Management | Transfer | | | | |
+| | Create | | | | |
+| | Delete | | | | |
+| | Update (Name) | | | | |
+| | Pause | | | | |
+| | Restore | | | | |
+| | Restart | | | | |
+| Custom Domains | View | | | | |
+| | Update | | | | |
+| Data (Database) | View | | | | |
+| | Manage | | | | |
+| **Infrastructure** | | | | | |
+| Read Replicas | List | | | | |
+| | Create | | | | |
+| | Delete | | | | |
+| Addons | Update | | | | |
+| **Integrations** | | | | | |
+| Authorize GitHub | - | | | | |
+| Add GitHub Repositories | - | | | | |
+| GitHub Connections | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| Vercel Connections | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| **Database Configuration** | | | | | |
+| Reset Password | - | | | | |
+| Pooling Settings | View | | | | |
+| | Update | | | | |
+| SSL Configuration | View | | | | |
+| | Update | | | | |
+| Disk Size Configuration | View | | | | |
+| | Update | | | | |
+| Network Restrictions | View | | | | |
+| | Create | | | | |
+| | Delete | | | | |
+| Network Bans | View | | | | |
+| | Unban | | | | |
+| **API Configuration** | | | | | |
+| API Keys | Read service key | | | | |
+| | Read anon key | | | | |
+| JWT Secret | View | | | | |
+| | Generate new | | | | |
+| API settings | View | | | | |
+| | Update | | | | |
+| **Auth Configuration** | | | | | |
+| Auth Settings | View | | | | |
+| | Update | | | | |
+| SMTP Settings | View | | | | |
+| | Update | | | | |
+| Advanced Settings | View | | | | |
+| | Update | | | | |
+| **Storage Configuration** | | | | | |
+| Upload Limit | View | | | | |
+| | Update | | | | |
+| S3 Access Keys | View | | | | |
+| | Create | | | | |
+| | Delete | | | | |
+| **Edge Functions Configuration** | | | | | |
+| Secrets | View | | | | [^4] |
+| | Create | | | | |
+| | Delete | | | | |
+| **SQL Editor** | | | | | |
+| Queries | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| | List | | | | |
+| | Run | | | | [^5] |
+| **Database** | | | | | |
+| Scheduled Backups | View | | | | |
+| | Download | | | | |
+| | Restore | | | | |
+| Physical backups (PITR) | View | | | | |
+| | Restore | | | | |
+| **Authentication** | | | | | |
+| Users | Create | | | | |
+| | Delete | | | | |
+| | List | | | | |
+| | Send OTP | | | | |
+| | Send password recovery | | | | |
+| | Send magic link | | | | |
+| | Remove MFA factors | | | | |
+| Providers | View | | | | |
+| | Update | | | | |
+| Rate Limits | View | | | | |
+| | Update | | | | |
+| Email Templates | View | | | | |
+| | Update | | | | |
+| URL Configuration | View | | | | |
+| | Update | | | | |
+| Hooks | View | | | | |
+| | Create | | | | |
+| | Delete | | | | |
+| **Storage** | | | | | |
+| Buckets | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| | List | | | | |
+| Files | Create (Upload) | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | List | | | | |
+| **Edge Functions** | | | | | |
+| Edge Functions | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| | List | | | | |
+| **Reports** | | | | | |
+| Custom Report | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| | List | | | | |
+| **Logs & Analytics** | | | | | |
+| Queries | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| | List | | | | |
+| | Run | | | | |
+| Events Collections | Create | | | | |
+| | Update | | | | |
+| | Delete | | | | |
+| | View | | | | |
+| | List | | | | |
+| Warehouse Access Tokens | Create | | | | |
+| | Revoke | | | | |
+| | List | | | | |
+| **Branching** | | | | | |
+| Enable branching | - | | | | |
+| Disable branching | - | | | | |
+| | Create | | | | |
+| | Delete | | | | |
+| | List | | | | |
+
+[^1]: Available on the Team and Enterprise Plans.
+[^2]: Invites sent from a SSO account can only be accepted by another SSO account coming from the same identity provider. This is a security measure that prevents accidental invites to accounts not managed by your company's enterprise systems.
+[^3]: Available on the Enterprise Plan.
+[^4]: Read-Only role is able to access secrets.
+[^5]: Limited to executing SELECT queries.
diff --git a/apps/docs/content/guides/platform/backups.mdx b/apps/docs/content/guides/platform/backups.mdx
index 76cf6333bd5..5f77fef5a5b 100644
--- a/apps/docs/content/guides/platform/backups.mdx
+++ b/apps/docs/content/guides/platform/backups.mdx
@@ -11,13 +11,19 @@ The Supabase team regularly monitors the status of backups. In case of any issue
+
+
+Once a project is deleted all associated data will be permanently removed, including any backups stored in S3. This action is irreversible and should be carefully considered before proceeding.
+
+
+
## Types of backups
Database backups can be categorized into two types: **logical** and **physical**. You can learn more about them [here](https://supabase.com/blog/postgresql-physical-logical-backups).
As a general rule of thumb, projects will either have logical or physical backups based on plan, database size, and add-ons:
-| Plan | Database Size (0-40GB) | [Database Size (>40GB)](#backup-process-for-large-databases) | [PITR](#point-in-time-recovery) | [Read Replicas](./read-replicas#prerequisites) |
+| Plan | Database Size (0-15GB) | [Database Size (>15GB)](#backup-process-for-large-databases) | [PITR](#point-in-time-recovery) | [Read Replicas](./read-replicas#prerequisites) |
| ---------- | ---------------------- | ------------------------------------------------------------ | ------------------------------- | ---------------------------------------------- |
| Pro | logical | physical | physical | physical |
| Team | logical | physical | physical | physical |
@@ -51,7 +57,7 @@ Database backups do not include objects stored via the Storage API, as the datab
## Daily backups
-All Pro and Enterprise plan Supabase projects are backed up automatically on a daily basis. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data if disaster hits at the most inopportune time. If a lower RPO is required, enabling Point-in-Time Recovery should be considered.
+All Pro, Team and Enterprise Plan Supabase projects are backed up automatically on a daily basis. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data if disaster hits at the most inopportune time. If a lower RPO is required, enabling Point-in-Time Recovery should be considered.
@@ -63,17 +69,17 @@ For security purposes, passwords for custom roles are not stored in daily backup
The PostgreSQL utility [pg_dumpall](https://www.postgresql.org/docs/current/app-pg-dumpall.html) is used to perform daily backups. An SQL file is generated, zipped up, and sent to our storage servers for safe keeping.
-You can access daily backups in the [Scheduled backups](https://supabase.com/dashboard/project/_/database/backups/scheduled) settings in the Dashboard. Pro plan projects can access the last 7 days' worth of daily backups while Enterprise plan projects can access up to 30 days' worth of daily backups. Users can restore their project to any one of the backups. If you wish to generate a logical backup on your own, you can do so through the [Supabase CLI](/docs/reference/cli/supabase-db-dump).
+You can access daily backups in the [Scheduled backups](https://supabase.com/dashboard/project/_/database/backups/scheduled) settings in the Dashboard. Pro Plan projects can access the last 7 days' worth of daily backups. Team Plan projects can access the last 14 days' worth of daily backups, while Enterprise Plan projects can access up to 30 days' worth of daily backups. Users can restore their project to any one of the backups. If you wish to generate a logical backup on your own, you can do so through the [Supabase CLI](/docs/reference/cli/supabase-db-dump).
#### Backup process for large databases
-Databases larger than 40GB[^1], if they're on a recent build[^2] of the Supabase platform, get automatically transitioned[^3] to use daily physical backups. Physical backups are a more performant backup mechanism that lowers the overhead and impact on the database being backed up, and also avoids holding locks on objects in your database for a long period of time. While restores are unaffected, the backups created using this method cannot be downloaded from the Backups section of the dashboard.
+Databases larger than 15GB[^1], if they're on a recent build[^2] of the Supabase platform, get automatically transitioned[^3] to use daily physical backups. Physical backups are a more performant backup mechanism that lowers the overhead and impact on the database being backed up, and also avoids holding locks on objects in your database for a long period of time. While restores are unaffected, the backups created using this method cannot be downloaded from the Backups section of the dashboard.
This class of physical backups only allows for recovery to a fixed time each day, similar to daily backups. You can upgrade to [PITR](#point-in-time-recovery) for access to more granular recovery options.
Once a database is transitioned to using physical backups, it continues to use physical backups, even if the database size falls back below the threshold for the transition.
-[^1]: The threshold for transitioning might be lowered in the future.
+[^1]: The threshold for transitioning will be slowly lowered over time. Eventually, all projects will be transitioned to using physical backups.
[^2]: Projects created or upgraded after the 14th of July 2022 are eligible.
[^3]: The transition to physical backups is handled transparently and does not require any user intervention. It involves a single restart of the database to pick up new configuration that can only be loaded at start; the expected downtime for the restart is a few seconds.
@@ -93,7 +99,7 @@ Point-in-Time Recovery (PITR) allows a project to be backed up at much shorter i
-This feature is available to all Enterprise plan projects. Pro and Team plan projects can enable PITR as an add-on.
+Pro, Team and Enterprise Plan projects can enable PITR as an add-on.
Projects interested in PITR will also need to use at least a Small compute add-on, in order to ensure smooth functioning.
@@ -128,3 +134,27 @@ A date and time picker will be provided upon pressing the `Start a restore` butt

After locking in the desired point in time to recover to, The Dashboard will then prompt for a review and confirmation before proceeding with the restoration. The project will be inaccessible following this. As such, do ensure to allot for downtime beforehand. This is dependent on the size of the database. The larger it is, the longer the downtime will be. Once the confirmation has been given, the latest physical backup available is downloaded to the project and the database is partially restored. WAL files generated after this physical backup up to the specified point-in-time are then downloaded. The underlying records of transactions in these files are replayed against the database to complete the restoration. The Dashboard will display a notification once the restoration completes.
+
+## Troubleshooting
+
+### Logical backups
+
+#### search_path issues
+
+During the `pg_restore` process, the `search_path` is set to an empty string for predictability, and security. Using unqualified references to functions or relations can cause restorations using logical backups to fail, as the database will not be able to locate the function or relation being referenced. This can happen even if the database functions without issues during normal operations, as the `search_path` is usually set to include several schemas during normal operations. Therefore, you should always use schema-qualified names within your SQL code.
+
+You can refer to [an example PR](https://github.com/supabase/supabase/pull/28393/files) on how to update SQL code to use schema-qualified names.
+
+#### Invalid check constraints
+
+Postgres requires that [check constraints](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS) be:
+
+1. immutable
+1. not reference table data other than the new or updated row being checked
+
+Violating these requirements can result in numerous failure scenarios, including during logical restorations.
+
+Common examples of check constraints that can result in such failures are:
+
+- validating against the current time, e.g. that the row being inserted references a future event
+- validating the contents of a row against the contents of another table
diff --git a/apps/docs/content/guides/platform/billing-faq.mdx b/apps/docs/content/guides/platform/billing-faq.mdx
index 4c29c53557b..09ddc3ef0b6 100644
--- a/apps/docs/content/guides/platform/billing-faq.mdx
+++ b/apps/docs/content/guides/platform/billing-faq.mdx
@@ -10,16 +10,16 @@ subtitle: 'This documentation covers frequently asked questions around subscript
#### What are organizations and projects?
The Supabase Platform has "organizations" and "projects". An organization may contain multiple projects. Each project is a dedicated Supabase instance with all of its sub-services including Storage, Auth, Functions and Realtime.
-Each organization only has a single subscription with a single plan (Free, Pro, Team or Enterprise). Project addons such as [Compute](https://supabase.com/docs/guides/platform/compute-add-ons), [Custom Domains](https://supabase.com/docs/guides/platform/custom-domains) and [PITR](https://supabase.com/docs/guides/platform/backups#point-in-time-recovery) are configured per project and are added to your organization subscription.
+Each organization only has a single subscription with a single Plan (Free, Pro, Team or Enterprise). Project addons such as [Compute](/docs/guides/platform/compute-add-ons), [IPv4](/docs/guides/platform/ipv4-address), [Log Drains](/docs/guides/platform/log-drains), [Advanced MFA](/docs/guides/auth/auth-mfa/phone), [Custom Domains](https://supabase.com/docs/guides/platform/custom-domains) and [PITR](https://supabase.com/docs/guides/platform/backups#point-in-time-recovery) are configured per project and are added to your organization subscription.
Read more on [how billing is organized](https://supabase.com/docs/guides/platform/org-based-billing#how-billing-is-organized).
#### How many free projects can I have?
-You are entitled to two active free projects. Paused projects do not count towards your quota. Note that within an organization, we count the free project limits from all members that are either Owner or Admin. If you’ve got another organization member with the Admin or Owner role that has already exhausted their free project quota, you won’t be able to launch another free project in that organization. You can create simply create another free plan organization or change the role of the affected member in your [organization’s team settings](https://supabase.com/dashboard/org/_/team).
+You are entitled to two active free projects. Paused projects do not count towards your quota. Note that within an organization, we count the free project limits from all members that are either Owner or Admin. If you’ve got another organization member with the Admin or Owner role that has already exhausted their free project quota, you won’t be able to launch another free project in that organization. You can create another Free Plan organization or change the role of the affected member in your [organization’s team settings](https://supabase.com/dashboard/org/_/team).
#### Can I mix free and paid projects in a single organization?
-The subscription plan is set on the organization level and it is not possible to mix paid and non-paid projects inside a single organization. However, you can have a paid and a free organization and make use of the [self-serve project transfers](https://supabase.com/docs/guides/platform/project-transfer) to organize your projects. All projects in an organization benefit from the subscription plan. If your organization is on the Pro plan, all projects within the organization benefit from no project pausing, automated backups and so on.
+The subscription plan is set on the organization level and it is not possible to mix paid and non-paid projects inside a single organization. However, you can have a paid and a free organization and make use of the [self-serve project transfers](https://supabase.com/docs/guides/platform/project-transfer) to organize your projects. All projects in an organization benefit from the subscription plan. If your organization is on the Pro Plan, all projects within the organization benefit from no project pausing, automated backups and so on.
#### Can I transfer my projects to another organization?
@@ -39,12 +39,12 @@ No, we do not charge for paused projects. Compute hours are only counted for act
#### How are multiple projects billed under a paid organization?
-We provide a dedicated server for every Supabase project. Each paid organization comes with $10 in Compute Credits to cover one project on the default instance size. Additional projects start at ~$10 a month (billed hourly). See how billing for compute works [here](https://supabase.com/docs/guides/platform/org-based-billing#usage-based-billing-for-compute).
+We provide a dedicated server for every Supabase project. Each paid organization comes with $10 in Compute Credits to cover one project on the default compute size. Additional projects start at ~$10 a month (billed hourly). See how billing for compute works [here](https://supabase.com/docs/guides/platform/org-based-billing#billing-for-compute-compute-hours).
-Running 3 projects in a Pro plan organization on the default micro instance:
+Running 3 projects in a Pro Plan organization on the default micro instance:
-- $25 Pro plan
-- $30 for 3 projects on the default compute instance size
+- $25 Pro Plan
+- $30 for 3 projects on the default compute size
- $10 Compute credits
⇒ $45 / month
@@ -54,25 +54,25 @@ Refer to our docs on [how billing works](https://supabase.com/docs/guides/platfo
Each Supabase project is a dedicated VM and Postgres database. By default, your instance runs on the Micro compute instance. You have the option to upgrade your compute size in your [Project settings](https://supabase.com/dashboard/project/_/settings/addons). See [Compute Add-ons](https://supabase.com/docs/guides/platform/compute-add-ons) for available options.
-When you change your compute size, there are no immediate upfront charges. Instead, you will be billed based on the compute runtime hours during your billing cycle reset.
+When you change your compute size, there are no immediate upfront charges. Instead, you will be billed based on the compute hours during your billing cycle reset.
If you launch additional instances on your paid plan, we will add the corresponding compute hours to your final invoice.
If you upgrade your project to a larger instance for 10 hours and then downgrade, you’ll only pay for the larger instance for the 10 hours of usage at the end of your billing cycle. You can see your current compute usage on your [organization’s usage page](https://supabase.com/dashboard/org/_/usage).
-Read more on [how billing for compute works](https://supabase.com/docs/guides/platform/org-based-billing#usage-based-billing-for-compute).
+Read more on [how billing for compute works](https://supabase.com/docs/guides/platform/org-based-billing#billing-for-compute-compute-hours).
#### What is unified egress and how is it billed?
-Unified egress refers to the total egress quota available to each organization. This quota can be utilized for various purposes such as Storage, Realtime, Auth, Functions, Supavisor and Database. Each plan includes a specific egress quota, and any additional usage beyond that quota is billed accordingly.
+Unified egress refers to the total egress quota available to each organization. This quota can be utilized for various purposes such as Storage, Realtime, Auth, Functions, Supavisor, Log Drains and Database. Each plan includes a specific egress quota, and any additional usage beyond that quota is billed accordingly.
-To learn more about unified egress and its details, please refer to the information provided [here](https://supabase.com/docs/guides/platform/org-based-billing#:~:text=running%20any%20projects.-,Unified%20egress,-%23).
+To learn more about unified egress and its details, please refer to the information provided [here](/docs/guides/platform/org-based-billing#unified-egress).
## Plans and subscriptions
#### How do I change my subscription plan?
-Change your subscription plan in your [organization's billing settings](https://supabase.com/dashboard/org/_/billing). To upgrade to an Enterprise plan, complete the [Enterprise request form](https://forms.supabase.com/enterprise).
+Change your subscription plan in your [organization's billing settings](https://supabase.com/dashboard/org/_/billing). To upgrade to an Enterprise Plan, complete the [Enterprise request form](https://forms.supabase.com/enterprise).
#### What happens if I cancel my subscription?
@@ -84,17 +84,47 @@ We can transfer the amount as credits to another organization of your choice. Yo
## Quotas and spend caps
-#### What will happen when I exceed the free plan quota?
+#### What will happen when I exceed the Free Plan quota?
-When you exceed the Free plan quota, we will monitor and notify you about the exceeding limits. It is important to take action at this point. If you continue to exceed the limits without reducing your usage, we will pause your projects. To avoid project pausing, you have two options: reduce your usage or upgrade to a paid plan.
+You will be notified when you exceed the Free Plan quota. It is important to take action at this point. If you continue to exceed the limits without reducing your usage, service restrictions will apply. To avoid service restrictions, you have two options: reduce your usage or upgrade to a paid plan. Learn more about restrictions in the [Fair Use Policy](#fair-use-policy) section.
-#### What will happen when I exceed the Pro plan quota and have the spend cap on?
+#### What will happen when I exceed the Pro Plan quota and have the spend cap on?
-When you exceed the Pro plan quota, we will monitor and notify you about the exceeding limits. To unblock yourself, you can simply toggle off your spend cap in your [organization’s billing settings](https://supabase.com/dashboard/org/_/billing) to pay for over-usage beyond the Pro plans limits. If you continue to exceed the limits without reducing your usage or turning off the spend cap, we will pause your projects.
+You will be notified when you exceed your Pro Plan quota. To unblock yourself, you can toggle off your spend cap in your [organization’s billing settings](https://supabase.com/dashboard/org/_/billing) to pay for over-usage beyond the Pro plans limits. If you continue to exceed the limits without reducing your usage or turning off the spend cap, restrictions will apply. Learn more about restrictions in the [Fair Use Policy](#fair-use-policy) section.
-#### How do I scale beyond the limits of my Pro plan?
+#### How do I scale beyond the limits of my Pro Plan?
-The Pro plan has a spend cap enabled by default to keep costs under control. If you want to scale beyond the plans included quota, ~~simply~~ switch off the spend cap to pay for additional usage beyond the plans included limits. You can toggle the spend cap in the [organization's billing settings](https://supabase.com/dashboard/org/_/billing). Read more about the [spend cap](https://supabase.com/docs/guides/platform/spend-cap).
+The Pro Plan has a spend cap enabled by default to keep costs under control. If you want to scale beyond the plan's included quota, switch off the spend cap to pay for additional usage beyond the plans included limits. You can toggle the spend cap in the [organization's billing settings](https://supabase.com/dashboard/org/_/billing). Read more about the [spend cap](https://supabase.com/docs/guides/platform/spend-cap).
+
+## Fair Use Policy
+
+#### What is the Fair Use Policy?
+
+Our Fair Use Policy gives developers the freedom to build and experiment with Supabase, while protecting our infrastructure. Under the Fair Use policy, service restrictions may apply to your organization if:
+
+- You continually exceed the Free Plan quota
+- You continually exceed Pro Plan quota and have the spend cap enabled
+- You have overdue invoices
+- You have an expired credit card
+
+You will receive a notification before Fair Use Policy restrictions are applied. However, in some cases, like suspected abuse of our services, restrictions may be applied without prior notice.
+
+#### How is the Fair Use Policy applied?
+
+The Fair Use Policy is applied through service restrictions. This could mean:
+
+- Pausing projects
+- Switching databases to read-only mode
+- Disabling new project launches/transfers
+- Responding with a [402 status code](/docs/guides/platform/http-status-codes#402-service-restriction) for all API requests
+
+The Fair Use Policy is generally applied to all projects of the restricted organization.
+
+#### How can I remove restrictions applied from the Fair Use Policy?
+
+To remove restrictions, you will need to address the issue that caused the restriction. This could be reducing your usage, paying overdue invoices, updating your payment method, or any other issue that caused the restriction. Once the issue is resolved, the restriction will be lifted.
+
+Restrictions due to usage limits are lifted with the next billing cycle as your quota refills at the beginning of each cycle. You can see when your current billing cycle ends on the [billing page](https://supabase.com/dashboard/org/_/usage) under "Billing Breakdown". If your organization is on the Free Plan, you can also lift restrictions immediately by [upgrading](https://supabase.com/dashboard/org/_/billing?panel=subscriptionPlan) to Pro.
## Reports and invoices
@@ -144,7 +174,7 @@ Please note that any changes made to your billing details will only be reflected
#### What happens if I am unable to make the payment?
-When an invoice becomes overdue, we will pause your projects and downgrade your organization to the Free plan. You will be able to restore your projects once you have paid all outstanding invoices.
+When an invoice becomes overdue, we will pause your projects and downgrade your organization to the Free Plan. You will be able to restore your projects once you have paid all outstanding invoices.
#### Why am I overdue?
@@ -152,10 +182,14 @@ We were unable to charge your payment method. This likely means that the payment
You can be overdue when
- A card is expired
-- The bank declined the payment.
-- There was no card on record - No payment method was added.
- To rectify, you can add a new payment method by navigating to your [organization’s billing page](https://supabase.com/dashboard/org/_/billing).
- If you are facing any issues, please raise a [support ticket](https://supabase.help).
+- The bank declined the payment
+- You had insufficient funds
+- There was no card on record
+
+Check your payment methods in your [organization’s billing page](https://supabase.com/dashboard/org/_/billing) to ensure there are no expired payment methods and the correct payment method is marked as default.
+If you are still facing issues, please raise a [support ticket](https://supabase.help).
+
+Payments are always in USD and may show up as coming from Singapore, given our payment entity is in Singapore. Make sure you allow payments from Singapore and in USD
#### Can I delay my payment?
diff --git a/apps/docs/content/guides/platform/compute-add-ons.mdx b/apps/docs/content/guides/platform/compute-add-ons.mdx
index a8a2e3ad4e6..2210cc7ae95 100644
--- a/apps/docs/content/guides/platform/compute-add-ons.mdx
+++ b/apps/docs/content/guides/platform/compute-add-ons.mdx
@@ -4,41 +4,41 @@ title: 'Compute Add-ons'
description: "Learn about your project's instance and additional add-ons."
---
-Every project on the Supabase Platform comes with its own dedicated Postgres instance running inside a virtual machine (VM). The following table describes the base instance with additional compute add-ons available if you need extra performance when scaling up Supabase.
+Every project on the Supabase Platform comes with its own dedicated Postgres instance running inside a virtual machine (VM).
-
+The following table describes the base instances, Nano (free projects) and Micro (paying projects), with additional compute add-ons available if you need extra performance when scaling up Supabase.
-Free plan resources subject to changes.
+
+
+If you upgraded your Supabase organization from the Free plan to a paid plan, any projects created on the Free plan will remain on Nano instances due to the upgrade process [incurring downtime](/docs/guides/platform/compute-add-ons#upgrade-downtime).
+
+Since every project on a paid plan is already paying for a Micro instance you can upgrade to Micro at no additional charge. See [Supabase Pricing](https://supabase.com/pricing) for more information.
+
+We recommend that you take advantage of the upgrade when your usage requires Micro resources because the upgrade incurs downtime.
-| Plan | Hourly Price USD | Monthly Price USD | CPU | Memory | Connections: Direct | Connections: Pooler | Recommended Maximum DB Size |
-| ----------- | ---------------- | ----------------- | ----------------------- | ------------ | ------------------- | ------------------- | --------------------------- |
-| Nano (free) | $0 | $0 | Shared | Up to 0.5 GB | 60 | 200 | 500MB |
-| Micro | $0.01344 | ~$10 | 2-core ARM (shared) | 1 GB | 60 | 200 | 10GB |
-| Small | $0.0206 | ~$15 | 2-core ARM (shared) | 2 GB | 90 | 400 | 50GB |
-| Medium | $0.0822 | ~$60 | 2-core ARM (shared) | 4 GB | 120 | 600 | 100GB |
-| Large | $0.1517 | ~$110 | 2-core ARM (dedicated) | 8 GB | 160 | 800 | 200GB |
-| XL | $0.2877 | ~$210 | 4-core ARM (dedicated) | 16 GB | 240 | 1,000 | 500GB |
-| 2XL | $0.562 | ~$410 | 8-core ARM (dedicated) | 32 GB | 380 | 1,500 | 1TB |
-| 4XL | $1.32 | ~$960 | 16-core ARM (dedicated) | 64 GB | 480 | 3,000 | 2TB |
-| 8XL | $2.562 | ~$1,870 | 32-core ARM (dedicated) | 128 GB | 490 | 6,000 | 4TB |
-| 12XL | $3.836 | ~$2,800 | 48-core ARM (dedicated) | 192 GB | 500 | 9,000 | 6TB |
-| 16XL | $5.12 | ~$3,730 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 | 10TB |
+| Compute Size | Hourly Price USD | Monthly Price USD | CPU | Memory | Database Max Connections[^1] | Connection Pooler Max Clients | Max DB Size (Recommended)[^2] |
+| ------------ | ---------------- | ----------------- | ----------------------- | ------------ | ---------------------------- | ----------------------------- | ----------------------------- |
+| Nano[^3] | $0 | $0 | Shared | Up to 0.5 GB | 60 | 200 | 500MB |
+| Micro | $0.01344 | ~$10 | 2-core ARM (shared) | 1 GB | 60 | 200 | 10GB |
+| Small | $0.0206 | ~$15 | 2-core ARM (shared) | 2 GB | 90 | 400 | 50GB |
+| Medium | $0.0822 | ~$60 | 2-core ARM (shared) | 4 GB | 120 | 600 | 100GB |
+| Large | $0.1517 | ~$110 | 2-core ARM (dedicated) | 8 GB | 160 | 800 | 200GB |
+| XL | $0.2877 | ~$210 | 4-core ARM (dedicated) | 16 GB | 240 | 1,000 | 500GB |
+| 2XL | $0.562 | ~$410 | 8-core ARM (dedicated) | 32 GB | 380 | 1,500 | 1TB |
+| 4XL | $1.32 | ~$960 | 16-core ARM (dedicated) | 64 GB | 480 | 3,000 | 2TB |
+| 8XL | $2.562 | ~$1,870 | 32-core ARM (dedicated) | 128 GB | 490 | 6,000 | 4TB |
+| 12XL | $3.836 | ~$2,800 | 48-core ARM (dedicated) | 192 GB | 500 | 9,000 | 6TB |
+| 16XL | $5.12 | ~$3,730 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 | 10TB |
-Number of connections above are recommended values.
+[^1]: Database max connections are recommended values and can be customized depending on your use case.
+[^2]: Database size for each compute instance is the default recommendation but the actual performance of your database has many contributing factors, including resources available to it and the size of the data contained within it. See the [shared responsibility model](https://supabase.com/docs/guides/platform/shared-responsibility-model) for more information.
+[^3]: Nano compute resources on the Free plan are subject to change.
-
+Compute sizes can be changed by first selecting your project in the dashboard [here](https://supabase.com/dashboard/project/_/settings/addons?panel=computeInstance) and the upgrade process will [incur downtime](/docs/guides/platform/compute-add-ons#upgrade-downtime).
-The database sizes recommended for each instance are intended purely as a guide and recommendation to ensure performance is optimal, these sizes are not enforced.
-
-The performance of your database is impacted by many factors, some of them being the resources available to it or the size of the data contained within it. See the [shared responsibility model](https://supabase.com/docs/guides/platform/shared-responsibility-model) for more information.
-
-
-
-We charge hourly for additional compute based on your usage. Read more about [usage-based billing for compute](/docs/guides/platform/org-based-billing#usage-based-billing-for-compute).
-
-Compute sizes can be changed in [the dashboard here by selecting your project](https://supabase.com/dashboard/project/_/settings/addons?panel=computeInstance). [Contact us](https://supabase.com/contact/enterprise) if you require a custom plan.
+We charge hourly for additional compute based on your usage. Read more about [usage-based billing for compute](/docs/guides/platform/org-based-billing#billing-for-compute-compute-hours).
## Dedicated vs. shared CPU
@@ -50,54 +50,118 @@ When considering compute upgrades, assess whether your bottlenecks are hardware-
## Disk IO
-SSD Disks are attached to your servers and the disk performance depends on the compute add-on of your instance. Disk IO refers to two metrics: throughput (Megabits per Second) and IOPS (Input/Output Operations per Second). Disk throughput is capped at 1,048 Mbps and IOPS is capped at 3,000 IOPS for all compute add-ons.
+SSD disks are attached to your servers, and their performance depends on the compute add-on of your instance as well as the attributes set for the disk.
-| Plan | Baseline Disk Throughput | Baseline IOPS |
-| ----------- | ------------------------ | ------------- |
-| Nano (free) | 43 Mbps | 250 IOPS |
-| Micro | 87 Mbps | 500 IOPS |
-| Small | 174 Mbps | 1,000 IOPS |
-| Medium | 347 Mbps | 2,000 IOPS |
-| Large | 630 Mbps | 3,000 IOPS |
-| XL | 1,048 Mbps | 3,000 IOPS |
-| 2XL | 1,048 Mbps | 3,000 IOPS |
-| 4XL | 1,048 Mbps | 3,000 IOPS |
-| 8XL | 1,048 Mbps | 3,000 IOPS |
-| 12XL | 1,048 Mbps | 3,000 IOPS |
-| 16XL | 1,048 Mbps | 3,000 IOPS |
+Disk I/O Metrics:
-[Contact us](https://supabase.com/contact/enterprise) if you require a plan with higher disk throughput and IOPS.
+- Throughput: Measured in Megabits per Second (Mbps), which indicates the rate of data transfer.
+- IOPS: Input/Output Operations per Second, which measures the number of read and write operations.
-### Bursting and disk IO budget
+Performance Limits:
-Smaller compute instances can burst up to 1,048 Mbps and 3,000 IOPS for 30 minutes in a day. Beyond that, the performance reverts to the baseline. For example, the micro plan can burst up to 1,048 Mbps for 30 minutes a day and reverts to the baseline performance of 87 Mbps. Your disk IO budget gets replenished throughout the day.
+Disk throughput is capped at 19,000 Mbps and IOPS is capped at 80,000 IOPS for all compute add-ons.
-If you need consistent disk performance, choose the XL or larger compute add-on which has the same baseline and maximum disk throughput and IOPS.
+| Compute add-on | Max Disk Throughput | Baseline Disk Throughput | Max IOPS | Baseline IOPS |
+| -------------- | ------------------- | ------------------------ | ----------- | ------------- |
+| Nano (free) | 2,085 Mbps | 43 Mbps | 11,800 IOPS | 250 IOPS |
+| Micro | 2,085 Mbps | 87 Mbps | 11,800 IOPS | 500 IOPS |
+| Small | 2,085 Mbps | 174 Mbps | 11,800 IOPS | 1,000 IOPS |
+| Medium | 2,085 Mbps | 347 Mbps | 11,800 IOPS | 2,000 IOPS |
+| Large | 4,750 Mbps | 630 Mbps | 20,000 IOPS | 3,600 IOPS |
+| XL | 4,750 Mbps | 1,188 Mbps | 20,000 IOPS | 6,000 IOPS |
+| 2XL | 4,750 Mbps | 2,375 Mbps | 20,000 IOPS | 12,000 IOPS |
+| 4XL | 4,750 Mbps | 4,750 Mbps | 20,000 IOPS | 20,000 IOPS |
+| 8XL | 9,500 Mbps | 9,500 Mbps | 40,000 IOPS | 40,000 IOPS |
+| 12XL | 14,250 Mbps | 14,250 Mbps | 50,000 IOPS | 50,000 IOPS |
+| 16XL | 19,000 Mbps | 19,000 Mbps | 80,000 IOPS | 80,000 IOPS |
+
+### Self-Serve Configurability
+
+You can now adjust storage type, IOPS, throughput and disk size parameters yourself through the Dashboard [here](https://supabase.com/dashboard/project/_/settings/database).
+
+
+
+
+
+Be aware that increasing IOPS or throughput incur additional charges.
+
+
+
+### Understanding I/O Balance and Bursting (Smaller add-ons)
+
+For smaller add-ons like Nano, Micro, and Small, you get I/O balance and burst capacity. These add-ons have limited base performance for IOPS and throughput. However, they come with burst credits that let you handle temporary spikes for 30 minutes in a day in demand by briefly exceeding their baseline performance. This means you can achieve higher performance for short bursts before reverting to your baseline limits.
+
+For example, the micro add-on can burst up to 2,085 Mbps for 30 minutes a day and reverts to the baseline performance of 87 Mbps. Your disk IO budget gets replenished throughout the day.
+
+### Dedicated I/O Resources (Larger add-ons)
+
+Larger add-ons, such as 4XL and above, come with dedicated I/O resources and don't rely on I/O balance or burst capacity. These add-ons are designed for sustained, high performance with specific IOPS and throughput limits. They provide consistent performance without needing to burst. If you hit your IOPS or throughput limit, throttling will occur, but you won't have burst credits to exceed these limits.
+
+### Storage Volume Types: gp3 vs. io2
+
+When selecting your storage volume, it’s essential to focus on the performance needs of your workload. Below is a comparison between the two main volume types we offer:
+
+**gp3 (General Purpose SSD) - Default Option**
+This is our standard, versatile option and offers great value for most use cases. It’s ideal for general workloads that require moderate IOPS and throughput, such as small to medium-sized databases, development environments, and everyday tasks.
+
+- Volume Size: Up to 16TB
+- IOPS: 3,000 (default) up to 16,000 (maximum)
+- Throughput: 125 MB/s (default) to 1,000 MB/s (maximum)
+
+**io2 (Provisioned IOPS SSD)**
+This option is designed for high-performance needs where low latency and very high IOPS are crucial. If you’re running large-scale databases or mission-critical applications, io2 provides the performance you need.
+
+- Volume Size: Up to 60TB
+- IOPS: Can scale up to 256,000 based on your disk attributes
+- Throughput: Scales with IOPS
+
+**Choosing the Right Type**
+Think about the performance your workload demands. For general, day-to-day operations, gp3 should be more than enough. If you need high throughput and IOPS for critical systems, io2 will provide the performance required.
+
+### Choosing the Right Compute Add-On for Consistent Disk Performance
+
+If you need consistent disk performance, choose the 4XL or larger compute add-on which has the same baseline and maximum disk throughput and IOPS.
If you're unsure of how much throughput or IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://supabase.com/dashboard/project/_/reports). If the `Disk IO % consumed` stat is more than 1%, it indicates that your workload has burst beyond the baseline IO throughput during the day. If this metric goes to 100%, the workload has used up all available disk IO budget and will revert to baseline performance. Projects that use any disk IO budget are good candidates for upgrading to a larger compute add-on with higher baseline throughput.
+
+
+Compute add-on changes will not cause you to lose your disk attributes.
+
+
+
+### Constraints
+
+- After **any** disk attribute change, there is a cooldown period of approximately six hours before you can make further adjustments. During this time, no changes are allowed. If you encounter throttling, you’ll need to wait until the cooldown period concludes before making additional modifications.
+- You can increase volume size but cannot decrease it.
+- The performance of EBS volumes is affected by the add-on size to which you are attached.
+
## Postgres replication slots and WAL senders
[Replication Slots](https://postgresqlco.nf/doc/en/param/max_replication_slots) and [WAL Senders](https://postgresqlco.nf/doc/en/param/max_wal_senders/) are used to enable [Postgres Replication](/docs/guides/database/replication).
-The maximum number of replication slots and WAL senders depends on your compute add-on plan, as follows:
+The maximum number of replication slots and WAL senders depends on your compute add-on size, as follows:
-| Plan | Max Replication Slots | Max WAL Senders |
-| ----------- | --------------------- | --------------- |
-| Nano (free) | 5 | 5 |
-| Micro | 5 | 5 |
-| Small | 5 | 5 |
-| Medium | 5 | 5 |
-| Large | 8 | 8 |
-| XL | 24 | 24 |
-| 2XL | 80 | 80 |
-| 4XL | 80 | 80 |
-| 8XL | 80 | 80 |
-| 12XL | 80 | 80 |
-| 16XL | 80 | 80 |
+| Compute add-on | Max Replication Slots | Max WAL Senders |
+| -------------- | --------------------- | --------------- |
+| Nano (free) | 5 | 5 |
+| Micro | 5 | 5 |
+| Small | 5 | 5 |
+| Medium | 5 | 5 |
+| Large | 8 | 8 |
+| XL | 24 | 24 |
+| 2XL | 80 | 80 |
+| 4XL | 80 | 80 |
+| 8XL | 80 | 80 |
+| 12XL | 80 | 80 |
+| 16XL | 80 | 80 |
As mentioned in the Postgres [documentation](https://postgresqlco.nf/doc/en/param/max_replication_slots/), setting `max_replication_slots` to a lower value than the current number of replication slots will prevent the server from starting. If you are downgrading your compute add-on, please ensure that you are using fewer slots than the maximum number of replication slots available for the new compute add-on.
+
+## Upgrade downtime
+
+Compute add-on changes are usually applied with less than 2 minutes of downtime, but can take longer depending on the Cloud Provider.
diff --git a/apps/docs/content/guides/platform/custom-domains.mdx b/apps/docs/content/guides/platform/custom-domains.mdx
index fd8c6ec2a63..03a998fb62d 100644
--- a/apps/docs/content/guides/platform/custom-domains.mdx
+++ b/apps/docs/content/guides/platform/custom-domains.mdx
@@ -68,16 +68,15 @@ In the CLI, run [`domains create`](/docs/reference/cli/supabase-domains-create)
supabase domains create --project-ref abcdefghijklmnopqrst --custom-hostname api.example.com
```
-Two TXT records are returned. For example:
+A single TXT records is returned. For example:
```text
[...]
Required outstanding validation records:
- _cf-custom-hostname.api.example.com TXT -> 46BBC14D-D50A-409C-8DB5-F862CF5BA660
- api.example.com TXT -> ca3-F1HvR9i938OgVwpCFwi1jTsbhe1hvT0Ic3efPY3Q
+ _acme-challenge.api.example.com. TXT -> ca3-F1HvR9i938OgVwpCFwi1jTsbhe1hvT0Ic3efPY3Q
```
-Add the records to your domains' DNS settings. Make sure to trim surrounding whitespace. Use a low TTL value so you can quickly change the records if you make a mistake.
+Add the record to your domains' DNS settings. Make sure to trim surrounding whitespace. Use a low TTL value so you can quickly change the records if you make a mistake.
Some DNS registrars automatically append your domain name to the DNS entries being created. As such, creating a DNS record for `api.example.com` might instead create a record for `api.example.com.example.com`. In such cases, please remove the domain name from the records you're creating; as an example, you would create a TXT record for `api`, instead of `api.example.com`.
@@ -86,8 +85,7 @@ Some DNS registrars automatically append your domain name to the DNS entries bei
Make sure you've configured all required DNS settings:
- CNAME for your custom domain pointing to the Supabase project domain.
-- TXT record for `_cf-custom-hostname.`.
-- TXT record for ``.
+- TXT record for `_acme-challenge.`.
Use the [`domains reverify`](/docs/reference/cli/supabase-domains-reverify) command to begin the verification process of your domain. You may need to run this command a few times because DNS records take a while to propagate.
@@ -157,7 +155,7 @@ To get started:
1. [Install](/docs/guides/resources/supabase-cli) the latest version of the Supabase CLI.
1. [Log in](/docs/guides/cli/local-development#log-in-to-the-supabase-cli) to your Supabase account using the CLI.
1. Ensure that you have [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project you'd like to set up a vanity subdomain for.
-1. Ensure that your organization is on a paid plan (Pro/Team/Enterprise plan) in the [Billing page of the Dashboard](https://supabase.com/dashboard/org/_/billing).
+1. Ensure that your organization is on a paid plan (Pro/Team/Enterprise Plan) in the [Billing page of the Dashboard](https://supabase.com/dashboard/org/_/billing).
### Configure a vanity subdomain
diff --git a/apps/docs/content/guides/platform/custom-postgres-config.mdx b/apps/docs/content/guides/platform/custom-postgres-config.mdx
deleted file mode 100644
index f9b77862f3f..00000000000
--- a/apps/docs/content/guides/platform/custom-postgres-config.mdx
+++ /dev/null
@@ -1,81 +0,0 @@
----
-id: 'custom-postgres-config'
-title: 'Custom Postgres Config'
-description: 'Configuring Postgres for your Supabase project.'
----
-
-Supabase projects come with a Postgres cluster that is pre-configured for optimal performance. The configuration is based on a diverse range of workloads as well as the compute add-ons being used in the project. You can override this configuration to better optimize the Postgres cluster for specific workloads.
-
-
-
-Custom Postgres Config gives you advanced control over your database. Using it to set values that are inappropriate for your workload or compute add-on could cause severe performance degradation or project instability.
-
-
-
-## Custom Postgres config
-
-While most Postgres parameters can be configured from [within SQL](https://www.postgresql.org/docs/current/config-setting.html#CONFIG-SETTING-SQL-COMMAND-INTERACTION), some parameters must either be set using a config file, or require superuser access. Custom Postgres Config allows you to configure such parameters.
-
-From the perspective of Postgres, config overrides will show up in the global configuration file. Role or database specific configuration could override them for some scenarios; please refer to the [Postgres docs](https://www.postgresql.org/docs/current/) on each parameter for additional details.
-
-### Supported parameters
-
-The following parameters are available for overrides:
-
-1. [effective_cache_size](https://postgresqlco.nf/doc/en/param/effective_cache_size/)
-1. [maintenance_work_mem](https://postgresqlco.nf/doc/en/param/maintenance_work_mem/)
-1. [max_connections](https://postgresqlco.nf/doc/en/param/max_connections/)
-1. [max_locks_per_transaction](https://postgresqlco.nf/doc/en/param/max_locks_per_transaction/)
-1. [max_parallel_maintenance_workers](https://postgresqlco.nf/doc/en/param/max_parallel_maintenance_workers/)
-1. [max_parallel_workers_per_gather](https://postgresqlco.nf/doc/en/param/max_parallel_workers_per_gather/)
-1. [max_parallel_workers](https://postgresqlco.nf/doc/en/param/max_parallel_workers/)
-1. [max_worker_processes](https://postgresqlco.nf/doc/en/param/max_worker_processes/)
-1. [session_replication_role](https://postgresqlco.nf/doc/en/param/session_replication_role/)
-1. [shared_buffers](https://postgresqlco.nf/doc/en/param/shared_buffers/)
-1. [statement_timeout](https://postgresqlco.nf/doc/en/param/statement_timeout/)
-1. [work_mem](https://postgresqlco.nf/doc/en/param/work_mem/)
-
-### Setting config using the CLI
-
-To get started:
-
-1. [Install](/docs/guides/resources/supabase-cli) the Supabase CLI 1.69.0+.
-1. [Log in](/docs/guides/cli/local-development#log-in-to-the-supabase-cli) to your Supabase account using the CLI.
-
-The `postgres config` command of the CLI can be used for setting configuration parameters:
-
-```bash
-$ supabase --experimental --project-ref postgres-config update --config max_parallel_workers=6 --config shared_buffers=250MB
-- Custom Postgres Config -
-Config |Value |
-shared_buffers |250MB |
-max_parallel_workers |6 |
-- End of Custom Postgres Config -
-```
-
-By default, the CLI will merge any provided config overrides with any existing ones. The `--replace-existing-overrides` flag can be used to instead force all existing overrides to be replaced with the ones being provided:
-
-```bash
-$ supabase --experimental --project-ref postgres-config update --config max_parallel_workers=3 --replace-existing-overrides
-
-- Custom Postgres Config -
-Config |Value |
-max_parallel_workers |3 |
-- End of Custom Postgres Config -
-```
-
-## Considerations
-
-1. The Postgres cluster will be restarted in order to change the configuration being used. This will cause momentary disruption to existing database connections; in most cases this should not take more than a few seconds.
-1. Custom Postgres Config will always override the default optimizations generated by Supabase. When changing compute add-ons, you should also review and update your custom Postgres Config to ensure they remain compatible and effective with the updated compute.
-
-## Pooler config
-
-You can also [customize some parameters](https://supabase.com/dashboard/project/_/settings/database) for the Connection Pooler:
-
-1. [Pooling Mode](https://supabase.github.io/supavisor/configuration/pool_modes/)
-1. [Default Pool Size](https://supabase.github.io/supavisor/configuration/users/)
-
-The default pool size, and the maximum number of clients allowed to connect concurrently is automatically optimized based on the compute add-on being used. At the moment, the Dashboard only reflects any custom configuration being used, and does not include the default optimized numbers used for your project.
-
-Custom Pooler Config may also require manual updates to any relevant overrides when changing compute add-ons.
diff --git a/apps/docs/content/guides/platform/database-size.mdx b/apps/docs/content/guides/platform/database-size.mdx
index af8594c6a6e..a455371e9ad 100644
--- a/apps/docs/content/guides/platform/database-size.mdx
+++ b/apps/docs/content/guides/platform/database-size.mdx
@@ -36,16 +36,16 @@ Depending on your billing plan, your database can go into read-only mode which c
Postgres does not immediately reclaim the physical space used by dead tuples (i.e., deleted rows) in the DB. They are marked as "removed" until a [vacuum operation](https://www.postgresql.org/docs/current/routine-vacuuming.html) is executed. As a result, deleting data from your database may not immediately reduce the reported disk usage. You can use the [Supabase CLI](https://supabase.com/docs/guides/cli/getting-started) `inspect db bloat` command to view all dead tuples in your database. Alternatively, you can run the [query](https://github.com/supabase/cli/blob/c9cce58025fded16b4c332747f819a44f45c3b83/internal/inspect/bloat/bloat.go#L17) found in the CLI's GitHub repo in the [SQL Editor](https://supabase.com/dashboard/project/_/sql/)
```bash
-# login to the CLI
+# Login to the CLI
npx supabase login
-# initlize a local supabase directory
+# Initialize a local supabase directory
npx supabase init
-# link a project
+# Link a project
npx supabase link
-# detect bloat
+# Detect bloat
npx supabase inspect db bloat --linked
```
@@ -57,7 +57,7 @@ vacuum full
;
-Vacuum operations can temporarily increase resource utilization, which may adversely impact the observed performance of your project until the maintenance is completed.
+Vacuum operations can temporarily increase resource utilization, which may adversely impact the observed performance of your project until the maintenance is completed. The [vacuum full](https://www.postgresql.org/docs/current/sql-vacuum.html) command will lock the table until the operation concludes.
@@ -75,7 +75,7 @@ Supabase uses network-attached storage to balance performance with scalability.
### Paid plan behavior
-Projects on the Pro plan and above have auto-scaling Disk Storage.
+Projects on the Pro Plan and above have auto-scaling Disk Storage.
Disk Storage expands automatically when the database reaches 90% of the disk size. The disk is expanded to be 50% larger (e.g., 8GB -> 12GB). Auto-scaling can only take place once every 6 hours. If within those 6 hours you reach 95% of the disk space, your project will enter read-only mode.
@@ -95,19 +95,13 @@ Due to provider restrictions, disk expansions can occur only once every six hour
-
+The maximum Disk Storage Size for the Pro/Team Plan is 60TB. If you need more than this, [contact us](https://forms.supabase.com/enterprise) to learn more about the Enterprise Plan.
-Disks don't automatically downsize during normal operation. They _will_ automatically "right-size" when a Supabase (or Postgres) update is applied. Their final size after the update is 1.2x the size of the database. For example, if your database is 100GB in size, and you have a 200GB disk, the size after a Supabase update will be 120GB.
-
-
-
-The maximum Disk Storage Size for the Pro plan is 16TB. If you need more than this, [contact us](https://forms.supabase.com/enterprise) to learn more about the Enterprise plan.
-
-### Free plan behavior
+### Free Plan behavior
Free Plan projects enter [read-only](#read-only-mode) mode when you exceed the 500MB limit. Once in read-only mode, you have several options:
-- [Upgrade to the Pro plan](https://supabase.com/dashboard/org/_/billing) to increase the limit to 8GB. [Disable the Spend Cap](https://app.supabase.com/org/_/billing?panel=costControl) if you want your Pro instance to auto-scale and expand beyond the 8GB database size limit.
+- [Upgrade to the Pro Plan](https://supabase.com/dashboard/org/_/billing) to increase the limit to 8GB. [Disable the Spend Cap](https://app.supabase.com/org/_/billing?panel=costControl) if you want your Pro instance to auto-scale and expand beyond the 8GB database size limit.
- [Disable read-only mode](#disabling-read-only-mode) and reduce your database size.
### Read-only mode
@@ -137,3 +131,9 @@ Once you have reclaimed space, you can run the following to disable [read-only](
```sql
set default_transaction_read_only = 'off';
```
+
+### Reducing disk size
+
+Disks don't automatically downsize during normal operation. Once you have [reduced your database space usage](/docs/guides/platform/database-size#database-space-management), they _will_ automatically "right-size" when a Supabase (or Postgres) update is applied. Their final size after the update is 1.2x the size of the database (min 8 GB). For example, if your database is 100GB in size, and you have a 200GB disk, the size after a Supabase update will be 120GB.
+
+Learn more about [upgrading your project](/docs/guides/platform/migrating-and-upgrading-projects#upgrade-your-project).
diff --git a/apps/docs/content/guides/platform/enterprise-billing.mdx b/apps/docs/content/guides/platform/enterprise-billing.mdx
deleted file mode 100644
index 4229af1d756..00000000000
--- a/apps/docs/content/guides/platform/enterprise-billing.mdx
+++ /dev/null
@@ -1,26 +0,0 @@
----
-id: 'enterprise-billing'
-title: 'Enterprise Billing'
-description: 'Multiple organizations using a single subscription for Enterprise customers'
-subtitle: 'Multiple organizations using a single subscription for Enterprise customers'
----
-
-Enterprise customers can use Account-based Billing to simplify billing for multiple organizations.
-
-This is for users considering or on Enterprise plan only.
-
-## Account-based billing
-
-By default, each organization has a subscription and a plan. For our Enterprise customers, we offer Account-based Billing, so multiple organizations can point to a single Enterprise subscription, further simplifying billing.
-
-
-
-With Account-based Billing, all organizations and projects within the subscription are recognized as Enterprise and your support tier or security addons propagate to all projects.
-
-## Monitoring Enterprise usage
-
-For the usage-billing, we’ll sum up the usage from all organizations and projects. You can still break down the usage per organization or per project by going to the [organization billing page](/dashboard/org/_/billing).
-
-## Upgrading to the Enterprise plan
-
-Account-based Billing is not self-serve. Please reach out to your customer success contact person to set this up.
diff --git a/apps/docs/content/guides/platform/fly-postgres.mdx b/apps/docs/content/guides/platform/fly-postgres.mdx
index 763ef0fbbb9..fceb1d8cf8e 100644
--- a/apps/docs/content/guides/platform/fly-postgres.mdx
+++ b/apps/docs/content/guides/platform/fly-postgres.mdx
@@ -56,7 +56,7 @@ Each Fly user is entitled to one free Fly Postgres database. To create additiona
Fly Postgres databases are priced exactly the same as projects launched via Supabase. For detailed pricing information, visit the [Supabase pricing page](https://supabase.com/pricing).
-The [usage page](https://supabase.com/dashboard/org/_/usage) details the usage of your Fly Postgres databases. The usage information is sent to Fly and the billing is added to your Fly invoice. Supabase adheres to the Fly billing cycles, which start on the 1st of every month. Any upgrades or downgrades in between are prorated and billed on the 1st of the next month. This can lead to delayed charges. For example, if you upgrade to the Pro plan in the middle of a month, you will be charged the pro-rated amount on the first of the next month.
+The [usage page](https://supabase.com/dashboard/org/_/usage) details the usage of your Fly Postgres databases. The usage information is sent to Fly and the billing is added to your Fly invoice. Supabase adheres to the Fly billing cycles, which start on the 1st of every month. Any upgrades or downgrades in between are prorated and billed on the 1st of the next month. This can lead to delayed charges. For example, if you upgrade to the Pro Plan in the middle of a month, you will be charged the pro-rated amount on the first of the next month.
## Limitations
diff --git a/apps/docs/content/guides/platform/ipv4-address.mdx b/apps/docs/content/guides/platform/ipv4-address.mdx
index 8301bacbf96..bae6317423f 100644
--- a/apps/docs/content/guides/platform/ipv4-address.mdx
+++ b/apps/docs/content/guides/platform/ipv4-address.mdx
@@ -4,43 +4,94 @@ description: 'Attach an IPv4 address to your database'
subtitle: 'Attach an IPv4 address to your database'
---
-The add-on is $4 per month - we're passing on the AWS costs to you. The add-on is available for all paid plans.
+The Supabase IPv4 add-on provides a dedicated IPv4 address for your Postgres database connection. It can be configured in the [Add-Ons Settings](/dashboard/project/_/settings/addons).
-## When is this add-on required?
+## Understanding IP addresses
-This add-on is not required if any of these conditions are met
+The Internet Protocol (IP) addresses devices on the internet. There are two main versions:
-- you are only connecting to the database via Supavisor, our connection pooler, or via any of our client libraries.
-- If you are connecting to the database directly and the network from which you are connecting to the database supports IPv6 addresses.
+- **IPv4**: The older version, with a limited address space.
+- **IPv6**: The newer version, offering a much larger address space and the future-proof option.
-This add-on is only required if you are connecting to the database directly from an IPv6 incompatible network.
+## When you need the IPv4 add-on:
-
+- When using the direct connection string in an IPv6-incompatible network instead of Supavisor or client libraries.
+- When you need a dedicated IP address for your direct connection string
-When you purchase this add-on, your project won't be migrated to IPv6. PGBouncer will still be removed for the project according to the timeline mentioned in [this discussion](https://github.com/orgs/supabase/discussions/17817);
+## Enabling the IPv4 add-on
-
+You can enable the IPv4 add-on in your project's [add-ons settings](/dashboard/project/_/settings/addons). Enabling the add-on may briefly disable your database during reconfiguration.
-## Enabling the add-on
+## Read replicas and IPv4 add-on
-You can enable the IPv4 add-on in your project [add-on settings](https://supabase.com/dashboard/project/_/settings/addons) page.
+When using the add-on, each database (including read replicas) receives an IPv4 address. Each replica adds to the total IPv4 cost.
-
- Note that there might be some downtime when toggling the add-on since some DNS clients might have
- cached the old DNS entry and it might take some time for the updated DNS entry to be picked up.
- Generally, this should be less than a minute.
-
+## Changes and updates
-## Read Replicas
+- While the IPv4 address generally remains the same, actions like pausing/unpausing the project or enabling/disabling the add-on can lead to a new IPv4 address.
-When the IPv4 add-on is enabled, an IPv4 address is provisioned for each database in your project.
+## Supabase and IPv6 compatibility
-By default, this is only the Primary database for your project. If [Read replicas](/docs/guides/platform/read-replicas) are being used, each replica also gets its own IPv4 address, with a corresponding $4 charge.
+By default, Supabase Postgres use IPv6 addresses. If your system doesn't support IPv6, you have the following options:
-## Caveats
+1. **Supavisor Connection Strings**: The Supavisor connection strings are IPv4-compatible alternatives to direct connections
+2. **Supabase Client Libraries**: These libraries are compatible with IPv4
+3. **Dedicated IPv4 Add-On (Pro Plans+)**: For a guaranteed IPv4 and static database address for the direct connection, enable this paid add-on.
-For regular use-cases like a restart or compute size changes, the IPv4 address remains unchanged.
-However, there are cases where maintaining the same IPv4 address isn't guaranteed. These include:
+### Checking your network IPv6 support
-- If the project gets paused and unpaused
-- If the add-on gets removed and added again
+Most services are IPv6 compatible, but some exceptions exist (listed below). To verify your personal network supports it, run this command on your server:
+
+```sh
+curl -6 https://ifconfig.co/ip
+```
+
+If it returns an IPv6 address then your system is compatible. An example IPv6 address might look like: `2a05:d014:1c06:5f0c:d7a9:8616:bee2:30df`.
+
+### Checking platforms for IPv6 support:
+
+The majority of services are IPv6 compatible. However, there are a few prominent ones that only accept IPv4 connections:
+
+- [Retool](https://retool.com/)
+- [Vercel](https://vercel.com/)
+- [GitHub Actions](https://docs.github.com/en/actions)
+- [Render](https://render.com/)
+
+## Finding your database's IP address
+
+Use an IP lookup website or this command (replace ``):
+
+```sh
+nslookup db..supabase.co
+```
+
+## Identifying your connections
+
+The pooler and direct connection strings can be found in the [database settings](https://supabase.com/dashboard/project/_/settings/database):
+
+#### Direct connection
+
+IPv6 unless IPv4 Add-On is enabled
+
+```sh
+# Example direct connection string
+postgresql://postgres:[YOUR-PASSWORD]@db.ajrbwkcuthywfihaarmflo.supabase.co:5432/postgres
+```
+
+#### Supavisor in transaction mode (port 6543)
+
+Always uses an IPv4 address
+
+```sh
+# Example transaction string
+postgresql://postgres.ajrbwkcuthywddfihrmflo:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres
+```
+
+#### Supavisor in session mode (port 5432)
+
+Always uses an IPv4 address
+
+```sh
+# Example session string
+postgresql://postgres.ajrbwkcuthywfddihrmflo:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres
+```
diff --git a/apps/docs/content/guides/platform/metrics.mdx b/apps/docs/content/guides/platform/metrics.mdx
deleted file mode 100644
index dece498959f..00000000000
--- a/apps/docs/content/guides/platform/metrics.mdx
+++ /dev/null
@@ -1,31 +0,0 @@
----
-id: 'metrics'
-title: 'Metrics'
-description: 'Observability for your Supabase project'
----
-
-In addition to the reports and charts built in to the Supabase dashboard, each project hosted on the Supabase platform comes with a [Prometheus](https://prometheus.io/)-compatible metrics endpoint, which can be used to gather insight into the health and status of your project.
-
-You can use this endpoint to ingest data into your own monitoring and alerting infrastructure, as long as it is capable of scraping Prometheus-compatible endpoints, in order to set up custom rules beyond those supported by the Supabase dashboard.
-
-
-
-The endpoint discussed in this article is in beta, and the metrics returned by it might evolve or be changed in the future to increase its utility.
-
-
-
-
-
-The endpoint discussed in this article is not available on self-hosted.
-
-
-
-## Accessing the metrics endpoint
-
-Your project's metrics endpoint is accessible at `https://.supabase.co/customer/v1/privileged/metrics`. Access to the endpoint is secured via HTTP Basic Auth; the username is `service_role`, while the password is the service role JWT available through the Supabase dashboard.
-
-```shell
-> curl https://.supabase.co/customer/v1/privileged/metrics --user 'service_role:'
-```
-
-Additionally, we [maintain a guide](https://github.com/supabase/supabase-grafana) on quickly setting up a scraping agent to work with Grafana Cloud.
diff --git a/apps/docs/content/guides/platform/migrating-and-upgrading-projects.mdx b/apps/docs/content/guides/platform/migrating-and-upgrading-projects.mdx
deleted file mode 100644
index 429a1f43dad..00000000000
--- a/apps/docs/content/guides/platform/migrating-and-upgrading-projects.mdx
+++ /dev/null
@@ -1,299 +0,0 @@
----
-id: 'migrating-and-upgrading-projects'
-title: 'Migrating and Upgrading Projects'
-description: 'Upgrade your project to the latest version of Supabase.'
-sidebar_label: 'Migrating and upgrading'
----
-
-Supabase ships fast and we endeavor to add all new features to existing projects wherever possible.
-In some cases, access to new features require upgrading or migrating your Supabase project.
-
-## Upgrade your project
-
-There are a few methods available to upgrade your project.
-
-### `pg_upgrade`
-
-
-
-This upgrade method is currently in Beta.
-
-
-
-pg_upgrade performs an in-place upgrade on your database. For projects larger than 1GB, pg_upgrade is generally faster than a pause+restore cycle, and the speed advantage grows with the size of the database.
-
-1. Plan for an appropriate downtime window, and ensure you have reviewed the [caveats](#caveats) section of this document before executing the upgrade.
-1. Use the "Upgrade project" button on the [Infrastructure](https://supabase.com/dashboard/project/_/settings/infrastructure) section of your dashboard.
-
-Additionally, if a pg_upgrade upgrade should fail, your original DB would be brought back up online and be able to service requests.
-
-As a rough rule of thumb, pg_upgrade operates at ~100mbps (when executing an upgrade on your data). Using the size of your database, you can use this metric to derive an approximate sense of the downtime window necessary for the upgrade. During this window, you should plan for your DB and associated services to be unavailable.
-
-### Pause + restore
-
-
-
-We recommend using the pg_upgrade method, as it is faster, and more reliable. Additionally, only Free-tier projects are eligible to use the Pause + Restore method.
-
-
-
-When you pause and restore a project, the restored database includes the latest features. This method _does_ include downtime, so be aware that your project will be inaccessible for a short period of time.
-
-1. On the [General Settings](https://supabase.com/dashboard/project/_/settings/general) page in the Dashboard, click **Pause project**. You will be redirected to the home screen as your project is pausing. This process can take several minutes.
-1. After your project is paused, click **Restore project**. The restoration can take several minutes depending on how much data your database has. You will receive an email once the restoration is complete.
-
-Note that a pause + restore upgrade involves tearing down your project's resources before bringing them back up again. If the restore process should fail, manual intervention from Supabase support will be required to bring your project back online.
-
-### Caveats
-
-Regardless of the upgrade method, a few caveats apply:
-
-#### Logical replication
-
-If you are using logical replication, the replication slots will not be preserved by the upgrade process. You will need to manually recreate them after the upgrade with the method `pg_create_logical_replication_slot`. Refer to the Postgres docs on [Replication Management Functions](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-REPLICATION) for more details about the method.
-
-#### Breaking changes
-
-Newer versions of services can break functionality or change the performance characteristics you rely on. If your project is eligible for an upgrade, you will be able to find your current service versions from within [the Supabase dashboard](https://supabase.com/dashboard/project/_/settings/infrastructure).
-
-Breaking changes are generally only present in major version upgrades of Postgres and PostgREST. You can find their respective release notes at:
-
-- [Postgres](https://www.postgresql.org/docs/release/)
-- [PostgREST](https://github.com/PostgREST/postgrest/releases)
-
-If you are upgrading from a significantly older version, you will need to consider the release notes for any intermediary releases as well.
-
-#### Time Limits
-
-Starting from 2024-06-13, when a project is paused, users then have a 90-day window to restore the project on the platform from within Supabase Studio.
-
-The 90-day window allows Supabase to introduce platform changes that may not be backwards compatible with older backups. Unlike active projects, static backups can't be updated to accommodate such changes.
-
-During the 90-day restore window a paused project can be restored to the platform with a single button click from [Studio's dashboard page](https://supabase.com/dashboard/projects).
-
-
-
-
-
-After the 90-day restore window, you can download your project's backup file from the project dashboard. This backup is compatible with [pg_restore](https://www.postgresql.org/docs/current/app-pgrestore.html). You can extract your data from it, or attempt to manually restore the project.
-
-
-
-
-
-If you upgrade to a paid plan while your project is paused, any expired one-click restore options are reenabled. Since the backup was taken outside the backwards compatibility window, it may fail to restore. If you have a problem restoring your backup after upgrading, contact [Support](/support).
-
-
-
-
-
-#### Disk sizing
-
-When upgrading, the Supabase platform will "right-size" your disk based on the current size of the database. For example, if your database is 100GB in size, and you have a 200GB disk, the upgrade will reduce the disk size to 120GB (1.2x the size of your database).
-
-#### Objects dependent on Postgres extensions
-
-pg_upgrade does not support upgrading of databases containing reg\* data types referencing system OIDs.
-If you have created any objects that depend on the following extensions, you will need to recreate them after the upgrade.
-
-#### Extensions
-
-pg_upgrade does not currently support upgrading of databases using extensions older than the following versions:
-
-- TimescaleDB 2.9.1
-- plv8 3.1.5
-
-To upgrade to a newer version of Postgres, you will need to drop the extensions before the upgrade, and recreate them after the upgrade.
-
-#### Authentication method changes - deprecating md5 in favor of scram-sha-256
-
-The md5 hashing method has [known weaknesses](https://en.wikipedia.org/wiki/MD5#Security) that make it unsuitable for cryptography.
-As such, we are deprecating md5 in favor of [scram-sha-256](https://www.postgresql.org/docs/current/auth-password.html), which is the default and most secure authentication method used in the latest Postgres versions.
-
-We automatically migrate Supabase-managed roles' passwords to scram-sha-256 during the upgrade process, but you will need to manually migrate the passwords of any custom roles you have created, else you won't be able to connect using them after the upgrade.
-
-To identify roles using the md5 hashing method and migrate their passwords, you can use the following SQL statements after the upgrade:
-
-```sql
--- List roles using md5 hashing method
-SELECT
- rolname
-FROM pg_authid
-WHERE rolcanlogin = true
- AND rolpassword LIKE 'md5%';
-
--- Migrate a role's password to scram-sha-256
-ALTER ROLE WITH PASSWORD '';
-```
-
-#### Database size reduction
-
-As part of the upgrade process, maintenance operations such as [vacuuming](https://www.postgresql.org/docs/current/routine-vacuuming.html#ROUTINE-VACUUMING) are also executed. This can result in a reduction in the reported database size.
-
-#### Post-upgrade validation
-
-Supabase performs extensive pre- and post-upgrade validations to ensure that the database has been correctly upgraded. However, you should plan for your own application-level validations, as there might be changes you might not have anticipated, and this should be budgeted for when planning your downtime window.
-
-## Migrate your project
-
-Migrating projects can be achieved using the Supabase CLI. This is particularly useful for older projects (e.g. to use a newer Postgres version).
-
-### Before you begin
-
-- Install [Postgres](https://www.postgresql.org/download/) so you can run `psql` and `pg_dump`.
-- Install the latest version of [Supabase CLI](/docs/guides/cli#installation).
-- Create a new [Supabase project](https://supabase.com/dashboard).
-- Install [Docker Desktop](https://www.docker.com) for your platform.
-- Set environment variables for the old project's database URL as `$OLD_DB_URL` and the new project's as `$NEW_DB_URL`.
- To find the database URL for a project, go to the project's dashboard page [Project Settings/Database](https://supabase.com/dashboard/project/_/settings/database) and look under `Connection string`. If your network provider supports IPv6, you can disable `Use connection pooling`. Otherwise, enable `Use connection pooling`. For the pooler mode, `Transaction` will work.
-
-### Backup your old database
-
-1. Run the following command from your terminal:
-
-```bash
-supabase db dump --db-url "$OLD_DB_URL" -f roles.sql --role-only
-supabase db dump --db-url "$OLD_DB_URL" -f schema.sql
-supabase db dump --db-url "$OLD_DB_URL" -f data.sql --use-copy --data-only
-```
-
-### Restore to your new project
-
-In your new project:
-
-1. Enable [Database Webhooks](https://supabase.com/dashboard/project/_/database/hooks) if you enabled them in your old project.
-2. Enable any [extensions](https://supabase.com/dashboard/project/_/database/extensions) that were enabled in your old project.
-
-If you use [column encryption](/docs/guides/database/column-encryption), first copy the root encryption key to your new project using your [Personal Access Token](https://supabase.com/dashboard/account/tokens).
-
-```bash
-export OLD_PROJECT_REF=""
-export NEW_PROJECT_REF=""
-export SUPABASE_ACCESS_TOKEN=""
-
-curl "https://api.supabase.com/v1/projects/$OLD_PROJECT_REF/pgsodium" \
- -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" |
-curl "https://api.supabase.com/v1/projects/$NEW_PROJECT_REF/pgsodium" \
- -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
- -X PUT --json @-
-```
-
-Then run the following command from your terminal:
-
-```bash
-psql \
- --single-transaction \
- --variable ON_ERROR_STOP=1 \
- --file roles.sql \
- --file schema.sql \
- --command 'SET session_replication_role = replica' \
- --file data.sql \
- --dbname "$NEW_DB_URL"
-```
-
-Setting the `session_replication_role` to `replica` disables all triggers so that columns are not double encrypted.
-
-Troubleshooting notes:
-
-- If you have created any [custom roles](https://supabase.com/dashboard/project/_/database/roles) with `login` attribute, you have to manually set their passwords in the new project.
-- If you run into any permission errors related to `supabase_admin` during restore, edit the `schema.sql` file and comment out any lines containing `ALTER ... OWNER TO "supabase_admin"`.
-
-### Preserving migration history
-
-If you were using Supabase CLI for managing migrations on your old database and would like to preserve the migration history in your newly restored project, you need to insert the migration records separately using the following commands.
-
-```bash
-supabase db dump --db-url "$OLD_DB_URL" -f history_schema.sql --schema supabase_migrations
-supabase db dump --db-url "$OLD_DB_URL" -f history_data.sql --use-copy --data-only --schema supabase_migrations
-psql \
- --single-transaction \
- --variable ON_ERROR_STOP=1 \
- --file history_schema.sql \
- --file history_data.sql \
- --dbname "$NEW_DB_URL"
-```
-
-### Schema changes to `auth` and `storage`
-
-If you have modified the `auth` and `storage` schemas in your old project, such as adding triggers or RLS policies, you have to restore them separately. The Supabase CLI can help you diff the changes to these schemas using the following commands.
-
-```bash
-supabase link --project-ref "$OLD_PROJECT_REF"
-supabase db diff --linked --schema auth,storage > changes.sql
-```
-
-### Enable publication on tables
-
-Replication for Realtime is disabled for all tables in your new project. On the [Publications](https://supabase.com/dashboard/project/_/database/publications) page in the Dashboard, select your new project and enable replication for tables that were enabled in your old project.
-
-### Migrate storage objects
-
-The new project has the old project's Storage buckets, but the Storage objects need to be migrated manually. Use this script to move storage objects from one project to another.
-
-```js
-// npm install @supabase/supabase-js@1
-const { createClient } = require('@supabase/supabase-js')
-
-const OLD_PROJECT_URL = 'https://xxx.supabase.co'
-const OLD_PROJECT_SERVICE_KEY = 'old-project-service-key-xxx'
-
-const NEW_PROJECT_URL = 'https://yyy.supabase.co'
-const NEW_PROJECT_SERVICE_KEY = 'new-project-service-key-yyy'
-
-;(async () => {
- const oldSupabaseRestClient = createClient(OLD_PROJECT_URL, OLD_PROJECT_SERVICE_KEY, {
- db: {
- schema: 'storage',
- },
- })
- const oldSupabaseClient = createClient(OLD_PROJECT_URL, OLD_PROJECT_SERVICE_KEY)
- const newSupabaseClient = createClient(NEW_PROJECT_URL, NEW_PROJECT_SERVICE_KEY)
-
- // make sure you update max_rows in postgrest settings if you have a lot of objects
- // or paginate here
- const { data: oldObjects, error } = await oldSupabaseRestClient.from('objects').select()
- if (error) {
- console.log('error getting objects from old bucket')
- throw error
- }
-
- for (const objectData of oldObjects) {
- console.log(`moving ${objectData.id}`)
- try {
- const { data, error: downloadObjectError } = await oldSupabaseClient.storage
- .from(objectData.bucket_id)
- .download(objectData.name)
- if (downloadObjectError) {
- throw downloadObjectError
- }
-
- const { _, error: uploadObjectError } = await newSupabaseClient.storage
- .from(objectData.bucket_id)
- .upload(objectData.name, data, {
- upsert: true,
- contentType: objectData.metadata.mimetype,
- cacheControl: objectData.metadata.cacheControl,
- })
- if (uploadObjectError) {
- throw uploadObjectError
- }
- } catch (err) {
- console.log('error moving ', objectData)
- console.log(err)
- }
- }
-})()
-```
-
-### Transfer to a different organization
-
-Note that project migration is for transferring your projects to different regions. If you need to move your project to a different organization without touching the infrastructure, see [project transfers](/docs/guides/platform/project-transfer).
diff --git a/apps/docs/content/guides/platform/migrating-to-supabase.mdx b/apps/docs/content/guides/platform/migrating-to-supabase.mdx
new file mode 100644
index 00000000000..494520af8af
--- /dev/null
+++ b/apps/docs/content/guides/platform/migrating-to-supabase.mdx
@@ -0,0 +1,19 @@
+---
+title: Migrating to Supabase
+---
+
+Learn how to migrate to Supabase from another database service.
+
+## Migration guides
+
+
+ {(migrationPages) => (
+
+ {migrationPages.map((page) => (
+
+
+
+ ))}
+
+ )}
+
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/amazon-rds.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/amazon-rds.mdx
similarity index 93%
rename from apps/docs/content/guides/resources/migrating-to-supabase/amazon-rds.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/amazon-rds.mdx
index 519e106616b..269c524be71 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/amazon-rds.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/amazon-rds.mdx
@@ -1,6 +1,7 @@
---
title: 'Migrate from Amazon RDS to Supabase'
-description: 'Migrate your Amazon RDS MySQL or MS SQL database to Supabase Postgres database.'
+description: 'Migrate your Amazon RDS MySQL or MS SQL database to Supabase.'
+subtitle: 'Migrate your Amazon RDS MySQL or MS SQL database to Supabase.'
---
This guide aims to exhibit the process of transferring your Amazon RDS database from any of these engines Postgres, MySQL or MS SQL to Supabase's Postgres database. Although Amazon RDS is a favored managed database service provided by AWS, it may not suffice for all use cases. Supabase, on the other hand, provides an excellent free and open source option that encompasses all the necessary backend features to develop a product: a Postgres database, authentication, instant APIs, edge functions, real-time subscriptions, and storage.
@@ -21,9 +22,7 @@ Supabase's core is Postgres, enabling the use of row-level security and providin
## Retrieve your Supabase host [#retrieve-supabase-host]
-1. If you're new to Supabase, [create a project](https://supabase.com/dashboard).
- Make a note of your password, you will need this later. If you forget it, you can [reset it here](https://supabase.com/dashboard/project/_/settings/database).
-
+1. If you're new to Supabase, [create a project](https://database.new). Make a note of your password, you will need this later. If you forget it, you can [reset it here](https://supabase.com/dashboard/project/_/settings/database).
1. Go to the [Database settings](https://supabase.com/dashboard/project/_/settings/database) for your project in the Supabase Dashboard.
1. Under **Connection Info**, note your Host (`$SUPABASE_HOST`).
diff --git a/apps/docs/content/guides/platform/migrating-to-supabase/auth0.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/auth0.mdx
new file mode 100644
index 00000000000..5c48d6ae374
--- /dev/null
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/auth0.mdx
@@ -0,0 +1,257 @@
+---
+title: 'Migrate from Auth0 to Supabase Auth'
+subtitle: 'Learn how to migrate your users from Auth0'
+breadcrumb: 'Migrations'
+---
+
+You can migrate your users from Auth0 to Supabase Auth.
+
+Changing authentication providers for a production app is an important operation. It can affect most aspects of your application. Prepare in advance by reading this guide, and develop a plan for handling the key migration steps and possible problems.
+
+With advance planning, a smooth and safe Auth migration is possible.
+
+## Before you begin
+
+Before beginning, consider the answers to the following questions. They will help you need decide if you need to migrate, and which strategy to use:
+
+- How do Auth provider costs scale as your user base grows?
+- Does the new Auth provider provide all needed features? (for example, OAuth, password logins, Security Assertion Markup Language (SAML), Multi-Factor Authentication (MFA))
+- Is downtime acceptable during the migration?
+- What is your timeline to migrate before terminating the old Auth provider?
+
+## Migration strategies
+
+Depending on your evaluation, you may choose to go with one of the following strategies:
+
+1. Rolling migration
+2. One-off migration
+
+| Strategy | Advantages | Disadvantages |
+| -------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Rolling |
0 downtime
Users may need to log in again
|
Need to maintain 2 different Auth services, which may be more costly in the short-term
Need to maintain separate codepaths for the period of the migration
Some existing users may be inactive and have not signed in with the new provider. This means that you eventually need to backfill these users. However, this is a much smaller-scale one-off migration with lower risks since these users are inactive.
|
+| One-off |
No need to maintain 2 different auth services for an extended period of time
|
Some downtime
Users will need to log in again. Risky for active users.
|
+
+## Migration steps
+
+Auth provider migrations require 2 main steps:
+
+1. Export your user data from the old provider (Auth0)
+1. Import the data into your new provider (Supabase Auth)
+
+### Step 1: Export your user data
+
+Auth0 provides two methods for exporting user data:
+
+1. Use the [Auth0 data export feature](https://auth0.com/docs/troubleshoot/customer-support/manage-subscriptions/export-data)
+1. Use the [Auth0 management API](https://auth0.com/docs/api/management/v2/users/get-users). This endpoint has a rate limit, so you may need to export your users in several batches.
+
+To export password hashes and MFA factors, contact Auth0 support.
+
+### Step 2: Import your users into Supabase Auth
+
+The steps for importing your users depends on the login methods that you support.
+
+See the following sections for how to import users with:
+
+- [Password-based login](#password-based-methods)
+- [Passwordless login](#passwordless-methods)
+- [OAuth](#oauth)
+
+#### Password-based methods
+
+For users who sign in with passwords, we recommend a hybrid approach to reduce downtime:
+
+1. For new users, use Supabase Auth for sign up.
+1. Migrate existing users in a one-off migration.
+
+##### Sign up new users
+
+Sign up new users using Supabase Auth's [signin methods](/docs/guides/auth/passwords#signing-up-with-an-email-and-password).
+
+##### Migrate existing users to Supabase Auth
+
+Migrate existing users to Supabase Auth. This requires two main steps: first, check which users need to be migrated, then create their accounts using the Supabase admin endpoints.
+
+1. Get your Auth 0 user export and password hash export lists.
+1. Filter for users who use password login.
+ - Under the `identities` field in the user object, these users will have `auth0` as a provider. In the same identity object, you can find their Auth0 `user_id`.
+ - Check that the user has a corresponding password hash by comapring their Auth0 `user_id` to the `oid` field in the password hash export.
+1. Use Supabase Auth's [admin create user](/docs/reference/javascript/auth-admin-createuser) method to recreate the user in Supabase Auth. If the user has a confirmed email address or phone number, set `email_confirm` or `phone_confirm` to `true`.
+
+ ```ts
+ const { data, error } = await supabase.auth.admin.createUser({
+ email: 'foo@example.com',
+ password_hash: '$2y$10$a9pghn27d7m0ltXvlX8LiOowy7XfFw0hW0G80OjKYQ1jaoejaA7NC',
+ email_confirm: true,
+ })
+ ```
+
+
+
+ Supabase supports BCrypt and Argon2 password hashes.
+
+
+
+ If you have a plaintext password instead of a hash, you can provide that instead. Supabase Auth will handle hashing the password for you. (Passwords are **always** stored hashed.)
+
+ ```ts
+ const { data, error } = await supabase.auth.admin.createUser({
+ email: 'foo@example.com',
+ password: 'supersecurepassword123!',
+ })
+ ```
+
+1. To sign in your migrated users, use the Supabase Auth [sign in methods](/docs/reference/javascript/auth-signinwithpassword).
+
+ To check for edge cases where users aren't successfully migrated, use a fallback strategy. This ensures that users can continue to sign in seamlessly:
+
+ 1. Try to sign in the user with Supabase Auth.
+ 1. If the signin fails, try to sign in with Auth0.
+ 1. If Auth0 signin succeeds, call the admin create user method again to create the user in Supabase Auth.
+
+#### Passwordless methods
+
+For passwordless signin via email or phone, check for users with verified email addresses or phone numbers. Create these users in Supabase Auth with `email_confirm` or `phone_confirm` set to `true`:
+
+```ts
+const { data, error } = await supabase.auth.admin.createUser({
+ email: 'foo@example.com',
+ email_confirm: true,
+})
+```
+
+Check your Supabase Auth [email configuration](/docs/guides/auth/auth-smtp) and configure your [email template](/dashboard/project/_/auth/templates) for use with magic links. See the [Email templates guide](/docs/guides/auth/auth-email-templates) to learn more.
+
+Once you have imported your users, you can sign them in using the [`signInWithOtp`](/docs/reference/javascript/auth-signinwithotp) method.
+
+#### OAuth
+
+Configure your OAuth providers in Supabase by following the [Social login guides](/docs/guides/auth/social-login).
+
+For both new and existing users, sign in the user using the [`signInWithOAuth`](https://supabase.com/docs/reference/javascript/auth-signinwithoauth) method. This works without pre-migrating existing users, since the user always needs to sign in through the OAuth provider before being redirected to your service.
+
+After the user has completed the OAuth flow successfully, you can check if the user is a new or existing user in Auth0 by mapping their social provider id to Auth0. Auth0 stores the social provider ID in the user ID, which has the format `provider_name|provider_id` (for example, `github|123456`). See the [Auth0 identity docs](https://auth0.com/docs/manage-users/user-accounts/identify-users) to learn more.
+
+## Mapping between Auth0 and Supabase Auth
+
+Each Auth provider has its own schema for tracking users and user information.
+
+In Supabase Auth, your users are stored in your project's database under the `auth` schema. Every user has an identity (unless the user is an anonymous user), which represents the signin method they can use with Supabase. This is represented by the `auth.users` and `auth.identities` table.
+
+See the [Users](/docs/guides/auth/users) and [Identities](/docs/guides/auth/identities) sections to learn more.
+
+### Mapping user metadata and custom claims
+
+Supabase Auth provides 2 fields which you can use to map user-specific metadata from Auth0:
+
+- `auth.users.raw_user_meta_data` : For storing non-sensitive user metadata that the user can update (e.g full name, age, favorite color).
+- `auth.users.raw_app_meta_data` : For storing non-sensitive user metadata that the user should not be able to update (e.g pricing plan, access control roles).
+
+Both columns are accessible from the admin user methods. To create a user with custom metadata, you can use the following method:
+
+```ts
+const { data, error } = await supabase.auth.admin.createUser({
+ email: 'foo@example.com',
+ user_metadata: {
+ full_name: 'Foo Bar',
+ },
+ app_metadata: {
+ role: 'admin',
+ },
+})
+```
+
+
+
+These fields will be exposed in the user's access token JWT so it is recommended not to store excessive metadata in these fields.
+
+
+
+These fields are stored as columns in the `auth.users` table using the `jsonb` type. Both fields can be updated by using the admin [`updateUserById` method](/docs/reference/javascript/auth-admin-updateuserbyid). If you want to allow the user to update their own `raw_user_meta_data` , you can use the [`updateUser` method](/docs/reference/javascript/auth-updateuser).
+
+If you have a lot of user-specific metadata to store, it is recommended to create your own table in a private schema that uses the user id as a foreign key:
+
+```sql
+create table private.user_metadata (
+ id int generated always as identity,
+ user_id uuid references auth.users(id) on delete cascade,
+ user_metadata jsonb
+);
+```
+
+## Frequently Asked Questions (FAQ)
+
+
+
+I have IDs assigned to existing users in my database, how can I maintain these IDs?}
+id="custom-user-id"
+>
+
+All users stored in Supabase Auth use the UUID V4 format as the ID. If your UUID format is identical, you can specify it in the admin create user method like this:
+
+
+
+New users in Supabase Auth will always be created with a UUID V4 ID by default.
+
+
+
+```ts
+// specify a custom id
+const { data, error } = await supabase.auth.admin.createUser({
+ id: 'e7f5ae65-376e-4d05-a18c-10a91295727a',
+ email: 'foo@example.com',
+})
+```
+
+
+
+How can I allow my users to retain their existing password?}
+id="existing-password"
+>
+
+Supabase Auth never stores passwords as plaintext. Since Supabase Auth supports reading BCrypt and argon2 password hashes, you can import your users passwords if they use the same hashing algorithm. New users in Supabase Auth who use password-based sign-in methods will always use a BCrypt hash. Passwords are stored in the `auth.users.encrypted_password` column.
+
+
+
+My users have multi-factor authentication (MFA) enabled, how do I make sure they don't have to set up MFA again?}
+id="mfa"
+>
+
+You can obtain an export of your users' MFA secrets by opening a support ticket with Auth0, similar to obtaining the export for password hashes. Supabase Auth only supports time-based one-time passwords (TOTP). Users who have TOTP-based factors may need to re-enroll using their choice of TOTP-based authenticator instead (e.g. 1Password / Google authenticator).
+
+
+
+How do I migrate existing SAML Single Sign-On (SSO) connections?}
+id="saml"
+>
+
+Customers may need to link their identity provider with Supabase Auth separately, but their users should still be able to sign-in as per-normal after authenticating with their identity provider. For more information about SSO with SAML 2.0, you can check out [this guide](/docs/guides/auth/enterprise-sso/auth-sso-saml). If you want to migrate your existing SAML SSO connections from Auth0 to Supabase Auth, please reach out to us via support.
+
+
+
+How do I migrate my Auth0 organizations to Supabase?}
+id="migrate-org"
+>
+
+This isn't supported by Supabase Auth yet.
+
+
+
+
+
+## Useful references
+
+- [Migrating 125k users from Auth0 to Supabase](https://kevcodez.medium.com/migrating-125-000-users-from-auth0-to-supabase-81c0568de307)
+- [Loper to Supabase migration](https://eigen.sh/posts/auth-migration)
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/firebase-auth.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/firebase-auth.mdx
similarity index 92%
rename from apps/docs/content/guides/resources/migrating-to-supabase/firebase-auth.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/firebase-auth.mdx
index 72302b38401..bf2687b7a10 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/firebase-auth.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/firebase-auth.mdx
@@ -1,14 +1,14 @@
---
id: 'firebase-auth'
-title: 'Firebase Auth Migration'
-description: 'Migrate Firebase auth users to a Supabase project.'
-sidebar_label: 'Firebase Auth'
+title: 'Migrate from Firebase Auth to Supabase'
+description: 'Migrate Firebase auth users to Supabase Auth.'
+subtitle: 'Migrate Firebase auth users to Supabase Auth.'
---
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/auth) to help migrate auth users from a Firebase project to a Supabase project. There are two parts to the migration process:
- `firestoreusers2json` ([TypeScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/firestoreusers2json.ts), [JavaScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/firestoreusers2json.js)) exports users from an existing Firebase project to a `.json` file on your local system.
-- `import_users` ([TypeScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/import_users.ts), [JavaScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/import_users.js)) imports users from a saved `.json` file into your Supabase project (inserting those users into the `auth.users` table of your `PostgreSQL` database instance).
+- `import_users` ([TypeScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/import_users.ts), [JavaScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/import_users.js)) imports users from a saved `.json` file into your Supabase project (inserting those users into the `auth.users` table of your `Postgres` database instance).
## Set up the migration tool [#set-up-migration-tool]
@@ -69,7 +69,7 @@ hash_config {
- `filename.json`: (optional) output filename (defaults to `./users.json`)
- `batchSize`: (optional) number of users to fetch in each batch (defaults to 100)
-### Import JSON users file to Supabase Auth (PostgreSQL: auth.users) [#import-json-users-file]
+### Import JSON users file to Supabase Auth (Postgres: auth.users) [#import-json-users-file]
`node import_users.js []`
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/firebase-storage.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/firebase-storage.mdx
similarity index 96%
rename from apps/docs/content/guides/resources/migrating-to-supabase/firebase-storage.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/firebase-storage.mdx
index d83f6a62acc..abbbd1038e4 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/firebase-storage.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/firebase-storage.mdx
@@ -1,8 +1,8 @@
---
id: 'firebase-storage'
-title: 'Firebase Storage Migration'
+title: 'Migrated from Firebase Storage to Supabase'
description: 'Migrate Firebase Storage files to Supabase Storage.'
-sidebar_label: 'Firebase Storage'
+subtitle: 'Migrate Firebase Storage files to Supabase Storage.'
---
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/storage) to convert storage files from Firebase Storage to Supabase Storage. Conversion is a two-step process:
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/firestore-data.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/firestore-data.mdx
similarity index 98%
rename from apps/docs/content/guides/resources/migrating-to-supabase/firestore-data.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/firestore-data.mdx
index ba2dda8d397..60f071362c9 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/firestore-data.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/firestore-data.mdx
@@ -1,8 +1,8 @@
---
id: 'firestore-data'
-title: 'Firestore Data Migration'
+title: 'Migrated from Firebase Firestore to Supabase'
description: 'Migrate your Firebase Firestore database to a Supabase Postgres database.'
-sidebar_label: 'Firestore Data'
+subtitle: 'Migrate your Firebase Firestore database to a Supabase Postgres database.'
---
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/firestore) to convert data from a Firebase Firestore database to a Supabase PostgreSQL database. The process copies the entire contents of a single Firestore `collection` to a single PostgreSQL `table`.
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/heroku.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/heroku.mdx
similarity index 97%
rename from apps/docs/content/guides/resources/migrating-to-supabase/heroku.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/heroku.mdx
index 17996649ab4..2256602e571 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/heroku.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/heroku.mdx
@@ -2,8 +2,8 @@
id: 'heroku'
title: 'Migrate from Heroku to Supabase'
description: 'Migrate your Heroku Postgres database to Supabase.'
-sidebar_label: 'Heroku'
-video: 'https://www.youtube.com/v/xsRhPMphtZ4'
+subtitle: 'Migrate your Heroku Postgres database to Supabase.'
+tocVideo: 'xsRhPMphtZ4'
---
Supabase is one of the best [free alternatives to Heroku Postgres](https://supabase.com/alternatives/supabase-vs-heroku-postgres). This guide shows how to migrate your Heroku Postgres database to Supabase. This migration requires the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) CLI tools, which are installed automatically as part of the complete PostgreSQL installation package.
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/mssql.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/mssql.mdx
similarity index 97%
rename from apps/docs/content/guides/resources/migrating-to-supabase/mssql.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/mssql.mdx
index 75a6a0d5b15..17b3aba57da 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/mssql.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/mssql.mdx
@@ -1,6 +1,7 @@
---
-title: 'Migrate Your MSSQL Database to Supabase'
-description: 'Migrate your Microsoft SQL Server database to Supabase Postgres database.'
+title: 'Migrate from MSSQL to Supabase'
+description: 'Migrate your Microsoft SQL Server database to Supabase.'
+subtitle: 'Migrate your Microsoft SQL Server database to Supabase.'
---
This guide aims to demonstrate the process of transferring your Microsoft SQL Server database to Supabase's PostgreSQL database. Supabase is a powerful and open-source platform offering a wide range of backend features, including a PostgreSQL database, authentication, instant APIs, edge functions, real-time subscriptions, and storage. Migrating your MSSQL database to Supabase's Postgres enables you to leverage Postgres's capabilities and access all the features you need for your project.
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/mysql.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/mysql.mdx
similarity index 97%
rename from apps/docs/content/guides/resources/migrating-to-supabase/mysql.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/mysql.mdx
index 8f558dfa6ce..981360e1293 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/mysql.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/mysql.mdx
@@ -1,6 +1,7 @@
---
-title: 'Migrate Your MySQL Database to Supabase'
+title: 'Migrate from MySQL to Supabase'
description: 'Migrate your MySQL database to Supabase Postgres database.'
+subtitle: 'Migrate your MySQL database to Supabase Postgres database.'
---
This guide aims to exhibit the process of transferring your MySQL database to Supabase's Postgres database. Supabase is a robust and open-source platform offering a wide range of backend features, including a PostgreSQL database, authentication, instant APIs, edge functions, real-time subscriptions, and storage. Migrating your MySQL database to Supabase's PostgreSQL enables you to leverage PostgreSQL's capabilities and access all the features you need for your project.
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/postgres.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/postgres.mdx
similarity index 96%
rename from apps/docs/content/guides/resources/migrating-to-supabase/postgres.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/postgres.mdx
index b8dc0f54370..3d7257e5972 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/postgres.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/postgres.mdx
@@ -1,6 +1,7 @@
---
title: 'Migrate from Postgres to Supabase'
-description: 'Migrate your Postgres database to Supabase.'
+description: 'Migrate your existing Postgres database to Supabase.'
+subtitle: 'Migrate your existing Postgres database to Supabase.'
---
This is a guide for migrating your Postgres database to [Supabase](https://supabase.com).
diff --git a/apps/docs/content/guides/resources/migrating-to-supabase/render.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/render.mdx
similarity index 98%
rename from apps/docs/content/guides/resources/migrating-to-supabase/render.mdx
rename to apps/docs/content/guides/platform/migrating-to-supabase/render.mdx
index 5078aeeee3a..3aaffad8751 100644
--- a/apps/docs/content/guides/resources/migrating-to-supabase/render.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/render.mdx
@@ -1,6 +1,7 @@
---
title: 'Migrate from Render to Supabase'
description: 'Migrate your Render Postgres database to Supabase.'
+subtitle: 'Migrate your Render Postgres database to Supabase.'
---
Render is a popular Web Hosting service in the online services category that also has a managed Postgres service. Render has a great developer experience, allowing users to deploy straight from GitHub or GitLab. This is the core of their product and they do it really well. However, when it comes to Postgres databases, it may not be the best option.
diff --git a/apps/docs/content/guides/platform/migrating-within-supabase.mdx b/apps/docs/content/guides/platform/migrating-within-supabase.mdx
new file mode 100644
index 00000000000..0c33c3a7015
--- /dev/null
+++ b/apps/docs/content/guides/platform/migrating-within-supabase.mdx
@@ -0,0 +1,157 @@
+---
+title: Migrating within Supabase
+subtitle: Migrate from one Supabase project to another
+---
+
+Migrating projects can be achieved using the Supabase CLI. This is particularly useful for older projects (for example to use a newer Postgres version).
+
+### Before you begin
+
+- Install [Postgres](https://www.postgresql.org/download/) so you can run `psql` and `pg_dump`.
+- Install the latest version of [Supabase CLI](/docs/guides/cli#installation).
+- Create a new [Supabase project](https://supabase.com/dashboard).
+- Install [Docker Desktop](https://www.docker.com) for your platform.
+- Set environment variables for the old project's database URL as `$OLD_DB_URL` and the new project's as `$NEW_DB_URL`.
+ To find the database URL for a project, go to the project's dashboard page [Project Settings/Database](https://supabase.com/dashboard/project/_/settings/database) and look under `Connection string`. If your network provider supports IPv6, you can disable `Use connection pooling`. Otherwise, enable `Use connection pooling`. For the pooler mode, `Transaction` will work.
+
+### Backup your old database
+
+1. Run the following command from your terminal:
+
+```bash
+supabase db dump --db-url "$OLD_DB_URL" -f roles.sql --role-only
+supabase db dump --db-url "$OLD_DB_URL" -f schema.sql
+supabase db dump --db-url "$OLD_DB_URL" -f data.sql --use-copy --data-only
+```
+
+### Restore to your new project
+
+In your new project:
+
+1. Enable [Database Webhooks](https://supabase.com/dashboard/project/_/database/hooks) if you enabled them in your old project.
+2. Enable any [extensions](https://supabase.com/dashboard/project/_/database/extensions) that were enabled in your old project.
+
+If you use [column encryption](/docs/guides/database/column-encryption), first copy the root encryption key to your new project using your [Personal Access Token](https://supabase.com/dashboard/account/tokens).
+
+```bash
+export OLD_PROJECT_REF=""
+export NEW_PROJECT_REF=""
+export SUPABASE_ACCESS_TOKEN=""
+
+curl "https://api.supabase.com/v1/projects/$OLD_PROJECT_REF/pgsodium" \
+ -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" |
+curl "https://api.supabase.com/v1/projects/$NEW_PROJECT_REF/pgsodium" \
+ -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
+ -X PUT --json @-
+```
+
+Then run the following command from your terminal:
+
+```bash
+psql \
+ --single-transaction \
+ --variable ON_ERROR_STOP=1 \
+ --file roles.sql \
+ --file schema.sql \
+ --command 'SET session_replication_role = replica' \
+ --file data.sql \
+ --dbname "$NEW_DB_URL"
+```
+
+Setting the `session_replication_role` to `replica` disables all triggers so that columns are not double encrypted.
+
+Troubleshooting notes:
+
+- If you have created any [custom roles](https://supabase.com/dashboard/project/_/database/roles) with `login` attribute, you have to manually set their passwords in the new project.
+- If you run into any permission errors related to `supabase_admin` during restore, edit the `schema.sql` file and comment out any lines containing `ALTER ... OWNER TO "supabase_admin"`.
+
+### Preserving migration history
+
+If you were using Supabase CLI for managing migrations on your old database and would like to preserve the migration history in your newly restored project, you need to insert the migration records separately using the following commands.
+
+```bash
+supabase db dump --db-url "$OLD_DB_URL" -f history_schema.sql --schema supabase_migrations
+supabase db dump --db-url "$OLD_DB_URL" -f history_data.sql --use-copy --data-only --schema supabase_migrations
+psql \
+ --single-transaction \
+ --variable ON_ERROR_STOP=1 \
+ --file history_schema.sql \
+ --file history_data.sql \
+ --dbname "$NEW_DB_URL"
+```
+
+### Schema changes to `auth` and `storage`
+
+If you have modified the `auth` and `storage` schemas in your old project, such as adding triggers or RLS policies, you have to restore them separately. The Supabase CLI can help you diff the changes to these schemas using the following commands.
+
+```bash
+supabase link --project-ref "$OLD_PROJECT_REF"
+supabase db diff --linked --schema auth,storage > changes.sql
+```
+
+### Enable publication on tables
+
+Replication for Realtime is disabled for all tables in your new project. On the [Publications](https://supabase.com/dashboard/project/_/database/publications) page in the Dashboard, select your new project and enable replication for tables that were enabled in your old project.
+
+### Migrate storage objects
+
+The new project has the old project's Storage buckets, but the Storage objects need to be migrated manually. Use this script to move storage objects from one project to another.
+
+```js
+// npm install @supabase/supabase-js@1
+const { createClient } = require('@supabase/supabase-js')
+
+const OLD_PROJECT_URL = 'https://xxx.supabase.co'
+const OLD_PROJECT_SERVICE_KEY = 'old-project-service-key-xxx'
+
+const NEW_PROJECT_URL = 'https://yyy.supabase.co'
+const NEW_PROJECT_SERVICE_KEY = 'new-project-service-key-yyy'
+
+;(async () => {
+ const oldSupabaseRestClient = createClient(OLD_PROJECT_URL, OLD_PROJECT_SERVICE_KEY, {
+ db: {
+ schema: 'storage',
+ },
+ })
+ const oldSupabaseClient = createClient(OLD_PROJECT_URL, OLD_PROJECT_SERVICE_KEY)
+ const newSupabaseClient = createClient(NEW_PROJECT_URL, NEW_PROJECT_SERVICE_KEY)
+
+ // make sure you update max_rows in postgrest settings if you have a lot of objects
+ // or paginate here
+ const { data: oldObjects, error } = await oldSupabaseRestClient.from('objects').select()
+ if (error) {
+ console.log('error getting objects from old bucket')
+ throw error
+ }
+
+ for (const objectData of oldObjects) {
+ console.log(`moving ${objectData.id}`)
+ try {
+ const { data, error: downloadObjectError } = await oldSupabaseClient.storage
+ .from(objectData.bucket_id)
+ .download(objectData.name)
+ if (downloadObjectError) {
+ throw downloadObjectError
+ }
+
+ const { _, error: uploadObjectError } = await newSupabaseClient.storage
+ .from(objectData.bucket_id)
+ .upload(objectData.name, data, {
+ upsert: true,
+ contentType: objectData.metadata.mimetype,
+ cacheControl: objectData.metadata.cacheControl,
+ })
+ if (uploadObjectError) {
+ throw uploadObjectError
+ }
+ } catch (err) {
+ console.log('error moving ', objectData)
+ console.log(err)
+ }
+ }
+})()
+```
+
+### Transfer to a different organization
+
+Note that project migration is for transferring your projects to different regions. If you need to move your project to a different organization without touching the infrastructure, see [project transfers](/docs/guides/platform/project-transfer).
diff --git a/apps/docs/content/guides/platform/org-based-billing.mdx b/apps/docs/content/guides/platform/org-based-billing.mdx
index b001ea8620e..60151a8cbfa 100644
--- a/apps/docs/content/guides/platform/org-based-billing.mdx
+++ b/apps/docs/content/guides/platform/org-based-billing.mdx
@@ -5,27 +5,36 @@ description: 'Learn how billing works in Supabase.'
subtitle: 'Learn how billing works in Supabase.'
---
-Our goal at Supabase is to provide a _predictable_ billing system that grows with your project.
+Our goal at Supabase is to provide a _predictable_ billing system that grows with you.
If you’re using Fly Postgres, see the [Fly Postgres Pricing](/docs/guides/platform/fly-postgres#pricing) section.
+If you’re using Vercel Marketplace, see the [Vercel Marketplace Pricing](/docs/guides/platform/vercel-marketplace#pricing) section.
+
## How billing is organized
-The Supabase Platform has "organizations" and "projects". An organization may contain multiple projects. Each project is a dedicated Supabase instance with all of its sub-services including Storage, Auth, Functions and Realtime. For example:
+The Supabase Platform has "organizations" and "projects". An organization may contain multiple projects. Each project is a dedicated Postgres and Supabase instance with all of its sub-services including Storage, Auth, Functions and Realtime. For example:
- `Organization 1`
- - `Project 1` (Database, Auth, Storage, Functions, Realtime)
- - `Project 2` (Database, Auth, Storage, Functions, Realtime)
+ - `Project 1` (Postgres, Data API, Auth, Storage, Functions, Realtime)
+ - `Project 2` (Postgres, Data API, Auth, Storage, Functions, Realtime)
- `Organization 2`
- - `Project 3` (Database, Auth, Storage, Functions, Realtime)
+ - `Project 3` (Postgres, Data API, Auth, Storage, Functions, Realtime)
-Each organization only has a single subscription with a single plan (Free, Pro, Team or Enterprise). Project addons such as [Compute](/docs/guides/platform/compute-add-ons), [Custom Domains](/docs/guides/platform/custom-domains) and [PITR](/docs/guides/platform/backups#point-in-time-recovery) are still configured per project and are added to your organization subscription.
+Each organization only has a single subscription with a single Plan (Free, Pro, Team or Enterprise). Project add-ons such as [Compute](/docs/guides/platform/compute-add-ons), [IPv4](/docs/guides/platform/ipv4-address), [Log Drains](/docs/guides/platform/log-drains), [Advanced MFA](/docs/guides/auth/auth-mfa/phone), [Custom Domains](/docs/guides/platform/custom-domains) and [PITR](/docs/guides/platform/backups#point-in-time-recovery) are still configured per project and are bundled into your organization subscription.
-
+
## Quotas and features
@@ -33,74 +42,91 @@ Quotas and features are applied to your entire organization, independent of how
Upgrading your organization to a paid plan means that you unlock the benefits of the paid plan for **all** projects within that organization - for example, no projects in your "Pro" organization will be paused. See [pricing page](https://supabase.com/pricing) for details on all plans.
+It is not possible to mix different plans inside a single organization. You cannot have one Pro Plan project and one Free Plan project inside a single organization. To have projects on different plans, you'll need to create different organizations. Also see [Project Transfers](/docs/guides/platform/project-transfer) in case you're looking to transfer a project to a different organization.
+
### Usage items
-| Metric | Free | Pro/Team | Enterprise |
-| -------------------------- | ------------------------ | ------------------------------------------- | ---------- |
-| Egress | 5GB | 250GB included, then $0.09 per GB | Custom |
-| Database Size | 500MB | 8GB included, then $0.125 per GB | Custom |
-| Storage Size | 1GB | 100GB included, then $0.021 per GB | Custom |
-| Monthly Active Users | 50,000 MAU | 100,000 MAU included, then $0.00325 per MAU | Custom |
-| Function Invocations | 500K | 2 million included, then $2 per million | Custom |
-| Function Count | 10 | 100 included, then $10 per 100 | Custom |
-| Storage Images Transformed | Unavailable on Free plan | 100 included, then $5 per 1000 | Custom |
-| Realtime Message Count | 2 million | 5 million included, then $2.5 per million | Custom |
-| Realtime Peak Connections | 200 | 500 included, then $10 per 1000 | Custom |
+| Metric | Free | Pro/Team | Enterprise |
+| -------------------------------- | ------------------------ | ------------------------------------------------- | ---------- |
+| Egress | 5GB | 250GB included, then $0.09 per GB | Custom |
+| Database Size | 500MB | 8GB disk per project included, then $0.125 per GB | Custom |
+| Storage Size | 1GB | 100GB included, then $0.021 per GB | Custom |
+| Monthly Active Users | 50,000 MAU | 100,000 MAU included, then $0.00325 per MAU | Custom |
+| Monthly Active Third-Party Users | 50 MAU | 50 MAU included, then $0.00325 per MAU | Custom |
+| Monthly Active SSO Users | - | 50 MAU included, then $0.015 per MAU | Custom |
+| Function Invocations | 500K | 2 million included, then $2 per million | Custom |
+| Function Count | 25 | 500/1000 | Unlimited |
+| Storage Images Transformed | Unavailable on Free Plan | 100 included, then $5 per 1000 | Custom |
+| Realtime Message Count | 2 million | 5 million included, then $2.5 per million | Custom |
+| Realtime Peak Connections | 200 | 500 included, then $10 per 1000 | Custom |
+| Log Drain Events | - | - / $0.20 per Million Events | Custom |
-## Usage-based billing for compute
+## Billing for Compute (Compute Hours)
-We provide a dedicated server for every Supabase project. By default, your instance runs on the Micro Compute instance. You can upgrade your compute size in your [project settings](https://supabase.com/dashboard/project/_/settings/addons).
+Supabase provides a dedicated server and database for every project. By default, your instance runs on the Micro Compute instance. You can upgrade your compute size in your [project settings](https://supabase.com/dashboard/project/_/settings/addons).
-You won't get an immediate charge upfront when changing compute, instead we'll bill you based on compute runtime hours when your billing cycle resets.
+Given we provide dedicated databases on paid plans that have 24/7 uptime and predictable performance, you pay the same amount for compute independent of your database usage. An active project on the Small compute size running for 30 days consumes 720 (30x24h) Small Compute Hours.
-If you launch a second or third instance on your paid plan, we add the additional compute hours to the final invoice.
+You won't get an immediate charge upfront when changing compute, instead we'll bill you based on compute hours when your billing cycle resets. If you launch a second or third instance on your paid plan, we add the additional compute hours to the final invoice. If you upgrade an instance for 24 hours, you'll only be billed for those 24 hours of additional compute. Compute hours are billed when you do plan up/downgrades (resets the billing cycle) or whenever your billing cycle resets once a month.
-We only count compute hours for instances that are active. Paused projects do not count towards compute usage. We’re working on self-serve pausing and unpausing on paid plans to further manage costs.
+We only count compute hours for instances that are active. Paused projects do not count towards compute usage.
### Compute pricing
-| Instance Size | Hourly Price | Estimated Monthly Price |
-| ------------- | ------------ | ----------------------- |
-| Micro | $0.01344 | ~$10 |
-| Small | $0.0206 | ~$15 |
-| Medium | $0.0822 | ~$60 |
-| Large | $0.1517 | ~$110 |
-| XL | $0.2877 | ~$210 |
-| 2XL | $0.562 | ~$410 |
-| 4XL | $1.32 | ~$960 |
-| 8XL | $2.562 | ~$1870 |
-| 12XL | $3.836 | ~$2800 |
-| 16XL | $5.12 | ~$3730 |
+| Compute Size | Hourly Price | Estimated Monthly Price |
+| ------------ | ------------ | ----------------------- |
+| Micro | $0.01344 | ~$10 |
+| Small | $0.0206 | ~$15 |
+| Medium | $0.0822 | ~$60 |
+| Large | $0.1517 | ~$110 |
+| XL | $0.2877 | ~$210 |
+| 2XL | $0.562 | ~$410 |
+| 4XL | $1.32 | ~$960 |
+| 8XL | $2.562 | ~$1870 |
+| 12XL | $3.836 | ~$2800 |
+| 16XL | $5.12 | ~$3730 |
-### Free plan
-
-Supabase provides two "Free organizations". Each organization can run a `Nano` instance for free. This is a great way to get started with Supabase and try out the platform.
-
-### Compute credits
+### Compute Credits
Paid plans come with $10 of Compute Credits to cover one Micro instance or parts of any other [Compute Add-On](/docs/guides/platform/compute-add-ons).
-Compute Credits are deducted from your Compute Usage. You can launch as many instances as you want on paid plans and we'll bill based on the compute hours. If you upgrade an instance for 24 hours, you'll only be billed for those 24 hours of additional compute. Compute hours are billed when you do plan up/downgrades (resets the billing cycle) or whenever your billing cycle resets once a month.
+Compute Credits are deducted from your Compute Usage. You can launch as many instances as you want on paid plans and we'll bill based on the compute hours.
-Compute Credits do not apply to other usage fees like egress, database size, ... They solely apply to Compute usage.
+Compute Credits do not apply to other usage fees like egress, disk size, ... They solely apply to Compute usage.
-Additionally, Compute Credits are given to you not only for the first month but for every month while you are on a paid plan. Notice though, that they reset every month and do not accumulate, in case you're not running any projects.
+Additionally, Compute Credits are given to you not only for the first month but for every month while you are on a paid plan. They reset every month and do not accumulate.
-### Replicas
+### Read Replicas
-In case of deploying additional [Read Replicas](/docs/guides/platform/read-replicas), each replica is a dedicated database that adds additional compute runtime hours.
+In case of deploying [Read Replicas](/docs/guides/platform/read-replicas), each replica is a dedicated database that adds additional compute hours and disk size usage.
-Each replica incurs one compute hour on the given instance size. An example:
+Each replica incurs one compute hour on the given compute size. An example:
A primary database on XL compute and two read replicas on XL compute would incur ~2.190 XL compute hours (3x 730h) and cost ~$630/month.
+The disk size of a replica instance is usually 1.25x the size of the primary disk to account for WAL archives
+
+## Project Add-ons
+
+While the subscription plan (Free, Pro, Team or Enterprise) applies to your entire organization and is only charged for once, you can level up your project by opting into several add-ons:
+
+- [Compute](/docs/guides/platform/compute-add-ons) to scale your database up to 64 cores and 256 GB RAM
+- [Read Replicas](/docs/guides/platform/read-replicas) to scale read operations and provide resiliency
+- [Log Drains](/docs/guides/platform/log-drains) to sync Supabase logs to a logging system of your choice
+- [Custom Domains](/docs/guides/platform/custom-domains) to provide a branded experience
+- [PITR](/docs/guides/platform/backups#point-in-time-recovery) to roll back to any specific point in time, down to the minute
+- [IPv4](/docs/guides/platform/ipv4-address) for a dedicated IPv4 address
+- [Advanced MFA](/docs/guides/auth/auth-mfa/phone) to provide other options than TOTP
+
+Project add-ons are charged by the hour at the end of your billing cycle. I.e. if you enable a log drain for two weeks, you will pay for 336 hours (14 days \* 24 hours).
+
## Unified egress
-Each organization receives a total egress quota (eg. 250GB on the Pro plan) which can be used for anything - Storage, Realtime, Auth, Functions, Supavisor or Database.
+Each organization receives a total egress quota (eg. 250GB on the Pro Plan) which can be used for anything - Storage, Realtime, Auth, Functions, Supavisor, Log Drains or Database.
### Included egress quota
@@ -127,31 +153,58 @@ You can see a breakdown of the different types of egress on your [organization u
/>
+## Disk Size
+
+We differentiate between database space usage and disk size. Database Space is the actual amount of space used by all your database objects, whereas disk size is the size of the underlying provisioned disk. Each database has a provisioned disk.
+
+Free Plan customers are limited to 500 MB of database space.
+
+For paid plan customers:
+
+- Each project starts with 8 GB GP3 disk provisioned by default
+- The first 8 GB of provisioned disk per project is free, then $0.125 per additional GB
+- Charges are prorated down to the hour, which is advantageous for short-lived projects and branches
+- Each read replica has a provisioned disk as well, which adds to your disk usage
+
+When your data gets close to the 8 GB disk size limit, we automatically scale it by 50% on paid plans to ensure your project does not run into a read-only mode. Each hour your disk is beyond the included 8 GB, it incurs one GB-Hr of usage. With a 12 GB provisioned disk (4 GB above the limit), this would incur a total monthly cost of $0.5 ($0.125 per GB / $0.000171 per GB-Hr). Disk Size is prorated down to the hour, so your bill will show "Disk Size GB-Hrs".
+
+You can see your project’s disk size in your [project settings](https://supabase.com/dashboard/project/_/settings/database) (Project Settings > Database).
+
+While we automatically scale up your disk, we do not automatically scale down your disk. Read more about [disk management](/docs/guides/platform/database-size#disk-management) and how to [reduce your disk size](/docs/guides/platform/database-size#reducing-disk-size).
+
+## Free Plan
+
+Supabase provides two "Free projects". Each project can run a `Nano` instance for free. This is a great way to get started with Supabase and try out the platform.
+
+We count your total limit of 2 free projects across all organizations you're either an Owner or Administrator of. You could have two Free Plan organizations with one project each, or one Free Plan organization with two projects. Paused projects do not count towards your free project limit.
+
## Billing Examples
Here are some examples on how the Billing affects you.
-### Multiple free plan projects
+### Multiple projects in a Free Plan organization
-Here are the quotas for the free plan:
+Here are the quotas for the Free Plan:
-| Usage Item | Free plan |
-| ------------------------- | --------------------------------------- |
-| Egress | 5GB across Database + Storage |
-| Database Space | 500MB |
-| Storage Space | 1GB |
-| Monthly Active Users | 50,000 MAU |
-| Edge Function Invocations | 500K |
-| Edge Function Count | 10 |
-| Realtime Message Count | 2 million |
-| Realtime Peak Connections | 200 |
-| | Max of 2 free orgs - 1 database per org |
+| Usage Item | Free Plan |
+| ------------------------- | ----------------------------- |
+| Egress | 5GB across Database + Storage |
+| Database Space | 500MB |
+| Storage Space | 1GB |
+| Monthly Active Users | 50,000 MAU |
+| Edge Function Invocations | 500K |
+| Edge Function Count | 25 |
+| Realtime Message Count | 2 million |
+| Realtime Peak Connections | 200 |
+| Max of 2 free projects\* |
-While we allow you to launch a total of two free projects, the quota applies to the entire organization, i.e. you can have a total of 4GB of database space.
+The quota applies to the entire organization, i.e. you can have a total of 5GB egress (sum of all projects egress combined).
-### Multiple pro plan projects
+\* All active projects in Free Plan organizations where you're either an Owner or Administrator count towards your total limit of 2 free projects. Paused projects do not count towards your limit.
-While you only pay for the $25 Pro plan once, launching additional projects at least incurs compute costs.
+### Multiple projects in a Pro Plan organization
+
+While you only pay for the $25 Pro Plan once, launching additional projects at least incurs compute costs.
**Invoice Breakdown**
@@ -164,18 +217,16 @@ While you only pay for the $25 Pro plan once, launching additional projects at l
Here's the quota for your entire organization:
-| Usage Item | Quota |
-| -------------------------- | ----------------------------------------- |
-| Egress | 250GB included, then $0.09 per GB |
-| Database Size | 8GB included, then $0.125 per GB |
-| Storage Size | 100GB included, then $0.021 per GB |
-| Monthly Active Users | 100K MAU included, then $0.00325 per MAU |
-| Function Invocations | 2 million included, then $2 per million |
-| Function Count | 100 included, then $10 per 100 |
-| Storage Images Transformed | 100 included, then $5 per 1000 |
-| Realtime Message Count | 5 million included, then $2.5 per million |
-| Realtime Peak Connections | 500 included, then $10 per 1000 |
-
-If you wish to keep the same quota for each project, you can separate your projects into three organizations on the Pro plan each. You'd end up paying $25 for each project, so $75 instead of $45 in total, while getting more quota.
+| Usage Item | Quota |
+| -------------------------- | -------------------------------------------- |
+| Egress | 250GB included, then $0.09 per GB |
+| Disk Size | 8GB per project included, then $0.125 per GB |
+| Storage Size | 100GB included, then $0.021 per GB |
+| Monthly Active Users | 100K MAU included, then $0.00325 per MAU |
+| Function Invocations | 2 million included, then $2 per million |
+| Function Count | 500 included |
+| Storage Images Transformed | 100 included, then $5 per 1000 |
+| Realtime Message Count | 5 million included, then $2.5 per million |
+| Realtime Peak Connections | 500 included, then $10 per 1000 |
See [Project Transfers](/docs/guides/platform/project-transfer) to move projects across organizations.
diff --git a/apps/docs/content/guides/platform/project-transfer.mdx b/apps/docs/content/guides/platform/project-transfer.mdx
index 5ac8c0d3425..83738e2a9e2 100644
--- a/apps/docs/content/guides/platform/project-transfer.mdx
+++ b/apps/docs/content/guides/platform/project-transfer.mdx
@@ -6,25 +6,25 @@ description: 'Transfer a project to another organization.'
With the introduction of [Organization-based Billing](/docs/guides/platform/org-based-billing), we’re adding the ability to freely transfer projects between different organizations. Head to your [projects' general settings](https://supabase.com/dashboard/project/_/settings/general) to initiate a project transfer.
-
-
-
+
-
-
-
+
Source organization - the organization the project currently belongs to
Target organization - the organization you want to move the project to
@@ -38,22 +38,15 @@ Target organization - the organization you want to move the project to
## Usage-billing and project addons
-If you’re using a [Point-In-Time-Recovery](/docs/guides/platform/backups#point-in-time-recovery) or a [Custom Domain](/docs/guides/platform/custom-domains), the source organization will get credits for the remaining unused time in your current billing cycle.
-For example, if you are in the middle of your current billing cycle and you already pre-paid $10 for a Custom domain, you’ll get $5 back in credits.
+For usage metrics such as disk size, egress or image transformations and project add-ons such as [Compute Add-On](/docs/guides/platform/compute-add-ons), [Point-In-Time-Recovery](/docs/guides/platform/backups#point-in-time-recovery), [IPv4](/docs/guides/platform/ipv4-address), [Log Drains](/docs/guides/platform/log-drains), [Advanced MFA](/docs/guides/auth/auth-mfa/phone) or a [Custom Domain](/docs/guides/platform/custom-domains), the source organization will still be charged for the usage up until the transfer. The charges will be added to the invoice when the billing cycle resets.
-The target organization will be charged immediately for the remaining time in the billing cycle.
-As the billing cycle from the source and target organization likely differ, the prorated charge on the target organization will also differ from the credits on the source organization.
-
-If you’re using a [Compute Add-On](/docs/guides/platform/compute-add-ons), the source organization will get credits for the remaining unused time, whereas the target organization won’t have any immediate charges.
-As we’re charging compute usage-based with [Organization-based Billing](/docs/guides/platform/org-based-billing), you’ll be charged for the usage when the next billing cycle hits based on how long the project has been in the target organization.
-
-Similarly, for other usage metrics such as database size, egress, image transformations and so on, the usage until the project transfer will be billed on the source organization and future usage will be added to the target organization once moved.
+The target organization will be charged at the end of the billing cycle for usage after the project transfer.
## Things to watch out for
-- Transferring a project might come with a short 1-2 minute downtime if you're moving a project from a paid to a free plan.
-- You could lose access to certain project features depending on the plan of the target organization, i.e. moving a project from a Pro plan to a Free plan.
-- When moving your project to a Free plan, we also ensure you’re not exceeding your two free project limit. In these cases, it is best to upgrade your target organization to Pro plan first.
+- Transferring a project might come with a short 1-2 minute downtime if you're moving a project from a paid to a Free Plan.
+- You could lose access to certain project features depending on the plan of the target organization, i.e. moving a project from a Pro Plan to a Free Plan.
+- When moving your project to a Free Plan, we also ensure you’re not exceeding your two free project limit. In these cases, it is best to upgrade your target organization to Pro Plan first.
- You could have less rights on the project depending on your role in the target organization, i.e. you were an Owner in the previous organization and only have a Read-Only role in the target organization.
## Transfer to a different region
diff --git a/apps/docs/content/guides/platform/read-replicas.mdx b/apps/docs/content/guides/platform/read-replicas.mdx
index 882892700d6..13c7cc73012 100644
--- a/apps/docs/content/guides/platform/read-replicas.mdx
+++ b/apps/docs/content/guides/platform/read-replicas.mdx
@@ -10,21 +10,12 @@ Read Replicas are additional databases that are kept in sync with your Primary d
- **Improved latency:** For projects with a global user base, additional databases can be deployed closer to users to reduce latency.
- **Redundancy:** Read Replicas provide data redundancy.
-
-
-
-
-
-
-
+
## About Read Replicas
@@ -59,7 +50,9 @@ Projects must meet these requirements to use Read Replicas:
## Getting started
-To add a Read Replica, go to the [Infrastructure Settings page](/dashboard/project/_/settings/infrastructure) in your dashboard. Each project can deploy up to two Read Replicas and all Read Replicas inherit the compute size of their Primary database.
+To add a Read Replica, go to the [Infrastructure Settings page](/dashboard/project/_/settings/infrastructure) in your dashboard.
+
+Projects on an XL compute add-on or larger can create up to five Read Replicas. Projects on compute add-ons smaller than XL can create up to two Read Replicas. All Read Replicas inherit the compute size of their Primary database.
### Deploying a Read Replica
@@ -84,14 +77,6 @@ Read Replicas offer the following features:
### Dedicated endpoints
-
-
-For Read Replicas deployed before May 24, 2024, there is a known bug that results in the [PostgREST schema cache](https://postgrest.org/en/v12/references/schema_cache.html) on Read Replicas not being reloaded correctly on DDL changes. A stale schema cache can result in PostgREST on Read Replicas not knowing about newly created functions, tables, changes to their structure, etc.
-
-A fix for this issue has already been deployed for newer Read Replicas but not fully applied for older ones as it requires a restart of the API service. You can perform a one-time restart of the service by [saving any trivial configuration change](https://supabase.com/dashboard/project/_/settings/api) to your PostgREST configuration (For example, changing the `Max Rows` by 1), which will result in the service being restarted.
-
-
-
Each Read Replica has its own dedicated database and API endpoints.
- Find the database endpoint on the [Database Settings page](/dashboard/project/_/settings/database) under **Connection Info**
@@ -117,6 +102,65 @@ To call a read-only Postgres function on Read Replicas through the REST API, use
If you remove all Read Replicas from your project, the load balancer and its endpoint are removed as well. Make sure to redirect requests back to your Primary database before removal.
+#### Experimental routing
+
+The API load balancer offers an experimental routing mode that builds on top of the existing functionality. It is now possible to use a load balancer endpoint for all Supabase services (Auth, Edge Functions, Realtime, and Storage) meaning there is no need to worry about which endpoint to use in which situations.
+
+
+
+Due to the requirements of the Auth service, all Auth requests are handled by the Primary, even when sent over the load balancer endpoint. This is similar to how non-Read requests for the Data API (PostgREST) are exclusively handled by the Primary.
+
+
+
+The experimental routing mode can be enabled by sending the header `sb-lb-routing-mode: alpha-all-services` along with requests. This gives the option to opt-in and try out the new routing, but still have the peace of mind that the original behavior is there should it be needed.
+
+What follows is an example of using the `supabase-js` library to create a new client with the appropriate headers and make an Auth request to create a new user.
+
+```ts ./create-user.ts
+import { createClient } from '@supabase/supabase-js'
+import * as dotenv from 'dotenv'
+
+// Load environment variables from .env file
+dotenv.config()
+
+// Supabase credentials
+const supabaseUrl = process.env.SUPABASE_URL as string
+const supabaseKey = process.env.SUPABASE_KEY as string
+
+// Initialize the Supabase client with custom headers
+const supabase = createClient(supabaseUrl, supabaseKey, {
+ global: {
+ headers: {
+ 'sb-lb-routing-mode': 'alpha-all-services',
+ },
+ },
+})
+
+/**
+ * @description simple user creation function
+ */
+async function createUser() {
+ // Make the Auth call to create a user
+ const { data, error } = await supabase.auth.signUp({
+ email: 'my-new-user@example.com',
+ password: 'my--really-strong-password',
+ })
+
+ // Throw on an error
+ if (error) {
+ throw new Error(error.message)
+ }
+
+ // Output the response
+ console.dir(data)
+}
+
+// Register a random user
+createUser()
+```
+
+The client creation function, with experimental routing header, can be used throughout an application to take advantage of load balancer experimental routing.
+
If you use a [custom domain](/docs/guides/platform/custom-domains), requests will not be routed through the load balancer. You should instead use the dedicated endpoints provided in the dashboard.
@@ -127,21 +171,12 @@ If you use a [custom domain](/docs/guides/platform/custom-domains), requests wil
In the SQL editor, you can choose if you want to run the query on a particular Read Replica.
-
-
-
-
-
-
-
+
### Logging
@@ -195,13 +230,13 @@ In this replication method, the Primary continuously buffers WAL changes to a lo
### File-based log shipping 🤝 streaming replication
-
-
- Map view of Primary and Read Replica databases
-
+
We bring these two methods together to achieve quick, stable, and reliable replication. Each method addresses the limitations of the other. Streaming replication minimizes replication lag, while file-based log shipping provides a fallback. For file-based log shipping, we use our existing Point In Time Recovery (PITR) infrastructure. We regularly archive files from the Primary using [WAL-G](https://github.com/wal-g/wal-g), an open source archival and restoration tool, and ship the WAL files to S3.
@@ -212,3 +247,24 @@ We combine it with streaming replication to reduce replication lag. Once WAL-G f
Replication lag for a specific Read Replica can be monitored through the dashboard. On the [Database Reports page](/dashboard/project/_/reports/database) Read Replicas will have an additional chart under `Replica Information` displaying historical replication lag in seconds. Realtime replication lag in seconds can be observed on the [Infrastructure Settings page](/dashboard/project/_/settings/infrastructure). This is the value on top of the Read Replica. Do note that there is no single threshold to indicate when replication lag should be addressed. It would be fully dependent on the requirements of your project.
If you are already ingesting your [project's metrics](/docs/guides/platform/metrics#accessing-the-metrics-endpoint) into your own environment, you can also keep track of replication lag and set alarms accordingly with the metric: `physical_replication_lag_physical_replica_lag_seconds`.
+
+Some common sources of high replication lag include:
+
+1. Exclusive locks on tables on the Primary.
+ Operations such as `drop table`, `reindex` (amongst others) take an Access Exclusive lock on the table. This can result in increasing replication lag for the duration of the lock.
+1. Resource Constraints on the database
+ Heavy utilization on the primary or the replica, if run on an under-resourced project, can result in high replication lag. This includes the characteristics of the disk being utilized (IOPS, Throughput).
+1. Long-running transactions on the Primary.
+ Transactions that run for a long-time on the primary can also result in high replication lag. You can use the `pg_stat_activity` view to identify and terminate such transactions if needed. `pg_stat_activity` is a live view, and does not offer historical data on transactions that might have been active for a long time in the past.
+
+High replication lag can result in stale data being returned for queries being executed against the affected read replicas.
+
+You can [consult](https://cloud.google.com/sql/docs/postgres/replication/replication-lag) [additional](https://repost.aws/knowledge-center/rds-postgresql-replication-lag) [resources](https://severalnines.com/blog/what-look-if-your-postgresql-replication-lagging/) on the subject as well.
+
+## Misc
+
+### Restart or compute add-on change behaviour
+
+When a project that utilizes Read Replicas is restarted, or the compute add-on size is changed, the Primary database gets restarted first. During this period, the Read Replicas remain available.
+
+Once the Primary database has completed restarting (or resizing, in case of a compute add-on change) and become available for usage, all the Read Replicas are restarted (and resized, if needed) concurrently.
diff --git a/apps/docs/content/guides/platform/regions.mdx b/apps/docs/content/guides/platform/regions.mdx
new file mode 100644
index 00000000000..99258244d14
--- /dev/null
+++ b/apps/docs/content/guides/platform/regions.mdx
@@ -0,0 +1,14 @@
+---
+title: Available regions
+subtitle: Spin up Supabase projects in our global regions
+---
+
+The following regions are available for your Supabase projects.
+
+## AWS
+
+
+
+## Fly
+
+Our [Fly Postgres](/docs/guides/platform/fly-postgres) offering (in private alpha) is supported in every region where Fly operates.
diff --git a/apps/docs/content/guides/platform/spend-cap.mdx b/apps/docs/content/guides/platform/spend-cap.mdx
index 359e4135e2b..478ccfa8561 100644
--- a/apps/docs/content/guides/platform/spend-cap.mdx
+++ b/apps/docs/content/guides/platform/spend-cap.mdx
@@ -12,14 +12,17 @@ This Spend Cap determines whether your organization can exceed the free quota al
Supabase charges for certain usage line items in your organization. Those line items include:
-- Database Size
+- Disk Size
- Storage Size
- Egress
- Storage Image Transformations
- Realtime concurrent peak connections
- Realtime messages
- Edge function invocations
-- Number of Edge functions
+- Log Drain Events
+- Monthly Active Users
+- Monthly Active Third-Party Users
+- Monthly Active SSO Users
You can view the pricing, and free quota allowance of each line item on the [pricing page](/pricing).
@@ -28,11 +31,17 @@ This will show you how much you have spent so far in the current billing cycle,
## Can I use the spend cap to limit my compute costs?
-The spend cap does not apply to compute usage. Each project is a dedicated server and database and incurs compute costs depending on the selected instance size. Costs for compute are predictable, as the maximum amount is number of hours in month \* hourly compute price (per project).
+The spend cap does not apply to compute usage. Each project is a dedicated server and database and incurs compute costs depending on the selected compute size. Costs for compute are predictable, as the maximum amount is number of hours in month \* hourly compute price (per project).
Launching additional projects on a paid plan incurs compute costs, even if your spend cap is toggled on.
-Read more about [how billing for compute works](https://supabase.com/docs/guides/platform/org-based-billing#usage-based-billing-for-compute).
+Read more about [how billing for compute works](https://supabase.com/docs/guides/platform/org-based-billing#billing-for-compute-compute-hours).
+
+## What's not covered by the Spend Cap?
+
+The Spend Cap does not apply to compute or project add-ons such as Custom Domains or IPv4. If you launch additional projects, opt into project add-ons, spin up branches or read replicas on a paid plan, it will incur additional costs.
+
+The Spend Cap also does not apply to Log Drain events given they have no included quota and are an explicit opt-in.
## How can I turn the Spend Cap on or off?
diff --git a/apps/docs/content/guides/platform/ssl-enforcement.mdx b/apps/docs/content/guides/platform/ssl-enforcement.mdx
index f104c6dfe7a..d0b4c82efa6 100644
--- a/apps/docs/content/guides/platform/ssl-enforcement.mdx
+++ b/apps/docs/content/guides/platform/ssl-enforcement.mdx
@@ -3,17 +3,17 @@ title: 'Postgres SSL Enforcement'
description: 'Enforce SSL usage for all Postgres connections'
---
+Your Supabase project supports connecting to the Postgres DB without SSL enabled to maximize client compatibility. For increased security, you can prevent clients from connecting if they're not using SSL.
+
+Disabling SSL enforcement only applies to connections to Postgres and Supavisor ("Connection Pooler"); all HTTP APIs offered by Supabase (e.g., PostgREST, Storage, Auth) automatically enforce SSL on all incoming connections.
+
-Postgres SSL Enforcement is currently in beta. Projects need to be at least on Postgres 13.3.0 to enable SSL enforcement. You can find the Postgres version of your project in the [Infrastructure Settings page](https://supabase.com/dashboard/project/_/settings/infrastructure). If your project is on an older version, you will need to
+Projects need to be at least on Postgres 13.3.0 to enable SSL enforcement. You can find the Postgres version of your project in the [Infrastructure Settings page](https://supabase.com/dashboard/project/_/settings/infrastructure). If your project is on an older version, you will need to
[upgrade](/docs/guides/platform/migrating-and-upgrading-projects#upgrade-your-project) to use this feature.
-Your Supabase project supports connecting to the Postgres DB without SSL enabled to maximize client compatibility. For increased security, you can prevent clients from connecting if they're not using SSL.
-
-SSL enforcement only applies to connections to both Postgres and Supavisor ("Connection Pooler"); all HTTP APIs offered by Supabase (e.g., PostgREST, Storage, Auth) automatically enforce SSL on all incoming connections.
-
## Manage SSL enforcement via the dashboard
SSL enforcement can be configured via the "Enforce SSL on incoming connections" setting under the SSL Configuration section in [Database Settings page](https://supabase.com/dashboard/project/_/settings/database) of the dashboard.
@@ -31,14 +31,18 @@ To get started:
You can use the `get` subcommand of the CLI to check whether SSL is currently being enforced:
```bash
-> supabase ssl-enforcement --project-ref {ref} get --experimental
+supabase ssl-enforcement --project-ref {ref} get --experimental
+```
+
+Response if SSL is being enforced:
+
+```bash
SSL is being enforced.
```
-Or similarly, if SSL is not being enforced, you will see:
+Response if SSL is not being enforced:
```bash
-> supabase ssl-enforcement --project-ref {ref} get --experimental
SSL is *NOT* being enforced.
```
@@ -47,13 +51,29 @@ SSL is *NOT* being enforced.
The `update` subcommand is used to change the SSL enforcement status for your project:
```bash
-> supabase ssl-enforcement --project-ref {ref} update --enable-db-ssl-enforcement --experimental
-SSL is now being enforced.
+supabase ssl-enforcement --project-ref {ref} update --enable-db-ssl-enforcement --experimental
```
Similarly, to disable SSL enforcement:
```bash
-> supabase ssl-enforcement --project-ref {ref} update --disable-db-ssl-enforcement --experimental
-SSL is *NOT* being enforced.
+supabase ssl-enforcement --project-ref {ref} update --disable-db-ssl-enforcement --experimental
+```
+
+### A note about Postgres SSL modes
+
+Postgres supports [multiple SSL modes](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION) on the client side. These modes provide different levels of protection. Depending on your needs, it is important to verify that the SSL mode in use is performing the required level of enforcement and verification of SSL connections.
+
+The strongest mode offered by Postgres is `verify-full` and this is the mode you most likely want to use when SSL enforcement is enabled. To use `verify-full` you will need to download the Supabase CA certificate for your database. The certificate is available through the dashboard under the SSL Configuration section in the [Database Settings page](/dashboard/project/_/settings/database).
+
+Once the CA certificate has been downloaded, add it to the certificate authority list used by Postgres.
+
+```bash
+cat {location of downloaded prod-ca-2021.crt} >> ~/.postgres/root.crt
+```
+
+With the CA certificate added to the trusted certificate authorities list, use `psql` or your client library to connect to Supabase:
+
+```bash
+psql "postgresql://aws-0-eu-central-1.pooler.supabase.com:6543/postgres?sslmode=verify-full" -U postgres.
```
diff --git a/apps/docs/content/guides/platform/sso/azure.mdx b/apps/docs/content/guides/platform/sso/azure.mdx
index 9e48e47b4fe..a4f08432c01 100644
--- a/apps/docs/content/guides/platform/sso/azure.mdx
+++ b/apps/docs/content/guides/platform/sso/azure.mdx
@@ -5,7 +5,13 @@ description: 'Configure single sign-on with Azure AD (Microsoft Entra).'
-This feature is only available on the Teams and Enterprise tiers. Please contact [Sales](https://forms.supabase.com/enterprise) before doing these steps.
+This feature is only available on the Team and Enterprise Plans. Please contact [Sales](https://forms.supabase.com/enterprise) before doing these steps.
+
+
+
+
+
+Looking for docs on how to add Single Sign-On support in your Supabase project? Head on over to [Single Sign-On with SAML 2.0 for Projects](/docs/guides/auth/enterprise-sso/auth-sso-saml).
@@ -77,7 +83,7 @@ Finally, click the _Save_ button to save the configuration.
## Step 7: Obtain metadata URL and send to Supabase [#send-metadata-url]
-Supabase needs to finalize enabling single sign-on with your Azure AD application. To do this, copy and send the link under **App Federation Metadata Url** in \*section 3 **SAML Certificates\*** to your support contact and await further instructions. If you're not clear who to send this link to or need further assistance, reach out to [support@supabase.com](mailto:support@supabase.com).
+Supabase needs to finalize enabling single sign-on with your Azure AD application. To do this, copy and send the link under **App Federation Metadata Url** in \*section 3 **SAML Certificates\*** to your support contact and await further instructions. If you're not clear who to send this link to or need further assistance, reach out to [Supabase Support](https://supabase.help).
**Do not test the login until you have heard back from the support contact.**
diff --git a/apps/docs/content/guides/platform/sso/gsuite.mdx b/apps/docs/content/guides/platform/sso/gsuite.mdx
index a7a7fa432b1..e20a1140660 100644
--- a/apps/docs/content/guides/platform/sso/gsuite.mdx
+++ b/apps/docs/content/guides/platform/sso/gsuite.mdx
@@ -5,7 +5,13 @@ description: 'Configure single sign-on with Google Workspace (GSuite).'
-This feature is only available on the Teams and Enterprise tiers. Please contact [Sales](https://forms.supabase.com/enterprise) before doing these steps.
+This feature is only available on the Team and Enterprise Plans. Please contact [Sales](https://forms.supabase.com/enterprise) before doing these steps.
+
+
+
+
+
+Looking for docs on how to add Single Sign-On support in your Supabase project? Head on over to [Single Sign-On with SAML 2.0 for Projects](/docs/guides/auth/enterprise-sso/auth-sso-saml).
@@ -33,7 +39,7 @@ This is a very important step. Click on _DOWNLOAD METADATA_ and save the file th

-It's very important to send this file to your support contact at Supabase to complete the SSO setup process. If you're not sure where to send this file, you can always reach us at [support@supabase.com](mailto:support@supabase.com).
+It's very important to send this file to your support contact at Supabase to complete the SSO setup process. If you're not sure where to send this file, you can always reach out to [Supabase Support](https://supabase.help).
**Important: Make sure the certificate as shown on screen has at least 1 year before it expires. Mark down this date in your calendar so you will be reminded that you need to update the certificate without any downtime for your users.**
diff --git a/apps/docs/content/guides/platform/sso/okta.mdx b/apps/docs/content/guides/platform/sso/okta.mdx
index 6134401cd67..78cce43d336 100644
--- a/apps/docs/content/guides/platform/sso/okta.mdx
+++ b/apps/docs/content/guides/platform/sso/okta.mdx
@@ -5,7 +5,13 @@ description: 'Configure single sign-on with Okta.'
-This feature is only available on the Teams and Enterprise tiers. Please contact [Sales](https://forms.supabase.com/enterprise) before doing these steps.
+This feature is only available on the Team and Enterprise Plans. Please contact [Sales](https://forms.supabase.com/enterprise) before doing these steps.
+
+
+
+
+
+Looking for docs on how to add Single Sign-On support in your Supabase project? Head on over to [Single Sign-On with SAML 2.0 for Projects](/docs/guides/auth/enterprise-sso/auth-sso-saml).
@@ -61,7 +67,7 @@ Supabase needs to finalize enabling single sign-on with your Okta application.
To do this scroll down to the _SAML Signing Certificates_ section on the _Sign On_ tab of the _Supabase_ application. Pick the the _SHA-2_ row with an _Active_ status. Click on the _Actions_ dropdown button and then on the _View IdP Metadata_.
-This will open up the SAML 2.0 Metadata XML file in a new tab in your browser. Copy this URL and send it to your support contact and await further instructions. If you're not clear who to send this link to or need further assistance, contact [support@supabase.com](mailto:support@supabase.com).
+This will open up the SAML 2.0 Metadata XML file in a new tab in your browser. Copy this URL and send it to your support contact and await further instructions. If you're not clear who to send this link to or need further assistance, contact [Supabase Support](https://supabase.help).
The link usually has this structure: `https://.okta.com/apps//sso/saml/metadata`
diff --git a/apps/docs/content/guides/platform/upgrading.mdx b/apps/docs/content/guides/platform/upgrading.mdx
new file mode 100644
index 00000000000..e6f848bffa0
--- /dev/null
+++ b/apps/docs/content/guides/platform/upgrading.mdx
@@ -0,0 +1,197 @@
+---
+title: Upgrading
+---
+
+Supabase ships fast and we endeavor to add all new features to existing projects wherever possible. In some cases, access to new features require upgrading or migrating your Supabase project.
+
+You can upgrade your project using `pg_upgrade` or by pausing and restoring your project.
+
+
+
+
+The Migrating and Upgrading guide has been divided into two sections. To migrate between Supabase projects, see [Migrating within Supabase](/docs/guides/platform/migrating-within-supabase).
+
+
+
+
+## `pg_upgrade`
+
+
+
+This upgrade method is currently in Beta.
+
+
+
+pg_upgrade performs an in-place upgrade on your database. For projects larger than 1GB, pg_upgrade is generally faster than a pause+restore cycle, and the speed advantage grows with the size of the database.
+
+1. Plan for an appropriate downtime window, and ensure you have reviewed the [caveats](#caveats) section of this document before executing the upgrade.
+1. Use the "Upgrade project" button on the [Infrastructure](https://supabase.com/dashboard/project/_/settings/infrastructure) section of your dashboard.
+
+Additionally, if a pg_upgrade upgrade should fail, your original DB would be brought back up online and be able to service requests.
+
+As a rough rule of thumb, pg_upgrade operates at ~100mbps (when executing an upgrade on your data). Using the size of your database, you can use this metric to derive an approximate sense of the downtime window necessary for the upgrade. During this window, you should plan for your DB and associated services to be unavailable.
+
+## Pause and restore
+
+
+
+We recommend using the pg_upgrade method, as it is faster, and more reliable. Additionally, only Free-tier projects are eligible to use the Pause + Restore method.
+
+
+
+When you pause and restore a project, the restored database includes the latest features. This method _does_ include downtime, so be aware that your project will be inaccessible for a short period of time.
+
+1. On the [General Settings](https://supabase.com/dashboard/project/_/settings/general) page in the Dashboard, click **Pause project**. You will be redirected to the home screen as your project is pausing. This process can take several minutes.
+1. After your project is paused, click **Restore project**. The restoration can take several minutes depending on how much data your database has. You will receive an email once the restoration is complete.
+
+Note that a pause + restore upgrade involves tearing down your project's resources before bringing them back up again. If the restore process should fail, manual intervention from Supabase support will be required to bring your project back online.
+
+### Caveats
+
+Regardless of the upgrade method, a few caveats apply:
+
+#### Logical replication
+
+If you are using logical replication, the replication slots will not be preserved by the upgrade process. You will need to manually recreate them after the upgrade with the method `pg_create_logical_replication_slot`. Refer to the Postgres docs on [Replication Management Functions](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-REPLICATION) for more details about the method.
+
+#### Breaking changes
+
+Newer versions of services can break functionality or change the performance characteristics you rely on. If your project is eligible for an upgrade, you will be able to find your current service versions from within [the Supabase dashboard](https://supabase.com/dashboard/project/_/settings/infrastructure).
+
+Breaking changes are generally only present in major version upgrades of Postgres and PostgREST. You can find their respective release notes at:
+
+- [Postgres](https://www.postgresql.org/docs/release/)
+- [PostgREST](https://github.com/PostgREST/postgrest/releases)
+
+If you are upgrading from a significantly older version, you will need to consider the release notes for any intermediary releases as well.
+
+#### Time Limits
+
+Starting from 2024-06-24, when a project is paused, users then have a 90-day window to restore the project on the platform from within Supabase Studio.
+
+The 90-day window allows Supabase to introduce platform changes that may not be backwards compatible with older backups. Unlike active projects, static backups can't be updated to accommodate such changes.
+
+During the 90-day restore window a paused project can be restored to the platform with a single button click from [Studio's dashboard page](https://supabase.com/dashboard/projects).
+
+
+
+After the 90-day restore window, you can download your project's backup file, and Storage objects from the project dashboard. See [restoring a backup locally](#restoring-a-downloaded-backup-locally) for instructions on how to load that backup locally to recover your data.
+
+
+
+If you upgrade to a paid plan while your project is paused, any expired one-click restore options are reenabled. Since the backup was taken outside the backwards compatibility window, it may fail to restore. If you have a problem restoring your backup after upgrading, contact [Support](/support).
+
+
+
+#### Restoring a Downloaded Backup Locally
+
+If the 90 day project restore window has expired but you need to access data contained within your project using SQL, you can attempt to restore the project into a local Postgres instance. Supabase publishes tooling that can be used for that purpose. Be aware that this workflow does not produce a complete Supabase environment with REST/Auth/Storage. Instead, it creates a standalone Postgres instance that is maximally compatible with your project's backup file to assist with recovering your data.
+
+Downloaded backups are plain text SQL files that can be executed in Postgres to restore your project's schema and data. If you need you access data within a backup we recommend restoring into a Postgres instance built using the Supabase open source build tooling to ensure that the local instance is as similar to the original project as possible. Here are the steps to start Supabase Postgres locally and restore the backup:
+
+Install [nix](https://nixos.org/):
+
+```sh
+curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
+```
+
+To identify your project's backup image version, reference the backup's download button following the `PG:` prefix:
+
+
+
+Given Postgres version `15.6.1.115`, start Postgres locally with the nix command:
+
+```sh
+nix run 'github:supabase/postgres?ref=15.6.1.115#start-server' 15
+```
+
+where `15` refers to the backup's Postgres major version.
+
+Note that the earliest Supabase Postgres version that supports a direct reference is `15.6.1.115`. If you don't know your roject's Postgres version or it is lower than `15.6.1.115` then omit the `ref` option to build our most recent image:
+
+```sh
+nix run 'github:supabase/postgres#start-server' -- 15
+```
+
+Then restore the backup file from a separate shell session:
+
+```sh
+nix run github:supabase/postgres#start-client -- --file
+```
+
+Which loads the backup into Postgres and drops you into a [psql](https://www.postgresql.org/docs/current/app-psql.html) prompt. For `start-client` you do not need to provide a reference to the backup's Postgres version.
+
+Once your project has been restored, connect to it with your preferred Postgres tooling using `host=localhost`, `user=postgres`, `port=5432`, and an empty password. This will allow you to inspect your saved data and export whatever is necessary.
+
+Errors encountered during the restore process are logged to the console by the `start-server` process. Review those errors and edit the backup file as needed until the project restores successfully. The backup file is a plain text sequence of SQL statements that recreate your database. To resolve common error classes like `relation "xxx" does not exist`, identify the line raising the exception and shuffle the `create ... "xxx"` statement before it. A failed restore leaves artifacts on the Postgres server so be sure to restart the `start-server` process between restore attempts.
+
+A Postgres database started with `start-server` command is not production ready and should never be used outside a local development workflow.
+
+#### Disk sizing
+
+When upgrading, the Supabase platform will "right-size" your disk based on the current size of the database. For example, if your database is 100GB in size, and you have a 200GB disk, the upgrade will reduce the disk size to 120GB (1.2x the size of your database).
+
+#### Objects dependent on Postgres extensions
+
+pg_upgrade does not support upgrading of databases containing reg\* data types referencing system OIDs.
+If you have created any objects that depend on the following extensions, you will need to recreate them after the upgrade.
+
+#### pg_cron records
+
+[pg_cron](https://github.com/citusdata/pg_cron#viewing-job-run-details) does not automatically clean up historical records. This can lead to extremely large `cron.job_run_details` tables if the records are not regularly pruned; you should clean unnecessary records from this table prior to an upgrade.
+
+During the `pg_upgrade` process, the `pg_cron` extension gets dropped and recreated. Prior to this process, the `cron.job_run_details` table is duplicated to avoid losing historical logs. The instantaneous disk pressure created by duplicating an extremely large details table can cause at best unnecessary performance degradation, or at worst, upgrade process failures.
+
+#### Extensions
+
+pg_upgrade does not currently support upgrading of databases using extensions older than the following versions:
+
+- TimescaleDB 2.16.1
+- plv8 3.1.10
+
+To upgrade to a newer version of Postgres, you will need to drop the extensions before the upgrade, and recreate them after the upgrade.
+
+#### Authentication method changes - deprecating md5 in favor of scram-sha-256
+
+The md5 hashing method has [known weaknesses](https://en.wikipedia.org/wiki/MD5#Security) that make it unsuitable for cryptography.
+As such, we are deprecating md5 in favor of [scram-sha-256](https://www.postgresql.org/docs/current/auth-password.html), which is the default and most secure authentication method used in the latest Postgres versions.
+
+We automatically migrate Supabase-managed roles' passwords to scram-sha-256 during the upgrade process, but you will need to manually migrate the passwords of any custom roles you have created, else you won't be able to connect using them after the upgrade.
+
+To identify roles using the md5 hashing method and migrate their passwords, you can use the following SQL statements after the upgrade:
+
+```sql
+-- List roles using md5 hashing method
+SELECT
+ rolname
+FROM pg_authid
+WHERE rolcanlogin = true
+ AND rolpassword LIKE 'md5%';
+
+-- Migrate a role's password to scram-sha-256
+ALTER ROLE WITH PASSWORD '';
+```
+
+#### Database size reduction
+
+As part of the upgrade process, maintenance operations such as [vacuuming](https://www.postgresql.org/docs/current/routine-vacuuming.html#ROUTINE-VACUUMING) are also executed. This can result in a reduction in the reported database size.
+
+#### Post-upgrade validation
+
+Supabase performs extensive pre- and post-upgrade validations to ensure that the database has been correctly upgraded. However, you should plan for your own application-level validations, as there might be changes you might not have anticipated, and this should be budgeted for when planning your downtime window.
diff --git a/apps/docs/content/guides/realtime/authorization.mdx b/apps/docs/content/guides/realtime/authorization.mdx
new file mode 100644
index 00000000000..437dd724acc
--- /dev/null
+++ b/apps/docs/content/guides/realtime/authorization.mdx
@@ -0,0 +1,349 @@
+---
+id: 'authorization'
+title: 'Realtime Authorization'
+description: 'Authorization for Supabase Realtime'
+sidebar_label: 'Authorization'
+---
+
+You can control client access to Realtime [Broadcast](/docs/guides/realtime/broadcast) and [Presence](/docs/guides/realtime/presence) by adding Row Level Security policies to the `realtime.messages` table. Each RLS policy can map to a specific action a client can take:
+
+- Control which clients can broadcast to a Channel
+- Control which clients can receive broadcasts from a Channel
+- Control which clients can publish their presence to a Channel
+- Control which clients can receive messages about the presence of other clients
+
+
+
+Realtime Authorization is in Public Alpha. To use Authorization for your Realtime Channels, use `supabase-js` version `v2.44.0` or later.
+
+
+
+## How it works
+
+Realtime uses the `messages` table in your database's `realtime` schema to generate access policies for your clients when they connect to a Channel topic.
+
+By creating RLS polices on the `realtime.messages` table you can control the access users have to a Channel topic, and features within a Channel topic.
+
+The validation is done when the user connects. When their WebSocket connection is established and a Channel topic is joined, their permissions are calculated based on:
+
+- The RLS policies on the `realtime.messages` table
+- The user information sent as part of their [Auth JWT](/docs/guides/auth/jwts)
+- The request headers
+- The Channel topic the user is trying to connect to
+
+When Realtime generates a policy for a client it performs a query on the `realtime.messages` table and then rolls it back. Realtime does not store any messages in your `realtime.messages` table.
+
+Using Realtime Authorization involves two steps:
+
+- In your database, create RLS policies on the `realtime.messages`
+- In your client, instantiate the Realtime Channel with the `config` option `private: true`
+
+
+
+Increased RLS complexity can impact database performance and connection time, leading to higher connection latency and decreased join rates.
+
+
+
+## Helper functions
+
+You can use the following helper functions when writing RLS policies:
+
+### `realtime.topic`
+
+Returns the Channel topic the user is attempting to connect to.
+
+```sql
+create policy "authenticated can read all messages on topic"
+on "realtime"."messages"
+for select
+to authenticated
+using (
+ (select realtime.topic()) = 'room-1'
+);
+```
+
+## Examples
+
+The following examples use this schema:
+
+```sql
+create table public.rooms (
+ id bigint generated by default as identity primary key,
+ topic text not null unique
+);
+
+alter table public.rooms enable row level security;
+
+create table public.profiles (
+ id uuid not null references auth.users on delete cascade,
+ email text NOT NULL,
+
+ primary key (id)
+);
+
+alter table public.profiles enable row level security;
+
+create table public.rooms_users (
+ user_id uuid references auth.users (id),
+ room_topic text references public.rooms (topic),
+ created_at timestamptz default current_timestamp
+);
+
+alter table public.rooms_users enable row level security;
+```
+
+### Broadcast
+
+The `extension` field on the `realtime.messages` table records the message type. For Broadcast messages, the value of `realtime.messages.extension` is `broadcast`. You can check for this in your RLS policies.
+
+#### Allow a user to join (and read) a Broadcast topic
+
+To join a Broadcast Channel, a user must have at least one read or write permission on the Channel topic.
+
+Here, we allow reads (`select`s) for users who are linked to the requested topic within the relationship table `public.room_users`:
+
+```sql
+create policy "authenticated can receive broadcast"
+on "realtime"."messages"
+for select
+to authenticated
+using (
+exists (
+ select
+ user_id
+ from
+ rooms_users
+ where
+ user_id = (select auth.uid())
+ and topic = (select realtime.topic())
+ and realtime.messages.extension in ('broadcast')
+ )
+);
+```
+
+Then, to join a topic with RLS enabled, instantiate the Channel with the `private` option set to `true`.
+
+
+
+ ```javascript
+ const channel = supabase.channel('room-1', {
+ config: { private: true },
+ })
+
+ channel
+ .on('broadcast', { event: 'test' }, (payload) => console.log(payload))
+ .subscribe((status, err) => {
+ if (status === 'SUBSCRIBED') {
+ console.log('Connected!')
+ } else {
+ console.error(err)
+ }
+ })
+ ```
+
+
+
+ ```dart
+ final channel = supabase.channel(
+ 'room-1',
+ opts: const RealtimeChannelConfig(private: true),
+ );
+
+ channel
+ .onBroadcast(event: 'test', callback: (payload) => print(payload))
+ .subscribe((status, err) {
+ if (status == RealtimeSubscribeStatus.subscribed) {
+ print('Connected!');
+ } else {
+ print(err);
+ }
+ });
+ ```
+
+
+
+ ```swift
+ let channel = supabase.channel("room-1") {
+ $0.isPrivate = true
+ }
+
+ Task {
+ for await payload in channel.broadcastStream(event: "test") {
+ print(payload)
+ }
+ }
+
+ await channel.subscribe()
+ print("Connected!")
+ ```
+
+
+
+ ```py
+ channel = realtime.channel(
+ "room-1", {"config": {"private": True}}
+ )
+
+ await channel.on_broadcast(
+ "test", callback=lambda payload: print(payload)
+ ).subscribe(
+ lambda state, err: (
+ print("Connected")
+ if state == RealtimeSubscribeStates.SUBSCRIBED
+ else print(err)
+ )
+ )
+ ```
+
+
+
+
+#### Allow a user to send a Broadcast message
+
+To authorize sending Broadcast messages, create a policy for `insert` where the value of `realtime.messages.extension` is `broadcast`.
+
+Here, we allow writes (sends) for users who are linked to the requested topic within the relationship table `public.room_users`:
+
+```sql
+create policy "authenticated can send broadcast on topic"
+on "realtime"."messages"
+for insert
+to authenticated
+with check (
+ exists (
+ select
+ user_id
+ from
+ rooms_users
+ where
+ user_id = (select auth.uid())
+ and topic = (select realtime.topic())
+ and realtime.messages.extension in ('broadcast')
+ )
+);
+```
+
+### Presence
+
+The `extension` field on the `realtime.messages` table records the message type. For Presence messages, the value of `realtime.messages.extension` is `presence`. You can check for this in your RLS policies.
+
+#### Allow users to listen to Presence messages on a Channel
+
+Create a policy for `select` on `realtime.messages` where `realtime.messages.extension` is `presence`.
+
+```sql
+create policy "authenticated can listen to presence in topic"
+on "realtime"."messages"
+for select
+to authenticated
+using (
+ exists (
+ select
+ user_id
+ from
+ rooms_users
+ where
+ user_id = (select auth.uid())
+ and topic = (select realtime.topic())
+ and realtime.messages.extension in ('presence')
+ )
+);
+```
+
+#### Allow users to send Presence messages on a channel
+
+To update the Presence status for a user create a policy for `insert` on `realtime.messages` where the value of `realtime.messages.extension` is `presence`.
+
+```sql
+create policy "authenticated can track presence on topic"
+on "realtime"."messages"
+for insert
+to authenticated
+with check (
+ exists (
+ select
+ user_id
+ from
+ rooms_users
+ where
+ user_id = (select auth.uid())
+ and name = (select realtime.topic())
+ and realtime.messages.extension in ('presence')
+ )
+);
+```
+
+### Presence and Broadcast
+
+Authorize both Presence and Broadcast by including both extensions in the `where` filter.
+
+#### Broadcast and Presence read
+
+Authorize Presence and Broadcast read in one RLS policy.
+
+```sql
+create policy "authenticated can listen to broadcast and presence on topic"
+on "realtime"."messages"
+for select
+to authenticated
+using (
+ exists (
+ select
+ user_id
+ from
+ rooms_users
+ where
+ user_id = (select auth.uid())
+ and topic = (select realtime.topic())
+ and realtime.messages.extension in ('broadcast', 'presence')
+ )
+);
+```
+
+#### Broadcast and Presence write
+
+Authorize Presence and Broadcast write in one RLS policy.
+
+```sql
+create policy "authenticated can send broadcast and presence on topic"
+on "realtime"."messages"
+for insert
+to authenticated
+with check (
+ exists (
+ select
+ user_id
+ from
+ rooms_users
+ where
+ user_id = (select auth.uid())
+ and name = (select realtime.topic())
+ and realtime.messages.extension in ('broadcast', 'presence')
+ )
+);
+```
+
+## Interaction with Postgres Changes
+
+Realtime Postgres Changes are separate from Channel authorization. The `private` Channel option does not apply to Postgres Changes.
+
+When using Postgres Changes with RLS, database records are sent only to clients who are allowed to read them based on your RLS policies.
+
+## Updating RLS policies
+
+Client access polices are cached for the duration of the connection. Your database is not queried for every Channel message.
+
+Realtime updates the access policy cache for a client based on your RLS polices when:
+
+- A client connects to Realtime and subscribes to a Channel
+- A new JWT is sent to Realtime from a client via the [`access_token` message](/docs/guides/realtime/protocol#access-token)
+
+If a new JWT is never received on the Channel, the client will be disconnected when the JWT expires.
+
+Make sure to keep the JWT expiration window short.
diff --git a/apps/docs/content/guides/realtime/broadcast.mdx b/apps/docs/content/guides/realtime/broadcast.mdx
index ae0ef21f32a..e741947d227 100644
--- a/apps/docs/content/guides/realtime/broadcast.mdx
+++ b/apps/docs/content/guides/realtime/broadcast.mdx
@@ -29,7 +29,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj
const SUPABASE_URL = 'https://.supabase.co'
const SUPABASE_KEY = ''
- const client = createClient(SUPABASE_URL, SUPABASE_KEY)
+ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
```
@@ -58,7 +58,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj
let SUPABASE_URL = "https://.supabase.co"
let SUPABASE_KEY = ""
- let client = SupabaseClient(supabaseURL: URL(string: SUPABASE_URL)!, supabaseKey: SUPABASE_KEY)
+ let supabase = SupabaseClient(supabaseURL: URL(string: SUPABASE_URL)!, supabaseKey: SUPABASE_KEY)
```
@@ -73,6 +73,17 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj
```
+
+
+ ```python
+ from supabase import create_client
+
+ URL = "https://.supabase.co"
+ KEY = ""
+ supabase = create_client(URL, KEY)
+ ```
+
+
### Listening to broadcast messages
@@ -91,7 +102,7 @@ You can provide a callback for the `broadcast` channel to receive message. In th
{/* prettier-ignore */}
```js
// Join a room/topic. Can be anything except for 'realtime'.
- const channelA = clientA.channel('room-1')
+ const channelA = supabase.channel('room-1')
// Simple function to log any messages we receive
function messageReceived(payload) {
@@ -112,6 +123,8 @@ You can provide a callback for the `broadcast` channel to receive message. In th
```dart
+ final channelA = supabase.channel('room-1');
+
// Simple function to log any messages we receive
void messageReceived(payload) {
print(payload);
@@ -128,7 +141,7 @@ You can provide a callback for the `broadcast` channel to receive message. In th
```swift
- let channelA = await clientA.channel("room-1")
+ let channelA = await supabase.channel("room-1")
// Listen for broadcast messages
let broadcastStream = await channelA.broadcast(event: "test")
@@ -144,7 +157,7 @@ You can provide a callback for the `broadcast` channel to receive message. In th
```kotlin
- val channelA = clientA.channel("room-1")
+ val channelA = supabase.channel("room-1")
//Listen for broadcast messages
val broadcastFlow: Flow = channelA.broadcastFlow("test")
@@ -157,6 +170,25 @@ You can provide a callback for the `broadcast` channel to receive message. In th
```
+
+ ```python
+ # Join a room/topic. Can be anything except for 'realtime'.
+ channel_a = supabase.channel('room-1')
+
+ # Simple function to log any messages we receive
+ def message_received(payload):
+ print(f"Broadcast received: {payload}")
+
+ # Subscribe to the Channel
+ channel_a
+ .on_broadcast(
+ 'test',
+ message_received
+ )
+ .subscribe()
+ ```
+
+
### Sending broadcast messages
@@ -175,7 +207,7 @@ You can provide a callback for the `broadcast` channel to receive message. In th
{/* prettier-ignore */}
```js
// Join a room/topic. Can be anything except for 'realtime'.
- const channelB = clientA.channel('room-1')
+ const channelB = supabase.channel('room-1')
channelB.subscribe((status) => {
// Wait for successful connection
@@ -195,6 +227,8 @@ You can provide a callback for the `broadcast` channel to receive message. In th
+ We can send Broadcast messages using `channelB.sendBroadcastMessage()`. Let's set up another client to send messages.
+
```dart
// Join a room/topic. Can be anything except for 'realtime'.
final channelB = supabase.channel('room-1');
@@ -219,7 +253,7 @@ You can provide a callback for the `broadcast` channel to receive message. In th
We can send Broadcast messages using `channelB.broadcast()`. Let's set up another client to send messages.
```swift
- let channelB = await clientA.channel("room-1")
+ let channelB = await supabase.channel("room-1")
await channelB.subscribe()
@@ -235,7 +269,7 @@ You can provide a callback for the `broadcast` channel to receive message. In th
We can send Broadcast messages using `channelB.broadcast()`. Let's set up another client to send messages.
```kotlin
- val channelB = clientA.channel("room-1")
+ val channelB = supabase.channel("room-1")
channelB.subscribe(blockUntilSubscribed = true) //You can also use the channelA.status flow instead, but this parameter will block the coroutine until the status is joined.
@@ -246,6 +280,28 @@ You can provide a callback for the `broadcast` channel to receive message. In th
```
+
+
+ We can send Broadcast messages using `channel_b.send_broadcast()`. Let's set up another client to send messages.
+
+ ```python
+ # Join a room/topic. Can be anything except for 'realtime'.
+ channel_b = supabase.channel('room-1')
+
+ def on_subscribe(status, err):
+ if status != RealtimeSubscribeStates.SUBSCRIBED:
+ return
+
+ # Send a message once the client is subscribed
+ channel_b.send_broadcast(
+ 'test',
+ { "message": 'hello, world' },
+ )
+
+ channel_b.subscribe(on_subscribe)
+ ```
+
+
Before sending messages we need to ensure the client is connected, which we have done within the `subscribe()` callback.
@@ -294,6 +350,8 @@ You can pass configuration options while initializing the Supabase Client.
+ By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `self` parameter to `true`.
+
```dart
final myChannel = supabase.channel(
'room-2',
@@ -368,6 +426,33 @@ You can pass configuration options while initializing the Supabase Client.
```
+
+
+ By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `self` parameter to `True`.
+
+ ```python
+ # Join a room/topic. Can be anything except for 'realtime'.
+ my_channel = supabase.channel('room-2', {"config": {"broadcast": {"self": True}}})
+
+ my_channel.on_broadcast(
+ 'test-my-messages',
+ lambda payload: print(payload)
+ )
+
+ def on_subscribe(status, err):
+ if status != RealtimeSubscribeStates.SUBSCRIBED:
+ return
+
+ # Send a message once the client is subscribed
+ channel_b.send_broadcast(
+ 'test-my-messages',
+ { "message": 'talking to myself' },
+ )
+
+ my_channel.subscribe(on_subscribe)
+ ```
+
+
### Acknowledge messages
@@ -385,7 +470,7 @@ You can pass configuration options while initializing the Supabase Client.
{/* prettier-ignore */}
```js
- const myChannel = clientD.channel('room-3', {
+ const myChannel = supabase.channel('room-3', {
config: {
broadcast: { ack: true },
},
@@ -433,7 +518,7 @@ You can pass configuration options while initializing the Supabase Client.
You can confirm that Realtime received your message by setting Broadcast's `acknowledgeBroadcasts` config to `true`.
```swift
- let myChannel = await clientD.channel("room-3") {
+ let myChannel = await supabase.channel("room-3") {
$0.broadcast.acknowledgeBroadcasts = true
}
@@ -460,6 +545,9 @@ You can pass configuration options while initializing the Supabase Client.
```
+
+ Unsupported in Python yet.
+
Use this to guarantee that the server has received the message before resolving `channelD.send`'s promise. If the `ack` config is not set to `true` when creating the channel, the promise returned by `channelD.send` will resolve immediately.
@@ -483,21 +571,21 @@ You can also send a Broadcast message by making an HTTP request to Realtime serv
```js
- const channel = client.channel('test-channel')
+ const channel = supabase.channel('test-channel')
// No need to subscribe to channel
channel
- .send({
- type: 'broadcast',
- event: 'test',
- payload: { message: 'Hi' },
- })
- .then((resp) => console.log(resp))
+ .send({
+ type: 'broadcast',
+ event: 'test',
+ payload: { message: 'Hi' },
+ })
+ .then((resp) => console.log(resp))
// Remember to clean up the channel
- client.removeChannel(channel)
+ supabase.removeChannel(channel)
```
@@ -545,4 +633,7 @@ You can also send a Broadcast message by making an HTTP request to Realtime serv
```
+
+ Unsupported in Python yet.
+
diff --git a/apps/docs/content/guides/realtime/concepts.mdx b/apps/docs/content/guides/realtime/concepts.mdx
index ca5a5672497..64bf45568a3 100644
--- a/apps/docs/content/guides/realtime/concepts.mdx
+++ b/apps/docs/content/guides/realtime/concepts.mdx
@@ -1,12 +1,12 @@
---
id: 'channels'
title: 'Realtime Concepts'
-description: 'Learn about channels and other features in Supabase Realtime'
-subtitle: 'Learn about channels and other features in Supabase Realtime'
+description: 'Learn about Channels and other extensions in Supabase Realtime'
+subtitle: 'Learn about Channels and other extensions in Supabase Realtime'
sidebar_label: 'Concepts'
---
-You can use Supabase Realtime to build real-time applications with collaborative/multiplayer functionality. It includes 3 core features:
+You can use Supabase Realtime to build real-time applications with collaborative/multiplayer functionality. It includes 3 core extensions:
- [Broadcast](/docs/guides/realtime/broadcast): sends rapid, ephemeral messages to other connected clients. You can use it to track mouse movements, for example.
- [Presence](/docs/guides/realtime/presence): sends user state between connected clients. You can use it to show an "online" status, which disappears when a user is disconnected.
@@ -21,9 +21,9 @@ When you initialize your Supabase Realtime client, you define a `topic` that uni
```js
import { createClient } from '@supabase/supabase-js'
-const client = createClient('https://.supabase.co', '')
+const supabase = createClient('https://.supabase.co', '')
-const roomOne = client.channel('room-one') // set your topic here
+const roomOne = supabase.channel('room-one') // set your topic here
```
## Broadcast
@@ -57,20 +57,12 @@ Each client maintains their own state, and this is then combined into a "shared
When a new client subscribes to a channel, it will immediately receive the channel's latest state in a single message because the state is held by the Realtime server.
-## Choosing between broadcast and presence
-
-We recommend using Broadcast by default, and then Presence when required. Presence utilizes an in-memory conflict-free replicated data type (CRDT) to track and synchronize shared state in an eventually consistent manner. It computes the difference between existing state and new state changes and sends the necessary updates to clients via Broadcast. This is computationally heavy, so you should use it sparingly. If you use Presence, it's best to throttle your changes so that you are sending updates less frequently.
-
-## Realtime extensions
-
-Channels provide a generic networking solution. Supabase Realtime is designed to leverage this networking primitive with "extensions". We currently support one extension: Postgres changes.
-
-### Postgres changes
+## Postgres Changes
The Postgres Changes extension listens for database changes and sends them to clients. Clients are required to subscribe with a JWT dictating which changes they are allowed to receive based on the database's [Row Level Security](/docs/guides/database/postgres/row-level-security).
```js
-const allChanges = client
+const allChanges = supabase
.channel('schema-db-changes')
.on(
'postgres_changes',
@@ -86,3 +78,7 @@ const allChanges = client
Anyone with access to a valid JWT signed with the project's JWT secret is able to listen to your database's changes, unless tables have [Row Level Security](/docs/guides/database/postgres/row-level-security) enabled and policies in place.
Clients can choose to receive `INSERT`, `UPDATE`, `DELETE`, or `*` (all) changes for all changes in a schema, a table in a schema, or a column's value in a table. Your clients should only listen to tables in the `public` schema and you must first enable the tables you want your clients to listen to.
+
+## Choosing between Broadcast and Presence
+
+We recommend using Broadcast by default, and then Presence when required. Presence utilizes an in-memory conflict-free replicated data type (CRDT) to track and synchronize shared state in an eventually consistent manner. It computes the difference between existing state and new state changes and sends the necessary updates to clients via Broadcast. This is computationally heavy, so you should use it sparingly. If you use Presence, it's best to throttle your changes so that you are sending updates less frequently.
diff --git a/apps/docs/content/guides/realtime/postgres-changes.mdx b/apps/docs/content/guides/realtime/postgres-changes.mdx
index 512d4b3b9fe..51a50672d38 100644
--- a/apps/docs/content/guides/realtime/postgres-changes.mdx
+++ b/apps/docs/content/guides/realtime/postgres-changes.mdx
@@ -126,7 +126,7 @@ In this example we'll set up a database table, secure it with Row Level Security
```js
import { createClient } from '@supabase/supabase-js'
- const client = createClient(
+ const supabase = createClient(
'https://.supabase.co',
''
)
@@ -152,7 +152,7 @@ In this example we'll set up a database table, secure it with Row Level Security
```js
- const channelA = client
+ const channelA = supabase
.channel('schema-db-changes')
.on(
'postgres_changes',
@@ -209,7 +209,7 @@ Subscribe to specific schema events using the `schema` parameter:
{/* prettier-ignore */}
```js
-const changes = client
+const changes = supabase
.channel('schema-db-changes')
.on(
'postgres_changes',
@@ -277,6 +277,18 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('schema-db-changes').on_postgres_changes(
+ "*",
+ schema="public",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -296,7 +308,7 @@ The channel name can be any string except 'realtime'.
Use the `event` parameter to listen only to database `INSERT`s:
```js
-const changes = client
+const changes = supabase
.channel('schema-db-changes')
.on(
'postgres_changes',
@@ -358,6 +370,18 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('schema-db-changes').on_postgres_changes(
+ "INSERT", # Listen only to INSERTs
+ schema="public",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -377,7 +401,7 @@ The channel name can be any string except 'realtime'.
Use the `event` parameter to listen only to database `UPDATE`s:
```js
-const changes = client
+const changes = supabase
.channel('schema-db-changes')
.on(
'postgres_changes',
@@ -439,6 +463,18 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('schema-db-changes').on_postgres_changes(
+ "UPDATE", # Listen only to UPDATEs
+ schema="public",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -458,7 +494,7 @@ The channel name can be any string except 'realtime'.
Use the `event` parameter to listen only to database `DELETE`s:
```js
-const changes = client
+const changes = supabase
.channel('schema-db-changes')
.on(
'postgres_changes',
@@ -520,6 +556,18 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('schema-db-changes').on_postgres_changes(
+ "DELETE", # Listen only to DELETEs
+ schema="public",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -539,7 +587,7 @@ Subscribe to specific table events using the `table` parameter:
```js
-const changes = client
+const changes = supabase
.channel('table-db-changes')
.on(
'postgres_changes',
@@ -606,6 +654,19 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="todos",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -693,6 +754,23 @@ val userChanges = myChannel.postgresChangeFlow(schema = "
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "*",
+ schema="public",
+ table="messages"
+ callback=lambda payload: print(payload)
+).on_postgres_changes(
+ "INSERT",
+ schema="public",
+ table="users",
+ callback=lambda payload: print(payload)
+).subscribe()
+```
+
@@ -710,7 +788,7 @@ Use the `filter` parameter for granular changes:
```js
-const changes = client
+const changes = supabase
.channel('table-filter-changes')
.on(
'postgres_changes',
@@ -784,6 +862,20 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "INSERT",
+ schema="public",
+ table="todos",
+ filter="id=eq.1",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -879,6 +971,20 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="messages",
+ filter="body=eq.hey",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -973,6 +1079,20 @@ supabase.realtime.connect()
myChannel.join()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "INSERT",
+ schema="public",
+ table="messages",
+ filter="body=neq.bye",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -1066,6 +1186,20 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "INSERT",
+ schema="public",
+ table="profiles",
+ filter="age=lt.65",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -1159,6 +1293,20 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="profiles",
+ filter="age=lte.65",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -1252,6 +1400,20 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="products",
+ filter="quantity=gt.10",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -1345,6 +1507,20 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="products",
+ filter="quantity=gte.10",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -1438,6 +1614,20 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="products",
+ filter="name=in.(red, blue, yellow)",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -1587,6 +1777,22 @@ changes
myChannel.subscribe()
```
+
+
+
+```python
+supabase.realtime.set_auth('your-custom-jwt')
+
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="products",
+ filter="name=in.(red, blue, yellow)",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
@@ -1634,6 +1840,13 @@ In Kotlin, you have to update the token manually per channel:
myChannel.updateAuth("fresh-token")
```
+
+
+
+```python
+supabase.realtime.set_auth('fresh-token')
+```
+
diff --git a/apps/docs/content/guides/realtime/presence.mdx b/apps/docs/content/guides/realtime/presence.mdx
index 02c6cf182ad..e7710fc4614 100644
--- a/apps/docs/content/guides/realtime/presence.mdx
+++ b/apps/docs/content/guides/realtime/presence.mdx
@@ -70,6 +70,18 @@ val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
}
```
+
+
+
+```python
+from supabase import create_client
+
+SUPABASE_URL = 'https://.supabase.co'
+SUPABASE_KEY = ''
+
+supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
+```
+
@@ -156,6 +168,21 @@ presenceFlow
roomOne.subscribe()
```
+
+
+
+Listen to the `sync`, `join`, and `leave` events triggered whenever any client joins or leaves the channel or changes their slice of state:
+
+```python
+room_one = supabase.channel('room_01')
+
+room_one
+ .on_presence_sync(lambda: print('sync', room_one.presenceState()))
+ .on_presence_join(lambda key, curr_presences, joined_presences: print('join', key, curr_presences, joined_presences))
+ .on_presence_leave(lambda key, curr_presences, left_presences: print('leave', key, curr_presences, left_presences))
+ .subscribe()
+```
+
@@ -249,6 +276,26 @@ roomOne.subscribe(blockUntilSubscribed = true) //You can also use the roomOne.st
roomOne.track(userStatus)
```
+
+
+
+```python
+room_one = supabase.channel('room_01')
+
+user_status = {
+ "user": 'user-1',
+ "online_at": datetime.datetime.now().isoformat(),
+}
+
+def on_subscribe(status, err):
+ if status != RealtimeSubscribeStates.SUBSCRIBED:
+ return
+
+ room_one.track(user_status)
+
+room_one.subscribe(on_subscribe)
+```
+
@@ -308,6 +355,13 @@ suspend fun untrackPresence() {
untrackPresence()
```
+
+
+
+```python
+room_one.untrack()
+```
+
@@ -370,5 +424,18 @@ val channelC = supabase.channel("test") {
}
```
+
+
+
+```python
+channel_c = supabase.channel('test', {
+ "config": {
+ "presence": {
+ "key": 'userId-123',
+ },
+ },
+})
+```
+
diff --git a/apps/docs/content/guides/realtime/quotas.mdx b/apps/docs/content/guides/realtime/quotas.mdx
index 2fc29c352e5..1fa2a95daba 100644
--- a/apps/docs/content/guides/realtime/quotas.mdx
+++ b/apps/docs/content/guides/realtime/quotas.mdx
@@ -17,16 +17,16 @@ Upgrade your plan to increase your quotas. Without a spend cap, or on an Enterpr
| | Free | Pro | Pro (no spend cap) | Team | Enterprise |
| -------------------------------------------------------------------------------------- | ----- | ----- | ------------------ | ------ | ---------- |
-| **Concurrent clients** | 200 | 500 | 10,000 | 10,000 | 10,000 |
-| **Messages per second** | 100 | 500 | 2,500 | 2,500 | 2,500 |
-| **Channel joins per second** | 100 | 500 | 2,500 | 2,500 | 2,500 |
-| **Channels per client** | 100 | 100 | 100 | 100 | 100 |
-| **Presence keys per object** | 10 | 10 | 10 | 10 | 10 |
-| **Presence messages per second** | 20 | 50 | 1,000 | 1,000 | 1,000 |
-| **Broadcast payload size KB** | 256 | 3,000 | 3,000 | 3,000 | 3,000 |
-| **Postgres change payload size KB ([**read more**](#postgres-changes-payload-quota))** | 1,024 | 1,024 | 1,024 | 1,024 | 1,024 |
+| **Concurrent clients** | 200 | 500 | 10,000 | 10,000 | 10,000+ |
+| **Messages per second** | 100 | 500 | 2,500 | 2,500 | 2,500+ |
+| **Channel joins per second** | 100 | 500 | 2,500 | 2,500 | 2,500+ |
+| **Channels per client** | 100 | 100 | 100 | 100 | 100+ |
+| **Presence keys per object** | 10 | 10 | 10 | 10 | 10+ |
+| **Presence messages per second** | 20 | 50 | 1,000 | 1,000 | 1,000+ |
+| **Broadcast payload size KB** | 256 | 3,000 | 3,000 | 3,000 | 3,000+ |
+| **Postgres change payload size KB ([**read more**](#postgres-changes-payload-quota))** | 1,024 | 1,024 | 1,024 | 1,024 | 1,024+ |
-Beyond the Free and Pro plan you can customize your quotas by [contacting support](https://supabase.com/dashboard/support/new).
+Beyond the Free and Pro Plan you can customize your quotas by [contacting support](https://supabase.com/dashboard/support/new).
## Quota errors
diff --git a/apps/docs/content/guides/realtime/realtime-listening-flutter.mdx b/apps/docs/content/guides/realtime/realtime-listening-flutter.mdx
new file mode 100644
index 00000000000..cc7eb48b947
--- /dev/null
+++ b/apps/docs/content/guides/realtime/realtime-listening-flutter.mdx
@@ -0,0 +1,17 @@
+---
+id: 'realtime-listening-flutter'
+title: 'Listening to Postgres Changes with Flutter'
+description: 'Listening to real-time changes on the database with Flutter'
+sidebar_label: 'Videos'
+---
+
+The Postgres Changes extension listens for database changes and sends them to clients which enables you to receive database changes in real-time.
+
+
+
+
diff --git a/apps/docs/content/guides/realtime/realtime-user-presence.mdx b/apps/docs/content/guides/realtime/realtime-user-presence.mdx
new file mode 100644
index 00000000000..2c8fcdc213c
--- /dev/null
+++ b/apps/docs/content/guides/realtime/realtime-user-presence.mdx
@@ -0,0 +1,19 @@
+---
+id: 'realtime-user-presence'
+title: 'Using Realtime Presence with Flutter'
+description: 'Track online users with Supabase Realtime Presence'
+sidebar_label: 'Videos'
+---
+
+Use Supabase Presence to display the currently online users on your Flutter application.
+
+Displaying the list of currently online users is a common feature for real-time collaborative applications. Supabase Presence makes it easy to track users joining and leaving the session so that you can easily make a collaborative app.
+
+
{
[
+ {
+ title: 'Auth0',
+ icon: '/docs/img/icons/auth0-icon',
+ href: '/guides/resources/migrating-to-supabase/auth0',
+ description: 'Move your auth users from Auth0 to a Supabase project.',
+ hasLightIcon: true,
+ },
{
title: 'Firebase Auth',
icon: '/docs/img/icons/firebase-icon',
diff --git a/apps/docs/content/guides/resources/examples.mdx b/apps/docs/content/guides/resources/examples.mdx
deleted file mode 100644
index 177df12f0c3..00000000000
--- a/apps/docs/content/guides/resources/examples.mdx
+++ /dev/null
@@ -1,173 +0,0 @@
----
-id: 'examples'
-title: 'Examples and Resources'
-description: 'Examples you can use to get started with Supabase'
-video: 'https://www.youtube.com/v/7uKQBl9uZ00'
----
-
-{/* */}
-
-We have a [set of examples](https://github.com/supabase/supabase/tree/master/examples) in our [main repository](https://github.com/supabase/supabase) to help you get started.
-
-## Featured
-
-## Build a Twitter clone with the Next.js App Router and Supabase
-
-By [Jon Meyers](https://twitter.com/jonmeyers_io)
-
-
-
-
-
-### Supabase crash course
-
-By [Traversy Media](https://www.youtube.com/watch?v=7uKQBl9uZ00).
-
-
-
-
-
-### Build an app with Supabase and Next.js
-
-By [@jlengstorf](https://twitter.com/jlengstorf) and [@jonmeyers_io](https://twitter.com/jonmeyers_io).
-
-
-
-
-
-### Is Supabase legit
-
-By [Fireship](https://www.youtube.com/watch?v=WiwfiVdfRIc).
-
-
-
-
-
-## Official examples
-
-### Todo list
-
-Build a basic Todo List with Supabase and your favorite frontend framework:
-
-- [Expo Todo List.](https://github.com/supabase/examples/tree/main/supabase-js-v1/todo-list/expo-todo-list)
-- [Next.js Todo List.](https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list)
-- [React Todo List.](https://github.com/supabase/examples/tree/main/supabase-js-v1/todo-list/react-todo-list)
-- [Svelte Todo List.](https://github.com/supabase/supabase/tree/master/examples/todo-list/sveltejs-todo-list)
-- [Vue 3 Todo List (Typescript).](https://github.com/supabase/examples/tree/main/supabase-js-v1/todo-list/vue3-ts-todo-list)
-- [Angular Todo List.](https://github.com/supabase/examples/tree/main/supabase-js-v1/todo-list/angular-todo-list)
-- [Nuxt 3 Todo List.](https://github.com/nuxt-modules/supabase/tree/main/demo)
-
-### Auth examples
-
-- [Supabase Auth with vanilla JavaScript.](https://github.com/supabase/examples/tree/main/supabase-js-v1/auth/javascript-auth). Use Supabase without any frontend frameworks.
-- [Supabase Auth with Next.js](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-user-management).
-- [Supabase Auth with RedwoodJS.](https://redwood-playground-auth.netlify.app/supabase) Try out Supabase authentication in the [RedwoodJS](https://redwoodjs.com) Authentication Playground complete with OAuth support and code samples.
-
-### Collaborative
-
-- [Next.js Slack Clone.](https://github.com/supabase/examples/tree/main/supabase-js-v1/slack-clone/nextjs-slack-clone)
-
-## Community
-
-### Courses
-
-- [Build a Twitter Clone with the Next.js App Router and Supabase - free egghead course](https://egghead.io/courses/build-a-twitter-clone-with-the-next-js-app-router-and-supabase-19bebadb)
-
-### Libraries
-
-- D (in development): [GitHub](https://github.com/csharpdf/dupabase)
-- Flutter (in development): [GitHub](https://github.com/supabase/supabase-flutter)
-- Python (in development): [GitHub](https://github.com/supabase/supabase-py)
-- C# (in development): [GitHub](https://github.com/supabase/supabase-csharp)
-- Kotlin (in development): [GitHub](https://github.com/supabase-community/supabase-kt)
-- `useSupabase`: Supabase React Hooks. [GitHub](https://github.com/gbibeaul/use-supabase)
-- `vue-supabase`: Supabase Vue wrapper. [GitHub](https://github.com/supabase/vue-supabase)
-- `vue-3-supabase`: Supabase Vue 3 wrapper. [GitHub](https://github.com/DidoMarchet/vue-3-supabase)
-- `nuxt-supabase`: Supabase Nuxt wrapper. [GitHub](https://github.com/supabase-community/nuxt-supabase)
-- `@nuxtjs/supabase`: Supabase Nuxt 3 module. [GitHub](https://github.com/nuxt-modules/supabase)
-- `react-supabase`: Supabase React Hooks. [Docs](https://react-supabase.vercel.app) [GitHub](https://github.com/tmm/react-supabase)
-- ` @triniwiz/nativescript-supabase`: Supabase NativeScript. [GitHub](https://github.com/triniwiz/nativescript-plugins/tree/master/packages/nativescript-supabase)
-
-### Guides
-
-- Supabase Auth with Redwood. [Docs](https://redwoodjs.com/tutorial/authentication)
-- Supabase Auth with Sapper SSR. [Blog](https://dev.to/jakobbouchard/how-to-setup-supabase-auth-with-sapper-ssr-13od)
-- Switch from Firebase Auth to Supabase Auth. [Blog](https://msyyn.medium.com/switch-from-firebase-auth-to-supabase-auth-the-open-source-firebase-alternative-509746952b1b)
-- Setting up Umami Analytics with Supabase. [Blog](https://dev.to/jakobbouchard/setting-up-umami-with-vercel-and-supabase-3a73)
-- Creating New Supabase Users In Next.js. [Blog](https://www.aboutmonica.com/blog/creating-new-supabase-users-in-next-js)
-- Creating Protected Routes In Next.js With Supabase. [Blog](https://www.aboutmonica.com/blog/creating-protected-routes-in-next-js-with-supabase)
-- Migrate from Google Cloud Storage (GCS) to Supabase Storage [Gist](https://gist.github.com/danalloway/86f79efd5ca336ff7364554ac3104014)
-- Subscriptions with Supabase and Stripe Billing [Blog](https://www.sandromaglione.com/2021/04/29/supabase-auth-create-stripe-customer-subscription-supabase-stripe-billing-part-1/)
-- Flutter Supabase Authentication [Blog](https://www.sandromaglione.com/2021/04/24/flutter-supabase-authentication/)
-- Supabase in 6 minutes [Video](https://www.youtube.com/watch?v=c8DNV9yl0mg)
-- Supabase React Auth UI Tutorial [Video](https://youtu.be/6ch1PtIqCUw)
-- Supabase React File Upload Tutorial [Video](https://youtu.be/HvOvdD2nX1k)
-- Let's build SupaAuth - Build an Authentication System using Supabase, Next.js and Typescript [6-part Blog Series](https://aalam.in/blog/supabase-auth-intro-setup-next)
-- In-depth self-hosting guide using Nginx [Blog](https://dev.to/chronsyn/self-hosting-with-supabase-1aii)
-- Build an Email and Social Auth for Next JS with Supabase, Tailwind CSS 3.0 and TypeScript [Blog](https://creativedesignsguru.com/next-js-supabase-auth/)
-- Link Shortener using Supabase and Ory [3-part Blog Series](https://www.ory.sh/tutorial-url-shortener-supabase-ory-integration-backend/)
-- Building a CRUD API with FastAPI and Supabase: A Step-by-Step Guide [Blog](https://blog.theinfosecguy.xyz/building-a-crud-api-with-fastapi-and-supabase-a-step-by-step-guide)
-
-### Example apps
-
-- Supabase + Stripe + Next.js. [GitHub](https://github.com/vercel/nextjs-subscription-payments)
-- Supabase + Svelte Trello clone. [GitHub](https://github.com/joshnuss/supabase-kanban)
-- Supabase + Expo Starter. [GitHub](https://github.com/codingki/react-native-expo-template/tree/master/template-typescript-bottom-tabs-supabase-auth-flow)
-- Supabase + Nest.js. [GitHub](https://github.com/hiro1107/nestjs-supabase-auth)
-- Supabase + Cloudflare Workers. [GitHub](https://github.com/supabase/examples/tree/main/supabase-js-v1/with-cloudflare-workers)
-- Supabase + Cloudflare Workers + Webpack. [GitHub](https://github.com/signalnerve/supabase-workers-proxy)
-- Realtime chat app with Supabase + React. [GitHub](https://github.com/shwosner/realtime-chat-supabase-react)
-- Repository.surf: GitHub insights dashboard. [GitHub](https://github.com/supabase/repository.surf)
-- Supabase + React Native Instagram Clone. [GitHub](https://github.com/NiketanG/instaclone)
-- KeepLink: Simple bookmark service with tags and archive. [GitHub](https://github.com/fengkx/keeplink)
-- Supabase + Next.js (Next.js Starter Kit) [GitHub](https://github.com/one-aalam/next-starter-kit/tree/auth-supabase)
-- Supabase + Svelte (Svelte Starter Kit) [GitHub](https://github.com/one-aalam/svelte-starter-kit)
-- Supabase + SolidJS (SolidJS Starter Kit) [GitHub](https://github.com/one-aalam/solid-starter-kit)
-- Supabase + Nuxt3 (Nuxt Starter Kit) [GitHub](https://github.com/one-aalam/nuxt-starter-kit)
-- Supabase + Remix (Remix Starter Kit) [GitHub](https://github.com/one-aalam/remix-starter-kit)
-- Supabase + Angular (Angular Starter Kit) [GitHub](https://github.com/one-aalam/ng-starter-kit)
-- Supabase + Nuxt3 + nuxtjs/supabase [Github](https://github.com/nuxt-modules/supabase/tree/main/demo)
-- Supabase + Ory Kratos & Ory Oathkeeper [Github](https://github.com/ory/examples/tree/master/kratos-keto-oathkeeper-supabase)
-- Supabase + Ory Cloud [Github](https://github.com/ory/examples/tree/master/supabase-ory-cloud)
-- Supabase Auth with React Native + Next.js (Monorepo) [Github](https://github.com/mateoguzmana/react-native-next-supabase-auth-monorepo)
-- Supabase Auth with Nuxt3 [Github](https://github.com/zackha/supaAuth)
-- Supabase Auth with Hono [Github](https://github.com/CarlosZiegler/hono-supabase)
-
-### Blog posts
-
-- Realtime Subscriptions using Vue + Supabase. [Blog](https://dev.to/ftonato/realtime-subscriptions-using-vue-supabase-1e11)
-- Creating a microblog using Vue + Supabase. [Blog](https://dev.to/ftonato/creating-a-microblog-using-vue-supabase-31p)
-- Track Real-time Page Views. [Blog](https://codebycorey.com/blog/page-views-nextjs-supabase)
-- Supabase as a Sentry alternative. [Blog](http://kopi.cloud/blog/2021/sentry-supabase/)
-- Using Supabase with Chartbrew. [Blog](https://chartbrew.com/blog/how-to-visualize-your-supabase-data-with-chartbrew/)
-- Authentication with Supabase and React. [Blog](https://hyperfoo.io/posts/supabase-authentication-react)
-- Supabase Schema Visualizer. [Blog](https://dev.to/zernonia/supabase-schema-visualizer-no-installation-login-49kg)
-- Create a real-time UI using Next.js + Supabase. [Blog](https://pablopunk.com/posts/how-to-create-a-real-time-ui-with-nextjs-and-supabase)
-- How to add Twitter auth quickly with Supabase to your Next.js site ⚡ [Blog](https://blog.avneesh.tech/how-to-add-twitter-auth-quickly-with-supabase-to-your-nextjs-site)
-- Under the hood: Architecture and Technology Stack of Supabase ⚡ [Blog](https://www.workingsoftware.dev/tech-stack-and-architecture-of-supabase/)
-
-### Podcasts
-
-- [Software Engineering Daily](https://softwareengineeringdaily.com/2020/10/15/supabase-open-source-firebase-with-paul-copplestone/)
-- [Heavy Bit](https://www.heavybit.com/library/podcasts/jamstack-radio/ep-71-open-source-firebase-alternative-with-paul-copplestone-of-supabase/)
-- [Log Rocket](https://podrocket.logrocket.com/9)
-- [FS Jam](https://fsjam.org/episodes/episode-33-supabase-with-paul-copplestone)
diff --git a/apps/docs/content/guides/self-hosting/docker.mdx b/apps/docs/content/guides/self-hosting/docker.mdx
index 4a73358c096..d45e1886573 100644
--- a/apps/docs/content/guides/self-hosting/docker.mdx
+++ b/apps/docs/content/guides/self-hosting/docker.mdx
@@ -67,6 +67,8 @@ docker compose up -d
+If you are using rootless docker, edit `.env` and set `DOCKER_SOCKET_LOCATION` to your docker socket location. For example: `/run/user/1000/docker.sock`. Otherwise, you will see an error like `container supabase-vector exited (0)`.
+
After all the services have started you can see them running in the background:
```sh
@@ -105,14 +107,25 @@ You can add new Functions as `volumes/functions//index.ts`. Resta
### Accessing Postgres
-You can connect to the Postgres database locally on port `5432`. For example, if you have `psql` on your local machine you can run:
+By default, the Supabase stack runs the [Supavisor](https://supabase.github.io/supavisor/development/docs/) connection pooler. Supavisor provides efficient management of database connections.
+
+You can connect to the Postgres database using the following methods:
+
+1. For session-based connections (equivalent to direct PostgreSQL connections):
```bash
-psql -h 127.0.0.1 -p 5432 -d postgres -U postgres
+psql 'postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@localhost:5432/postgres'
```
-The default password is `your-super-secret-and-long-postgres-password`. You should change this as soon as possible using the [instructions below](#update-secrets). By default the database is not accessible from outside the local machine. You can [change this](#exposing-your-postgres-database) by updating the `docker-compose.yml` file.
+2. For pooled transactional connections:
+```bash
+psql 'postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@localhost:6543/postgres'
+```
+
+The default tenant ID is `your-tenant-id`, and the default password is `your-super-secret-and-long-postgres-password`. You should change these as soon as possible using the [instructions below](#update-secrets).
+
+By default, the database is not accessible from outside the local machine but the pooler is. You can [change this](#exposing-your-postgres-database) by updating the `docker-compose.yml` file.
## Securing your services
@@ -143,6 +156,7 @@ Update the `.env` file with your own secrets. In particular, these are required:
- `JWT_SECRET`: used by PostgREST and GoTrue, among others.
- `SITE_URL`: the base URL of your site.
- `SMTP_*`: mail server credentials. You can use any SMTP server.
+- `POOLER_TENANT_ID`: the tenant-id that will be used by supavisor pooler for your connection string
You will need to [restart](#restarting-all-services) the services for the changes to take effect.
@@ -238,6 +252,7 @@ If the tool doesn't exist, we build and open source it ourselves.
- [Storage](https://github.com/supabase/storage-api) provides a RESTful interface for managing Files stored in S3, using Postgres to manage permissions.
- [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc.
- [PostgreSQL](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
+- [Supavisor](https://github.com/supabase/supavisor) is a scalable connection pooler for Postgres, allowing for efficient management of database connections.
For the system to work cohesively, some services require additional configuration within the Postgres database. For example, the APIs and Auth system require several [default roles](/docs/guides/database/postgres/roles) and the `pgjwt` Postgres extension.
@@ -253,6 +268,7 @@ Each system has a number of configuration options which can be found in the rele
- [GoTrue](https://github.com/supabase/gotrue)
- [Storage](https://github.com/supabase/storage-api)
- [Kong](https://docs.konghq.com/gateway/latest/install/docker/)
+- [Supavisor](https://supabase.github.io/supavisor/development/docs/)
These configuration items are generally added to the `env` section of each service, inside the `docker-compose.yml` section. If these configuration items are sensitive, they should be stored in a [secret manager](/docs/guides/self-hosting#managing-your-secrets) or using an `.env` file and then referenced using the `${}` syntax.
@@ -311,11 +327,21 @@ You can find all the available options in the [storage repository](https://githu
By default, `docker compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime. You can configure `log_min_messages` using any of the Postgres [Severity Levels](https://www.postgresql.org/docs/current/runtime-config-logging.html#RUNTIME-CONFIG-SEVERITY-LEVELS).
+#### Accessing Postgres through Supavisor
+
+By default, the Postgres database is accessible through the Supavisor connection pooler. This allows for more efficient management of database connections. You can connect to the pooled database using the `POOLER_PROXY_PORT_TRANSACTION` port and `POSTGRES_PORT` for session based connections.
+
+For more information on configuring and using Supavisor, see the [Supavisor documentation](https://supabase.github.io/supavisor/).
+
#### Exposing your Postgres database
-By default, the Postgres database is only accessible locally. If you want to expose it to the outside world, you can update the `docker-compose.yml` file and remove the `127.0.0.1:` prefix from the `ports` section:
+If you need direct access to the Postgres database without going through Supavisor, you can expose it by updating the `docker-compose.yml` file:
```yaml docker-compose.yml
+# Comment or remove the supavisor section of the docker-compose file
+# supavisor:
+# ports:
+# ...
db:
ports:
- ${POSTGRES_PORT}:${POSTGRES_PORT}
diff --git a/apps/docs/content/guides/storage/buckets/creating-buckets.mdx b/apps/docs/content/guides/storage/buckets/creating-buckets.mdx
index b7684933de7..b441adc2a91 100644
--- a/apps/docs/content/guides/storage/buckets/creating-buckets.mdx
+++ b/apps/docs/content/guides/storage/buckets/creating-buckets.mdx
@@ -62,6 +62,30 @@ void main() async {
[Reference.](https://pub.dev/documentation/storage_client/latest/storage_client/SupabaseStorageClient/createBucket.html)
+
+
+
+```swift
+try await supabase.storage.createBucket(
+ "avatars",
+ options: BucketOptions(public: true)
+)
+```
+
+[Reference.](/docs/reference/swift/storage-createbucket)
+
+
+
+
+```python
+supabase.storage.create_bucket(
+ 'avatars',
+ options={"public": True}
+)
+```
+
+[Reference.](/docs/reference/python/storage-createbucket)
+
diff --git a/apps/docs/content/guides/storage/cdn/smart-cdn.mdx b/apps/docs/content/guides/storage/cdn/smart-cdn.mdx
index e6b9f235cd9..dffa1f278af 100644
--- a/apps/docs/content/guides/storage/cdn/smart-cdn.mdx
+++ b/apps/docs/content/guides/storage/cdn/smart-cdn.mdx
@@ -11,7 +11,7 @@ Moreover, the Smart CDN achieves a greater cache hit rate by shielding the origi
-Smart CDN caching is automatically enabled for [Pro plan and above](https://supabase.com/pricing).
+Smart CDN caching is automatically enabled for [Pro Plan and above](https://supabase.com/pricing).
diff --git a/apps/docs/content/guides/storage/debugging/logs.mdx b/apps/docs/content/guides/storage/debugging/logs.mdx
index ddf0e93dabb..3139523d717 100644
--- a/apps/docs/content/guides/storage/debugging/logs.mdx
+++ b/apps/docs/content/guides/storage/debugging/logs.mdx
@@ -12,27 +12,41 @@ Accessing the [Storage Logs](/dashboard/project/__/logs/explorer?q=select+id%2C+
#### Filter by status 5XX error
```sql
-select id, storage_logs.timestamp, event_message, r.statusCode, e.message as errorMessage e.raw as rawError
- from storage_logs
+select
+ id,
+ storage_logs.timestamp,
+ event_message,
+ r.statusCode,
+ e.message as errorMessage,
+ e.raw as rawError
+from
+ storage_logs
cross join unnest(metadata) as m
cross join unnest(m.res) as r
cross join unnest(m.error) as e
- where r.statusCode >= 500
- order by timestamp desc
- limit 100
+where r.statusCode >= 500
+order by timestamp desc
+limit 100;
```
#### Filter by status 4XX error
```sql
-select id, storage_logs.timestamp, event_message, r.statusCode, e.message as errorMessage e.raw as rawError
- from storage_logs
+select
+ id,
+ storage_logs.timestamp,
+ event_message,
+ r.statusCode,
+ e.message as errorMessage,
+ e.raw as rawError
+from
+ storage_logs
cross join unnest(metadata) as m
cross join unnest(m.res) as r
cross join unnest(m.error) as e
- where r.statusCode >= 400 and r.statusCode < 500
- order by timestamp desc
- limit 100
+where r.statusCode >= 400 and r.statusCode < 500
+order by timestamp desc
+limit 100;
```
#### Filter by method
diff --git a/apps/docs/content/guides/storage/management/delete-objects.mdx b/apps/docs/content/guides/storage/management/delete-objects.mdx
index fba9cfdd7e1..986eb8661bf 100644
--- a/apps/docs/content/guides/storage/management/delete-objects.mdx
+++ b/apps/docs/content/guides/storage/management/delete-objects.mdx
@@ -19,7 +19,7 @@ Deleting objects should always be done via the **Storage API** and NOT via a **S
To delete one or more objects, use the `remove` method.
```javascript
-await supabase.storage.from('bucket').remove(['object-key-1', 'object-key-2'])
+await supabase.storage.from('bucket').remove(['object-path-2', 'folder/avatar2.png'])
```
## RLS
diff --git a/apps/docs/content/guides/storage/production/scaling.mdx b/apps/docs/content/guides/storage/production/scaling.mdx
index f1b2bfebb0c..c5f38e72eae 100644
--- a/apps/docs/content/guides/storage/production/scaling.mdx
+++ b/apps/docs/content/guides/storage/production/scaling.mdx
@@ -28,7 +28,7 @@ You have the option to set a maximum upload size for your bucket. Doing this can
Once you have a substantial number of objects, you might observe that the `supabase.storage.list()` method starts to slow down. This occurs because the endpoint is quite generic and attempts to retrieve both folders and objects in a single query. While this approach is very useful for building features like the Storage viewer on the Supabase dashboard, it can impact performance with a large number of objects.
-If your application don't need the entire hierarchy computed you can speed up drastically the query execution for listing your objects by creating a Postgres function as following:
+If your application doesn't need the entire hierarchy computed you can speed up drastically the query execution for listing your objects by creating a Postgres function as following:
```sql
create or replace function list_objects(
diff --git a/apps/docs/content/guides/storage/quickstart.mdx b/apps/docs/content/guides/storage/quickstart.mdx
index 233d05e5738..534d6be7b5a 100644
--- a/apps/docs/content/guides/storage/quickstart.mdx
+++ b/apps/docs/content/guides/storage/quickstart.mdx
@@ -87,6 +87,24 @@ void main() async {
[Reference.](https://pub.dev/documentation/storage_client/latest/storage_client/SupabaseStorageClient/createBucket.html)
+
+
+
+```swift
+try await supabase.storage.createBucket("avatars")
+```
+
+[Reference.](/docs/reference/swift/storage-createbucket)
+
+
+
+
+```python
+response = supabase.storage.create_bucket('avatars')
+```
+
+[Reference.](/docs/reference/python/storage-createbucket)
+
@@ -187,6 +205,24 @@ void main() async {
[Reference.](/docs/reference/dart/storage-from-download)
+
+
+
+```swift
+let response = try await supabase.storage.from("avatars").download(path: "public/avatar1.png")
+```
+
+[Reference.](/docs/reference/python/storage-from-download)
+
+
+
+
+```python
+response = supabase.storage.from_('avatars').download('public/avatar1.png')
+```
+
+[Reference.](/docs/reference/python/storage-from-download)
+
diff --git a/apps/docs/content/guides/storage/s3/authentication.mdx b/apps/docs/content/guides/storage/s3/authentication.mdx
index ab127a2d1a4..b6a09be5555 100644
--- a/apps/docs/content/guides/storage/s3/authentication.mdx
+++ b/apps/docs/content/guides/storage/s3/authentication.mdx
@@ -42,7 +42,7 @@ This is all the information you need to connect to Supabase Storage using any S3
endpoint: 'https://project_ref.supabase.co/storage/v1/s3',
credentials: {
accessKeyId: 'your_access_key_id',
- accessSecretKey: 'your_secret_access_key',
+ secretAccessKey: 'your_secret_access_key',
}
})
```
@@ -71,7 +71,7 @@ All S3 operations performed with the Session Token are scoped to the authenticat
To authenticate with S3 using a Session Token, use the following credentials:
- access_key_id: `project_ref`
-- access_secret_key: `anonKey`
+- secret_access_key: `anonKey`
- session_token: `valid jwt token`
For example, using the `aws-sdk` library:
@@ -89,7 +89,7 @@ const client = new S3Client({
endpoint: 'https://project_ref.supabase.co/storage/v1/s3',
credentials: {
accessKeyId: 'project_ref',
- accessSecretKey: 'anonKey',
+ secretAccessKey: 'anonKey',
sessionToken: session.access_token,
},
})
diff --git a/apps/docs/content/guides/storage/schema/custom-roles.mdx b/apps/docs/content/guides/storage/schema/custom-roles.mdx
index 8d2b86cc8dc..f020ccbd696 100644
--- a/apps/docs/content/guides/storage/schema/custom-roles.mdx
+++ b/apps/docs/content/guides/storage/schema/custom-roles.mdx
@@ -6,7 +6,7 @@ subtitle: 'Learn about using custom roles with storage schema'
sidebar_label: 'Schema'
---
-In this guide, you will learn how to create and use custom roles with Storage to manage role-based access to objects and buckets.
+In this guide, you will learn how to create and use custom roles with Storage to manage role-based access to objects and buckets. The same approach can be used to use custom roles with any other Supabase service.
Supabase Storage uses the same role-based access control system as any other Supabase service using RLS (Row Level Security).
@@ -32,7 +32,7 @@ on storage.objects
for select
to manager
using (
- bucker_id = 'teams'
+ bucket_id = 'teams'
);
```
diff --git a/apps/docs/content/guides/storage/schema/helper-functions.mdx b/apps/docs/content/guides/storage/schema/helper-functions.mdx
index a29ab4bb17a..b8fef508159 100644
--- a/apps/docs/content/guides/storage/schema/helper-functions.mdx
+++ b/apps/docs/content/guides/storage/schema/helper-functions.mdx
@@ -17,7 +17,7 @@ Returns the name of a file. For example, if your file is stored in `public/subfo
This example demonstrates how you would allow any user to download a file called `favicon.ico`:
```sql
-create policy "Allow authenticated uploads"
+create policy "Allow public downloads"
on storage.objects
for select
to public
diff --git a/apps/docs/content/guides/storage/serving/bandwidth.mdx b/apps/docs/content/guides/storage/serving/bandwidth.mdx
index 4c0049ed0db..b2153fbb91c 100644
--- a/apps/docs/content/guides/storage/serving/bandwidth.mdx
+++ b/apps/docs/content/guides/storage/serving/bandwidth.mdx
@@ -8,7 +8,7 @@ sidebar_label: 'Bandwidth & Storage Egress'
## Bandwidth & Storage Egress
-Free plan Organizations in Supabase have a limit of 5 GB of bandwidth. This limit is calculated by the sum of all the data transferred from the Supabase servers to the client. This includes all the data transferred from the database, storage, and functions.
+Free Plan Organizations in Supabase have a limit of 5 GB of bandwidth. This limit is calculated by the sum of all the data transferred from the Supabase servers to the client. This includes all the data transferred from the database, storage, and functions.
### Checking Storage Egress Requests in Log Explorer:
@@ -69,4 +69,4 @@ You can see that these values can get quite large, so it's important to keep tra
### Optimizing Egress:
-If you are on the pro plan, you can use the [Supabase Image Transformations](/docs/guides/storage/image-transformations) to optimize the images and reduce the egress.
+If you are on the Pro Plan, you can use the [Supabase Image Transformations](/docs/guides/storage/image-transformations) to optimize the images and reduce the egress.
diff --git a/apps/docs/content/guides/storage/serving/image-transformations.mdx b/apps/docs/content/guides/storage/serving/image-transformations.mdx
index aa1e1ebfb67..b09ef5639ad 100644
--- a/apps/docs/content/guides/storage/serving/image-transformations.mdx
+++ b/apps/docs/content/guides/storage/serving/image-transformations.mdx
@@ -11,7 +11,7 @@ Supabase Storage offers the functionality to optimize and resize images on the f
-Image Resizing is currently enabled for [Pro plan and above](https://supabase.com/pricing).
+Image Resizing is currently enabled for [Pro Plan and above](https://supabase.com/pricing).
@@ -70,12 +70,29 @@ val url = supabase.storage.from("bucket").publicRenderUrl("image.jpg") {
}
```
+
+
+
+```python
+url = supabase.storage.from_('avatars').get_public_url(
+ 'image.jpg',
+ {
+ 'transform': {
+ 'width': 500,
+ 'height': 500,
+ },
+ }
+)
+```
+
An example URL could look like this:
-`https://project_id.supabase.co/storage/v1/render/image/public/bucket/image.jpg?width=500&height=600`
+```
+https://project_id.supabase.co/storage/v1/render/image/public/bucket/image.jpg?width=500&height=600`
+```
## Signing URLs with transformation options
@@ -196,16 +213,33 @@ let data = try await supabase.storage.from("bucket")
```kotlin
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
- size(800, 300)
+ transform {
+ size(800, 300)
+ }
}
//Or on JVM stream directly to a file
val file = File("image.jpg")
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
- size(800, 300)
+ transform {
+ size(800, 300)
+ }
}
```
+
+
+
+```python
+response = supabase.storage.from_('bucket').download(
+ 'image.jpg',
+ {
+ 'width': 800,
+ 'height': 300,
+ },
+)
+```
+
@@ -278,18 +312,38 @@ let data = try await supabase.storage.from("bucket")
```kotlin
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
- size(200, 200)
- format = "origin"
+ transform {
+ size(200, 200)
+ format = ImageTransformation.Format.ORIGIN
+ }
}
//Or on JVM stream directly to a file
val file = File("image.jpg")
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
- size(200, 200)
- format = "origin"
+ transform {
+ size(200, 200)
+ format = ImageTransformation.Format.ORIGIN
+ }
}
```
+
+
+
+```python
+response = supabase.storage.from_('bucket').download(
+ 'image.jpeg',
+ {
+ 'transform': {
+ 'width': 200,
+ 'height': 200,
+ 'format': 'origin',
+ },
+ }
+)
+```
+
@@ -303,7 +357,7 @@ To get started, create a `supabase-image-loader.js` file in your Next.js project
const projectId = '' // your supabase project id
export default function supabaseLoader({ src, width, quality }) {
- return `https://${projectId}.supabase.co/storage/v1/object/public/${src}?width=${width}&quality=${quality || 75}`
+ return `https://${projectId}.supabase.co/storage/v1/render/image/public/${src}?width=${width}&quality=${quality || 75}`
}
```
@@ -385,16 +439,34 @@ let data = try await supabase.storage.from("bucket")
```kotlin
val data = supabase.storage["bucket"].downloadAuthenticated("image.jpg") {
- quality = 50
+ transform {
+ quality = 50
+ }
}
//Or on JVM stream directly to a file
val file = File("image.jpg")
supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) {
- quality = 50
+ transform {
+ quality = 50
+ }
}
```
+
+
+
+```python
+response = supabase.storage.from_('bucket').download(
+ 'image.jpg',
+ {
+ 'transform': {
+ 'quality': 50,
+ },
+ }
+)
+```
+
@@ -469,18 +541,38 @@ let data = try await supabase.storage.from("bucket")
```kotlin
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
- size(800, 300)
- resize = ImageTransformation.Resize.CONTAIN
+ transform {
+ size(800, 300)
+ resize = ImageTransformation.Resize.CONTAIN
+ }
}
//Or on JVM stream directly to a file
val file = File("image.jpg")
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
- size(800, 300)
- resize = ImageTransformation.Resize.CONTAIN
+ transform {
+ size(800, 300)
+ resize = ImageTransformation.Resize.CONTAIN
+ }
}
```
+
+
+
+```python
+response = supabase.storage.from_('bucket').download(
+ 'image.jpg',
+ {
+ 'transform': {
+ 'width': 800,
+ 'height': 300,
+ 'resize': 'contain', # 'cover' | 'fill'
+ }
+ }
+)
+```
+
diff --git a/apps/docs/content/guides/storage/uploads/standard-uploads.mdx b/apps/docs/content/guides/storage/uploads/standard-uploads.mdx
index dde2143ae9a..255e01155a6 100644
--- a/apps/docs/content/guides/storage/uploads/standard-uploads.mdx
+++ b/apps/docs/content/guides/storage/uploads/standard-uploads.mdx
@@ -70,18 +70,17 @@ try await supabase.storage.from("bucket_name").upload(path: "file_path", file: f
```kotlin
-val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
- install(Storage)
-}
-
-suspend fun uploadData(bytes: ByteArray) {
- supabase.storage.from("bucket_name").upload("file_path", bytes)
-}
+supabase.storage.from("bucket_name").upload("file_path", bytes)
//Or on JVM/Android: (This will stream the data from the file to supabase)
-suspend fun uploadFile(file: File) {
- supabase.storage.from("bucket_name").upload("file_path", file)
-}
+supabase.storage.from("bucket_name").upload("file_path", file)
+```
+
+
+
+
+```python
+response = supabase.storage.from_('bucket_name').upload('file_path', file)
```
@@ -140,6 +139,24 @@ try await supabase.storage.from("bucket_name")
)
```
+
+
+
+```kotlin
+supabase.storage.from("bucket_name").upload("file_path", bytes) {
+ upsert = true
+}
+```
+
+
+
+
+```python
+response = supabase.storage.from_('bucket_name').upload('file_path', file, {
+ 'upsert': 'true',
+})
+```
+
@@ -198,6 +215,24 @@ try await supabase.storage.from("bucket_name")
)
```
+
+
+
+```kotlin
+supabase.storage.from("bucket_name").upload("file_path", bytes) {
+ contentType = ContentType.Image.JPEG
+}
+```
+
+
+
+
+```python
+response = supabase.storage.from_('bucket_name').upload('file_path', file, {
+ 'content-type': 'image/jpeg',
+})
+```
+
diff --git a/apps/docs/content/navigation.references.ts b/apps/docs/content/navigation.references.ts
new file mode 100644
index 00000000000..e3b02e54ab0
--- /dev/null
+++ b/apps/docs/content/navigation.references.ts
@@ -0,0 +1,168 @@
+export const REFERENCES = {
+ javascript: {
+ type: 'sdk',
+ name: 'JavaScript',
+ library: 'supabase-js',
+ libPath: 'javascript',
+ versions: ['v2', 'v1'],
+ typeSpec: true,
+ icon: 'reference-javascript',
+ meta: {
+ v2: {
+ libId: 'reference_javascript_v2',
+ specFile: 'supabase_js_v2',
+ },
+ v1: {
+ libId: 'reference_javascript_v1',
+ specFile: 'supabase_js_v1',
+ },
+ },
+ },
+ dart: {
+ type: 'sdk',
+ name: 'Flutter',
+ library: 'supabase-dart',
+ libPath: 'dart',
+ versions: ['v2', 'v1'],
+ icon: 'reference-dart',
+ meta: {
+ v2: {
+ libId: 'reference_dart_v2',
+ specFile: 'supabase_dart_v2',
+ },
+ v1: {
+ libId: 'reference_dart_v1',
+ specFile: 'supabase_dart_v1',
+ },
+ },
+ },
+ csharp: {
+ type: 'sdk',
+ name: 'C#',
+ library: 'supabase-csharp',
+ libPath: 'csharp',
+ versions: ['v1', 'v0'],
+ icon: 'reference-csharp',
+ meta: {
+ v1: {
+ libId: 'reference_csharp_v1',
+ specFile: 'supabase_csharp_v1',
+ },
+ v0: {
+ libId: 'reference_csharp_v0',
+ specFile: 'supabase_csharp_v0',
+ },
+ },
+ },
+ swift: {
+ type: 'sdk',
+ name: 'Swift',
+ library: 'supabase-swift',
+ libPath: 'swift',
+ versions: ['v2', 'v1'],
+ icon: 'reference-swift',
+ meta: {
+ v2: {
+ libId: 'reference_swift_v2',
+ specFile: 'supabase_swift_v2',
+ },
+ v1: {
+ libId: 'reference_swift_v1',
+ specFile: 'supabase_swift_v1',
+ },
+ },
+ },
+ kotlin: {
+ type: 'sdk',
+ name: 'Kotlin',
+ library: 'supabase-kt',
+ libPath: 'kotlin',
+ versions: ['v3', 'v2', 'v1'],
+ icon: 'reference-kotlin',
+ meta: {
+ v3: {
+ libId: 'reference_kotlin_v3',
+ specFile: 'supabase_kt_v3',
+ },
+ v2: {
+ libId: 'reference_kotlin_v2',
+ specFile: 'supabase_kt_v2',
+ },
+ v1: {
+ libId: 'reference_kotlin_v1',
+ specFile: 'supabase_kt_v1',
+ },
+ },
+ },
+ python: {
+ type: 'sdk',
+ name: 'Python',
+ library: 'supabase-py',
+ libPath: 'python',
+ versions: ['v2'],
+ icon: 'reference-python',
+ meta: {
+ v2: {
+ libId: 'reference_python_v2',
+ specFile: 'supabase_py_v2',
+ },
+ },
+ },
+ cli: {
+ type: 'cli',
+ name: 'CLI',
+ libPath: 'cli',
+ versions: [],
+ icon: 'reference-cli',
+ },
+ api: {
+ type: 'api',
+ name: 'API',
+ libPath: 'api',
+ versions: [],
+ icon: 'reference-api',
+ },
+ self_hosting_analytics: {
+ type: 'self-hosting',
+ name: 'Self-Hosting Analytics',
+ libPath: 'self-hosting-analytics',
+ versions: [],
+ icon: 'reference-analytics',
+ },
+ self_hosting_auth: {
+ type: 'self-hosting',
+ name: 'Self-Hosting Auth',
+ libPath: 'self-hosting-auth',
+ versions: [],
+ icon: 'self-hosting',
+ },
+ self_hosting_functions: {
+ type: 'self-hosting',
+ name: 'Self-Hosting Functions',
+ libPath: 'self-hosting-functions',
+ versions: [],
+ icon: 'reference-functions',
+ },
+ self_hosting_realtime: {
+ type: 'self-hosting',
+ name: 'Self-Hosting Realtime',
+ libPath: 'self-hosting-realtime',
+ versions: [],
+ icon: 'self-hosting',
+ },
+ self_hosting_storage: {
+ type: 'self-hosting',
+ name: 'Self-Hosting Storage',
+ libPath: 'self-hosting-storage',
+ versions: [],
+ icon: 'self-hosting',
+ },
+} as const
+
+export const clientSdkIds = Object.keys(REFERENCES).filter(
+ (reference) => REFERENCES[reference].type === 'sdk'
+)
+
+export const selfHostingServices = Object.keys(REFERENCES).filter(
+ (reference) => REFERENCES[reference].type === 'self-hosting'
+)
diff --git a/apps/docs/content/troubleshooting/_template.mdx b/apps/docs/content/troubleshooting/_template.mdx
new file mode 100644
index 00000000000..a54843f581b
--- /dev/null
+++ b/apps/docs/content/troubleshooting/_template.mdx
@@ -0,0 +1,48 @@
+---
+title = "Troubleshoot slow queries"
+topics = [
+ "ai",
+ "auth",
+ "branching",
+ "cli",
+ "database",
+ "functions",
+ "platform",
+ "realtime",
+ "self-hosting",
+ "storage",
+ "studio",
+ "supavisor",
+ "terraform",
+]
+keywords = [] # any strings (topics are automatically added so no need to duplicate)
+
+# Optionally, list the endpoints/commands/methods that are affected by this issue.
+#
+# Use the ID, which you can get from the API reference on supabase.com/docs by
+# navigating to the API and copying the last segment of the URL.
+#
+# Delete any sections that are not applicable.
+[api]
+sdk = ["auth-mfa-enroll", "auth-mfa-verify"]
+management_api = ["v1-get-postgres-config"]
+cli = ["supabase-migration-repair"]
+
+# Optionally, list the error messages associated with this issue.
+[[errors]]
+http_status_code = 404
+code = "not_found"
+message = "The resource was not found"
+
+[[errors]]
+http_status_code = 403
+code = "forbidden"
+message = "Not allowed to access this resource"
+---
+
+Freeform Markdown here. Some suggestions on what to include:
+
+- Why the issue occurs
+- How to diagnose it
+- How to fix it
+- How to prevent it
diff --git a/apps/docs/content/troubleshooting/monitor-supavisor-postgres-connections.mdx b/apps/docs/content/troubleshooting/monitor-supavisor-postgres-connections.mdx
new file mode 100644
index 00000000000..57662e00c61
--- /dev/null
+++ b/apps/docs/content/troubleshooting/monitor-supavisor-postgres-connections.mdx
@@ -0,0 +1,112 @@
+---
+title = "How to monitor Postgres and Supavisor connections"
+topics = [
+"database",
+"supavisor",
+]
+keywords = [
+"Grafana",
+"connections",
+"performance",
+"pooler",
+]
+---
+
+This guide explains how connections impact your Supabase database's performance and how to optimize them for better resource utilization.
+
+## Installing Supabase Grafana
+
+Supabase has an [open-source Grafana Repo](https://github.com/supabase/supabase-grafana) that displays real-time metrics of your database. Although the [Reports Dashboard ](/dashboard/project/_/reports) provides similar metrics, it averages the data by the hour or day. Having visuals of your connection usage can help you better allocate resources.
+
+_Visual of Grafana Dashboard_
+
+
+It can be run locally within Docker. Alternatively, you can deploy it to fly.io or Grafana Cloud, which are better for long-term data collection.
+
+Installation instructions can be found in it the [metrics docs ](/docs/guides/platform/metrics#deploying-supabase-grafana)
+
+## Observing Connections
+
+In Supabase Grafana, the "Client Connections" graph shows connections to both Supavisor and Postgres
+
+
+- **Yellow**: The yellow line represents the number of actively querying and idle connections to the Supavisor Pooler.
+- **Green**: The green line represents the total number of actively querying and idle direct connections to the database.
+
+## Investigating Connection Sources
+
+`pg_stat_activity` is a `VIEW` that keeps track of processes being run by your database, including connections. It's particularly useful for determining if idle clients are hogging connection slots.
+
+This is a query you can use to observe the database roles and servers connecting to your database:
+
+```sql
+SELECT
+ pg_stat_activity.pid,
+ ssl AS ssl_connection,
+ datname AS database,
+ usename AS connected_role,
+ application_name,
+ client_addr,
+ query,
+ query_start,
+ state,
+ backend_start
+FROM pg_stat_ssl
+JOIN pg_stat_activity
+ ON pg_stat_ssl.pid = pg_stat_activity.pid;
+```
+
+Interpreting the query:
+
+| Column | Description |
+| ---------------- | ------------------------------------------------- |
+| pid | connection id |
+| ssl | Indicates if SSL is in use |
+| datname | Name of the connected database (usually postgres) |
+| usename | Role of the connected user |
+| application_name | Name of the connecting application |
+| client_addr | IP address of the connecting server |
+| query | Last query executed by the connection |
+| query_start | Time when the last query was executed |
+| state | Querying state: active or idle |
+| backend_start | Timestamp of the connection's establishment |
+
+- Note: If you are unfamiliar with the Supabase database roles, check this [reference](https://gist.github.com/TheOtherBrian1/d6e862a65e03049eb4f102f6ca809401)
+
+If you believe a connection should be killed, you can do so by running the following query:
+
+```sql
+ select pg_terminate_backend(pid)
+ from pg_stat_activity
+ where pid = ;
+```
+
+## Managing the Supavisor Pooler:
+
+The Supavisor Pooler is an intermediary between your clients (application servers) and the database. In transaction mode (port 6543), it can enable Postgres to share single connections with many clients, only allowing access when a query is pending. This prevents idle clients from hogging a direct connection and allows for more throughput.
+
+In cases where you see significantly more pooler connections than direct connections, if you can, you should consider increasing how many direct connections the pooler is allowed to manage in the [Dashboard's Database Settings](/dashboard/project/_/settings/database):
+.
+
+The general rule is that if you are using the PostgREST database API, you should avoid raising your pool size past 40%. Otherwise, you can commit 80% to the pool. This leaves adequate room for the Authentication server and other utilities.
+
+These numbers are generalizations and assume a certain level of activity from all connected servers. The actual values depend on your peak connection usage. For instance, if you were only using 80 connections in a week period and your database could support 500 connections, then realistically you could allocate the remaining 420 (minus a reasonable buffer) to service more demand.
+
+## Secondary Issues:
+
+When managing Postgres, outside of connections, there are generally 3 likely bottlenecks (links to address each):
+
+- [Disk/IO](https://github.com/orgs/supabase/discussions/27003)
+- [Memory](https://github.com/orgs/supabase/discussions/27021)
+- [CPU](https://github.com/orgs/supabase/discussions/27022)
+
+They're all intertwined to some extent. If IO, CPU, or Memory are constrained, this can cause queries to slow down. Your application servers and Supavisor may compensate by creating more database connections or letting queries wait longer in their respective queues. Sometimes, by addressing or optimizing other factors of the database, you can better address connection issues.
+
+## Other helpful resources:
+
+- [Supavisor FAQ](https://github.com/orgs/supabase/discussions/21566)
+- [Using SQLAlchemy with Supabase](https://github.com/orgs/supabase/discussions/27071)
+- [Supabase and IPv4/IPv6 compatibility ](https://github.com/orgs/supabase/discussions/27034)
+- [Addressing Max Client Errors](https://github.com/orgs/supabase/discussions/22305)
+- [Connecting to your database](/docs/guides/database/connecting-to-postgres#integrations)
+- [How to Change Max Database Connections](https://github.com/orgs/supabase/discussions/27197)
diff --git a/apps/docs/content/troubleshooting/supabase-grafana-memory-charts.mdx b/apps/docs/content/troubleshooting/supabase-grafana-memory-charts.mdx
new file mode 100644
index 00000000000..ba1c5dcd9cc
--- /dev/null
+++ b/apps/docs/content/troubleshooting/supabase-grafana-memory-charts.mdx
@@ -0,0 +1,60 @@
+---
+title = "Interpreting Supabase Grafana Memory Charts"
+topics = [
+"database",
+]
+keywords = [
+"Grafana",
+"performance",
+"memory"
+]
+---
+
+> [Supabase Grafana Installation Guide](/docs/guides/platform/metrics#deploying-supabase-grafana)
+
+Here are examples of unhealthy memory usage:
+
+
+
+- **Yellow**: represents active memory
+- **Red**: represents SWAP, which is disk storage that the system treats as if it were actually memory
+- **Green**: it is unclaimed (the system will always leave some memory unclaimed)
+- **Blue**: it is cached data and a buffer
+
+The cache in PostgreSQL is important because the database will store frequently accessed data in it for rapid retrieval. If too much active memory is needed, it runs the risk of excessively displacing cache. This will force queries to check disk, which is slow.
+
+Most data in a database is idle, but in cases where there is little available memory or uncached data is rapidly accessed, [thrashing]() can occur.
+
+Ideally, you want queries to hit the cache 99% of the time. You can use the [Supabase CLI](/docs/guides/database/inspect) `inspect db cache hit` command to check your cache hit rate. Alternatively, you can run the [query](https://github.com/supabase/cli/blob/c9cce58025fded16b4c332747f819a44f45c3b83/internal/inspect/bloat/bloat.go#L17) found in the CLI's GitHub repo in the [SQL Editor](/dashboard/project/_/sql/)
+
+```sh
+# login to the CLI
+npx supabase login
+
+# initlize a local supabase directory
+npx supabase init
+
+#link your project
+npx supabase link
+
+# find cache hit rate
+npx supabase inspect db cache-hit --linked
+
+```
+
+If the cache hit rate begins to drop below the ideal amount, one should consider taking the following actions:
+
+Optimizing:
+
+1. [Apply indexes](https://github.com/orgs/supabase/discussions/22449): can reduce the amount of data pulled from disk into memory
+2. [Increasing the compute size](/docs/guides/platform/compute-add-ons)
+3. [Distribute load by using read-replicas](/dashboard/project/_/settings/infrastructure)
+4. [Partitions](/docs/guides/database/partitions): Generally should be used on _very_ large tables to minimize data pulled from disk into memory
+5. [Remove bloat](/docs/guides/database/bloat): Bloat can fragment data across pages, causing redundant data to be pulled from disk.
+6. Table refactoring: Split tables to isolate columns that are less frequently accessed, so they are not redundantly pulled into memory while accessing hotter data
+
+### Other useful Supabase Grafana guides:
+
+- [Connections](https://github.com/orgs/supabase/discussions/27141)
+- [Disk](https://github.com/orgs/supabase/discussions/27003)
+- [CPU](https://github.com/orgs/supabase/discussions/27022)
diff --git a/apps/docs/data/footer.ts b/apps/docs/data/footer.ts
index d076d36159d..552e00c4775 100644
--- a/apps/docs/data/footer.ts
+++ b/apps/docs/data/footer.ts
@@ -1,21 +1,21 @@
-import { IconCheckCircle, IconLifeBuoy } from 'ui'
+import { CheckCircle, FlaskConical, LifeBuoy } from 'lucide-react'
import { PrivacySettings } from 'ui-patterns/PrivacySettings'
export const primaryLinks = [
{
- featherIcon: IconLifeBuoy,
+ featherIcon: LifeBuoy,
text: 'Need some help?',
ctaLabel: 'Contact support',
url: 'https://supabase.com/support',
},
{
- icon: 'M12.9521 10.5073C12.766 10.3212 12.5289 10.1943 12.2708 10.1427L10.6794 9.82467C9.80719 9.65024 8.90169 9.77152 8.10609 10.1693L7.89409 10.2747C7.0985 10.6725 6.193 10.7938 5.32076 10.6193L4.03343 10.362C3.81823 10.319 3.59574 10.3298 3.38571 10.3934C3.17569 10.457 2.9846 10.5715 2.82943 10.7267M5.33343 2.88867H10.6668L10.0001 3.55534V7.00334C10.0002 7.35693 10.1407 7.69602 10.3908 7.94601L13.7241 11.2793C14.5641 12.1193 13.9688 13.5553 12.7808 13.5553H3.21876C2.03076 13.5553 1.43609 12.1193 2.27609 11.2793L5.60943 7.94601C5.85949 7.69602 6.00002 7.35693 6.00009 7.00334V3.55534L5.33343 2.88867Z',
+ featherIcon: FlaskConical,
text: 'Latest product updates?',
ctaLabel: 'See Changelog',
url: 'https://supabase.com/changelog',
},
{
- featherIcon: IconCheckCircle,
+ featherIcon: CheckCircle,
text: "Something's not right?",
ctaLabel: 'Check system status',
url: 'https://status.supabase.com/',
diff --git a/apps/docs/docs/ref/csharp/introduction.mdx b/apps/docs/docs/ref/csharp/introduction.mdx
index 84c307600c9..c3531177427 100644
--- a/apps/docs/docs/ref/csharp/introduction.mdx
+++ b/apps/docs/docs/ref/csharp/introduction.mdx
@@ -1,29 +1,9 @@
----
-id: introduction
-title: Introduction
-hideTitle: true
----
+This reference documents every object and method available in Supabase's C# library, [supabase](https://www.nuget.org/packages/supabase). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
-
-
-
C# Client Library
-
- @supabase-community/supabase-csharp
-
-
-
+
-{/* prettier-ignore */}
-
- This reference documents every object and method available in Supabase's C# library, [supabase](https://www.nuget.org/packages/supabase). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
+The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
-{/* prettier-ignore */}
-
+Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
- The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
-
- Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
-
-
+
diff --git a/apps/docs/docs/ref/csharp/v0/introduction.mdx b/apps/docs/docs/ref/csharp/v0/introduction.mdx
index 1fed5441e0f..c428f250498 100644
--- a/apps/docs/docs/ref/csharp/v0/introduction.mdx
+++ b/apps/docs/docs/ref/csharp/v0/introduction.mdx
@@ -1,29 +1,9 @@
----
-id: introduction
-title: Introduction
-hideTitle: true
----
+This reference documents every object and method available in Supabase's C# library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
-
-
-
C# Client Library
-
- @supabase-community/supabase-csharp
-
-
-
+
-{/* prettier-ignore */}
-
- This reference documents every object and method available in Supabase's C# library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
+The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
-{/* prettier-ignore */}
-
+Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
- The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
-
- Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
-
-
+
diff --git a/apps/docs/docs/ref/dart/introduction.mdx b/apps/docs/docs/ref/dart/introduction.mdx
index c7c7f2f58fd..fd681dcfe91 100644
--- a/apps/docs/docs/ref/dart/introduction.mdx
+++ b/apps/docs/docs/ref/dart/introduction.mdx
@@ -1,20 +1,3 @@
----
-id: introduction
-title: Introduction
-hideTitle: true
----
+This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
-
-
-
Flutter Client Library
-
supabase-flutter
-
-
-
-{/* prettier-ignore */}
-
- This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
- We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
-
+We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
diff --git a/apps/docs/docs/ref/dart/v0/introduction.mdx b/apps/docs/docs/ref/dart/v0/introduction.mdx
index 9be35f2dab5..fd681dcfe91 100644
--- a/apps/docs/docs/ref/dart/v0/introduction.mdx
+++ b/apps/docs/docs/ref/dart/v0/introduction.mdx
@@ -1,24 +1,3 @@
----
-id: introduction
-title: Introduction
-hideTitle: true
----
+This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
-
-
-
Flutter Client Library
-
supabase-flutter
-
-
-
-
-
-
- You're viewing the docs for an older version of the `supabase-flutter` library. Learn how to [upgrade to the latest version](/docs/reference/dart/v0/upgrade-guide).
-
-
- This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
- We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
-
+We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
diff --git a/apps/docs/docs/ref/dart/v1/introduction.mdx b/apps/docs/docs/ref/dart/v1/introduction.mdx
index c7c7f2f58fd..fd681dcfe91 100644
--- a/apps/docs/docs/ref/dart/v1/introduction.mdx
+++ b/apps/docs/docs/ref/dart/v1/introduction.mdx
@@ -1,20 +1,3 @@
----
-id: introduction
-title: Introduction
-hideTitle: true
----
+This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
-
-
-
Flutter Client Library
-
supabase-flutter
-
-
-
-{/* prettier-ignore */}
-
- This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
- We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
-
+We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
diff --git a/apps/docs/docs/ref/javascript/installing.mdx b/apps/docs/docs/ref/javascript/installing.mdx
index eb133446abf..3a5d4cbe44d 100644
--- a/apps/docs/docs/ref/javascript/installing.mdx
+++ b/apps/docs/docs/ref/javascript/installing.mdx
@@ -73,14 +73,14 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
- You can use supabase-js in the Deno runtime via esm.sh:
+ You can use supabase-js in the Deno runtime via [JSR](https://jsr.io/@supabase/supabase-js):
```ts
- import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
+ import { createClient } from 'jsr:@supabase/supabase-js@2'
```
diff --git a/apps/docs/docs/ref/javascript/introduction.mdx b/apps/docs/docs/ref/javascript/introduction.mdx
index 37835c2498b..ac1adb04ab3 100644
--- a/apps/docs/docs/ref/javascript/introduction.mdx
+++ b/apps/docs/docs/ref/javascript/introduction.mdx
@@ -4,15 +4,4 @@ title: Introduction
hideTitle: true
---
-
-
-
-
JavaScript Client Library
-
@supabase/supabase-js
-
-
-
-{/* prettier-ignore */}
-
- This reference documents every object and method available in Supabase's isomorphic JavaScript library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
+This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`. You can use `supabase-js` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
diff --git a/apps/docs/docs/ref/javascript/typescript-support.mdx b/apps/docs/docs/ref/javascript/typescript-support.mdx
index 101350b24d5..3818dddca9f 100644
--- a/apps/docs/docs/ref/javascript/typescript-support.mdx
+++ b/apps/docs/docs/ref/javascript/typescript-support.mdx
@@ -14,7 +14,7 @@ With TypeScript, `supabase-js` detects things like `not null` constraints and [g
- You can use the Supabase CLI to [generate the types](/docs/reference/cli/supabase-gen-types-typescript). You can also generate the types [from the dashboard](https://supabase.com/dashboard/project/_/api?page=tables-intro).
+ You can use the Supabase CLI to [generate the types](/docs/reference/cli/supabase-gen-types). You can also generate the types [from the dashboard](https://supabase.com/dashboard/project/_/api?page=tables-intro).
diff --git a/apps/docs/docs/ref/javascript/v1/introduction.mdx b/apps/docs/docs/ref/javascript/v1/introduction.mdx
index ded6df7a760..7060fc0645f 100644
--- a/apps/docs/docs/ref/javascript/v1/introduction.mdx
+++ b/apps/docs/docs/ref/javascript/v1/introduction.mdx
@@ -1,25 +1 @@
----
-id: introduction
-title: Introduction
-hideTitle: true
----
-
-
-
-
-
JavaScript Client Library
-
@supabase/supabase-js
-
-
-
-
-
-
- You're viewing the docs for an older version of the `supabase-js` library. Learn how to [upgrade to the latest version](/docs/reference/javascript/v1/upgrade-guide).
-
-
-
-
- This reference documents every object and method available in Supabase's isomorphic JavaScript library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
-
+This reference documents every object and method available in Supabase's isomorphic JavaScript library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
diff --git a/apps/docs/docs/ref/javascript/v1/release-notes.mdx b/apps/docs/docs/ref/javascript/v1/release-notes.mdx
deleted file mode 100644
index 725cbb5201e..00000000000
--- a/apps/docs/docs/ref/javascript/v1/release-notes.mdx
+++ /dev/null
@@ -1,6 +0,0 @@
----
-id: release-notes
-title: Release Notes
----
-
-## js v1 this is the release notes file.
diff --git a/apps/docs/docs/ref/kotlin/installing.mdx b/apps/docs/docs/ref/kotlin/installing.mdx
index e8e7c65b15f..c3eb71b2374 100644
--- a/apps/docs/docs/ref/kotlin/installing.mdx
+++ b/apps/docs/docs/ref/kotlin/installing.mdx
@@ -8,7 +8,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
### Add one or more modules to your project
-
+
- Add dependency to your build file:
-
-
- ```kotlin
- implementation("io.github.jan-tennert.supabase:[module]:VERSION")
- ```
-
-
-
-
- ```groovy
- implementation 'io.github.jan-tennert.supabase:[module]:VERSION'
- ```
-
-
-
-
- ```xml
-
- io.github.jan-tennert.supabase
- [module]
- VERSION
-
- ```
-
-
-
+ Add dependency to your build file using the BOM.
The available modules are:
- - [**gotrue-kt**](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue)
+ - [**auth-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Auth)
- [**realtime-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Realtime)
- [**storage-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Storage)
- [**functions-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Functions)
- [**postgrest-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest)
- - [apollo-graphql](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ApolloGraphQL) - Creates an [Apollo GraphQL Client](https://github.com/apollographql/apollo-kotlin) for interacting with the Supabase API.
- - [compose-auth](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ComposeAuth) - Provides easy Native Google & Apple Auth for Compose Multiplatform targets.
- - [compose-auth-ui](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ComposeAuthUI) - Provides UI Components for Compose Multiplatform.
- - [coil-integration](https://github.com/supabase-community/supabase-kt/tree/master/plugins/CoilIntegration) - Provides a [Coil](https://github.com/coil-kt/coil) Integration for displaying images stored in Supabase Storage.
- - [imageloader-integration](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ImageLoaderIntegration) - Provides a [Compose ImageLoader](https://github.com/qdsfdhvh/compose-imageloader) Integration for displaying images stored in Supabase Storage.
+ - Other plugins also available [here](https://github.com/supabase-community/supabase-kt/tree/master/plugins)
- When using multiple modules, you can also use the BOM dependency to ensure that all modules use the same version:
+ Checkout the different READMEs for information about supported Kotlin targets.
+
+ io.github.jan-tennert.supabase
postgrest-kt
+
+ io.github.jan-tennert.supabase
+ auth-kt
+ io.github.jan-tennert.supabaserealtime-kt
@@ -111,7 +84,6 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
-
@@ -119,10 +91,14 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
### Add Ktor Client Engine to each of your Kotlin targets (required)
-
+
You can find a list of engines [here](https://ktor.io/docs/http-client-engines.html)
+ - Note that not all Ktor engines support Websockets. So if you plan to use the Realtime module, make sure to use an engine that supports Websockets. Checkout the [engine limitations](https://ktor.io/docs/client-engines.html#limitations) for more information.
+ - If using `supabase-kt` 3.0.0 and above, you need to use Ktor version 3.0.0-rc-1 or later.
+
+
+
+
+
Multiplatform example:
+
+
+
-
-
-
Kotlin Client Library
-
@supabase-community/supabase-kt
-
-
-
-
-
This reference documents every object and method available in Supabase's Kotlin Multiplatform library, [supabase-kt](https://github.com/supabase-community/supabase-kt). You can use supabase-kt to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
To see supported Kotlin targets, check the corresponding module README on [GitHub](https://github.com/supabase-community/supabase-kt).
-To migrate from version 1.4.X to 2.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md)
+To migrate from version 2.X to 3.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md)
-
+
-
The Kotlin client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
Huge thanks to official maintainer, [jan-tennert](https://github.com/jan-tennert).
-
+
diff --git a/apps/docs/docs/ref/kotlin/v1/installing.mdx b/apps/docs/docs/ref/kotlin/v1/installing.mdx
new file mode 100644
index 00000000000..f6e5c9ba8b3
--- /dev/null
+++ b/apps/docs/docs/ref/kotlin/v1/installing.mdx
@@ -0,0 +1,393 @@
+---
+id: installing
+title: 'Installing & Initialization'
+slug: installing
+custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
+---
+
+### Add one or more modules to your project
+
+
+
+
+
+ Add dependency to your build file using the BOM.
+
+ The available modules are:
+ - [**gotrue-kt**](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue)
+ - [**realtime-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Realtime)
+ - [**storage-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Storage)
+ - [**functions-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Functions)
+ - [**postgrest-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest)
+ - Other plugins also available [here](https://github.com/supabase-community/supabase-kt/tree/master/plugins)
+
+
+
+
+
+
+ ```kotlin
+ implementation(platform("io.github.jan-tennert.supabase:bom:VERSION"))
+ implementation("io.github.jan-tennert.supabase:postgrest-kt")
+ implementation("io.github.jan-tennert.supabase:gotrue-kt")
+ implementation("io.github.jan-tennert.supabase:realtime-kt")
+ ```
+
+
+
+
+ ```groovy
+ implementation platform("io.github.jan-tennert.supabase:bom:VERSION")
+ implementation 'io.github.jan-tennert.supabase:postgrest-kt'
+ implementation 'io.github.jan-tennert.supabase:gotrue-kt'
+ implementation 'io.github.jan-tennert.supabase:realtime-kt'
+ ```
+
+
+
+
+ ```xml
+
+ io.github.jan-tennert.supabase
+ bom
+ VERSION
+ pom
+ import
+
+
+ io.github.jan-tennert.supabase
+ postgrest-kt
+
+
+ io.github.jan-tennert.supabase
+ gotrue-kt
+
+
+ io.github.jan-tennert.supabase
+ realtime-kt
+
+ ```
+
+
+
+
+
+
+
+### Add Ktor Client Engine to each of your Kotlin targets (required)
+
+
+
+
+ You can find a list of engines [here](https://ktor.io/docs/http-client-engines.html)
+ Note that not all Ktor engines support Websockets. So if you plan to use the Realtime module, make sure to use an engine that supports Websockets. Checkout the [engine limitations](https://ktor.io/docs/client-engines.html#limitations) for more information.
+
+
+
+
+
+
+ ```kotlin
+ implementation("io.ktor:ktor-client-[engine]:KTOR_VERSION")
+ ```
+
+
+
+
+ ```groovy
+ implementation 'io.ktor:ktor-client-[engine]:KTOR_VERSION'
+ ```
+
+
+
+
+ ```xml
+
+ io.ktor
+ ktor-client-[engine]
+ KTOR_VERSION
+
+ ```
+
+
+
+
+
+
+
+ Multiplatform example:
+
+
+
+
+
+
+
+ ```kotlin
+ val commonMain by getting {
+ dependencies {
+ //supabase modules
+ }
+ }
+ val jvmMain by getting {
+ dependencies {
+ implementation("io.ktor:ktor-client-cio:KTOR_VERSION")
+ }
+ }
+ val androidMain by getting {
+ dependsOn(jvmMain)
+ }
+ val jsMain by getting {
+ dependencies {
+ implementation("io.ktor:ktor-client-js:KTOR_VERSION")
+ }
+ }
+ val iosMain by getting {
+ dependencies {
+ implementation("io.ktor:ktor-client-darwin:KTOR_VERSION")
+ }
+ }
+ ```
+
+
+
+
+
+
+
+
+### Serialization
+
+supabase-kt provides several different ways to encode and decode your custom objects.
+By default, [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization) is used.
+
+
+
+
+ Use [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization).
+
+
+
+
+
+
+
+ ```kotlin
+ plugins {
+ kotlin("plugin.serialization") version "KOTLIN_VERSION"
+ }
+ ```
+
+
+
+
+ ```groovy
+ plugins {
+ id 'org.jetbrains.kotlin.plugin.serialization' version 'KOTLIN_VERSION'
+ }
+ ```
+
+
+
+
+ ```xml
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+
+
+ kotlinx-serialization
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-serialization
+ ${kotlin.version}
+
+
+
+
+
+ ```
+
+
+
+ ```kotlin
+ val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
+ //Already the default serializer, but you can provide a custom Json instance (optional):
+ defaultSerializer = KotlinXSerializer(Json {
+ //apply your custom config
+ })
+ }
+ ```
+
+
+
+
+
+
+ Use [Moshi](https://github.com/square/moshi).
+
+
+
+
+
+
+
+ ```kotlin
+ implementation("io.github.jan-tennert.supabase:serializer-moshi:VERSION")
+ ```
+
+
+
+
+ ```groovy
+ implementation 'io.github.jan-tennert.supabase:serializer-moshi:VERSION'
+ ```
+
+
+
+
+ ```xml
+
+ io.github.jan-tennert.supabase
+ serializer-moshi
+ VERSION
+
+ ```
+
+
+
+ ```kotlin
+ val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
+ defaultSerializer = MoshiSerializer()
+ }
+ ```
+
+
+
+
+
+
+
+ Use [Jackson](https://github.com/FasterXML/jackson-module-kotlin).
+
+
+
+
+
+
+
+ ```kotlin
+ implementation("io.github.jan-tennert.supabase:serializer-jackson:VERSION")
+ ```
+
+
+
+
+ ```groovy
+ implementation 'io.github.jan-tennert.supabase:serializer-jackson:VERSION'
+ ```
+
+
+
+
+ ```xml
+
+ io.github.jan-tennert.supabase
+ serializer-jackson
+ VERSION
+
+ ```
+
+
+
+ ```kotlin
+ val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
+ defaultSerializer = JacksonSerializer()
+ }
+ ```
+
+
+
+
+
+
+
+ Use custom serializer.
+
+
+
+
+ ```kotlin
+ class CustomSerializer: SupabaseSerializer {
+
+ override fun encode(type: KType, value: T): String {
+ //encode value to string
+ }
+
+ override fun decode(type: KType, value: String): T {
+ //decode value
+ }
+
+ }
+ ```
+
+ ```kotlin
+ val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
+ defaultSerializer = CustomSerializer()
+ }
+ ```
+
+
+
+
diff --git a/apps/docs/docs/ref/kotlin/v2/introduction.mdx b/apps/docs/docs/ref/kotlin/v2/introduction.mdx
new file mode 100644
index 00000000000..602ed0284ee
--- /dev/null
+++ b/apps/docs/docs/ref/kotlin/v2/introduction.mdx
@@ -0,0 +1,13 @@
+This reference documents every object and method available in Supabase's Kotlin Multiplatform library, [supabase-kt](https://github.com/supabase-community/supabase-kt). You can use supabase-kt to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
+
+To see supported Kotlin targets, check the corresponding module README on [GitHub](https://github.com/supabase-community/supabase-kt).
+
+To migrate from version 1.4.X to 2.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md)
+
+
+
+The Kotlin client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
+
+Huge thanks to official maintainer, [jan-tennert](https://github.com/jan-tennert).
+
+
diff --git a/apps/docs/docs/ref/python/installing.mdx b/apps/docs/docs/ref/python/installing.mdx
index 58a56b8d9fe..fb1be005e46 100644
--- a/apps/docs/docs/ref/python/installing.mdx
+++ b/apps/docs/docs/ref/python/installing.mdx
@@ -10,7 +10,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/apps/docs/spec
- You can install supabase-py via the terminal. (for > Python 3.7)
+ You can install supabase-py via the terminal. (for Python > 3.8)
diff --git a/apps/docs/docs/ref/python/introduction.mdx b/apps/docs/docs/ref/python/introduction.mdx
index 8a634813dae..7927d1b6d75 100644
--- a/apps/docs/docs/ref/python/introduction.mdx
+++ b/apps/docs/docs/ref/python/introduction.mdx
@@ -1,28 +1 @@
----
-id: introduction
-title: Introduction
-hideTitle: true
----
-
-
-
-
-
Python Client Library
-
@supabase-community/supabase-py
-
-
-
-
- {/* prettier-ignore */}
-
This reference documents every object and method available in the [supabase-py](https://github.com/supabase-community/supabase-py) library from the Supabase community. You can use `supabase-py` to test with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
-
-
-
-The Python client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
-
-Huge thanks to official maintainers, [anand2312](https://github.com/anand2312/), [dreinon](https://github.com/dreinon), [J0](https://github.com/j0), and [Leynier](https://github.com/leynier).
-
-Shoutout to [timkpaine](https://github.com/timkpaine) for maintaining our Conda libraries as well.
-
-
+This reference documents every object and method available in Supabase's Python library, [supabase-py](https://github.com/supabase/supabase-py). You can use supabase-py to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
diff --git a/apps/docs/docs/ref/swift/introduction.mdx b/apps/docs/docs/ref/swift/introduction.mdx
index dadc4490275..25c9f79a791 100644
--- a/apps/docs/docs/ref/swift/introduction.mdx
+++ b/apps/docs/docs/ref/swift/introduction.mdx
@@ -1,20 +1 @@
----
-id: introduction
-title: Introduction
-hideTitle: true
----
-
-
-
-
-
Swift Client Library
-
@supabase/supabase-swift
-
-
-
-{/* prettier-ignore */}
-
-
- This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
-
-
+This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
diff --git a/apps/docs/docs/ref/swift/v1/installing.mdx b/apps/docs/docs/ref/swift/v1/installing.mdx
new file mode 100644
index 00000000000..cbc57f06a7b
--- /dev/null
+++ b/apps/docs/docs/ref/swift/v1/installing.mdx
@@ -0,0 +1,63 @@
+---
+id: installing
+title: 'Installing'
+slug: installing
+custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
+---
+
+### Install using Swift Package Manager
+
+
+
+
+ You can install Supabase package using Swift Package Manager.
+
+ The package exposes multiple libraries, you can choose between adding all of them using Supabase, or some of:
+
+ - `Auth`
+ - `Realtime`
+ - `Postgrest`
+ - `Functions`
+ - `Storage`
+
+
+
+
+
+
+
+
+ ```swift
+ let package = Package(
+ ...
+ dependencies: [
+ ...
+ .package(
+ url: "https://github.com/supabase/supabase-swift.git",
+ from: "2.0.0"
+ ),
+ ],
+ targets: [
+ .target(
+ name: "YourTargetName",
+ dependencies: [
+ .product(
+ name: "Supabase", // Auth, Realtime, Postgrest, Functions, or Storage
+ package: "supabase-swift"
+ ),
+ ]
+ )
+ ]
+ )
+ ```
+
+
+
+
+
+
diff --git a/apps/docs/docs/ref/swift/v1/introduction.mdx b/apps/docs/docs/ref/swift/v1/introduction.mdx
new file mode 100644
index 00000000000..25c9f79a791
--- /dev/null
+++ b/apps/docs/docs/ref/swift/v1/introduction.mdx
@@ -0,0 +1 @@
+This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
diff --git a/apps/docs/features/app.providers.tsx b/apps/docs/features/app.providers.tsx
new file mode 100644
index 00000000000..dbafe0c8de6
--- /dev/null
+++ b/apps/docs/features/app.providers.tsx
@@ -0,0 +1,42 @@
+import { type PropsWithChildren } from 'react'
+
+import { ThemeProvider } from 'common'
+import { SonnerToaster } from 'ui'
+import { CommandProvider } from 'ui-patterns/CommandMenu'
+// import { PromoToast } from 'ui-patterns/PromoToast'
+import SiteLayout from '~/layouts/SiteLayout'
+import { AuthContainer } from './auth/auth.client'
+import { DocsCommandMenu } from './command'
+import { QueryClientProvider } from './data/queryClient.client'
+import { PageTelemetry } from './telemetry/telemetry.client'
+import { ScrollRestoration } from './ui/helpers.scroll.client'
+import { ThemeSandbox } from './ui/theme.client'
+
+/**
+ * Global providers that wrap the entire app
+ */
+function GlobalProviders({ children }: PropsWithChildren) {
+ return (
+
+
+
+
+
+
+
+
+ {/* */}
+ {children}
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export { GlobalProviders }
diff --git a/apps/docs/features/command/CommandMenu.tsx b/apps/docs/features/command/CommandMenu.tsx
new file mode 100644
index 00000000000..c2278063234
--- /dev/null
+++ b/apps/docs/features/command/CommandMenu.tsx
@@ -0,0 +1,33 @@
+import { CommandHeader, CommandInput, CommandList, CommandMenu } from 'ui-patterns/CommandMenu'
+import { useChangelogCommand } from 'ui-patterns/CommandMenu/prepackaged/Changelog'
+import { useDocsAiCommands } from 'ui-patterns/CommandMenu/prepackaged/DocsAi'
+import { useDocsSearchCommands } from 'ui-patterns/CommandMenu/prepackaged/DocsSearch'
+import { useSupportCommands } from 'ui-patterns/CommandMenu/prepackaged/Support'
+import { useThemeSwitcherCommands } from 'ui-patterns/CommandMenu/prepackaged/ThemeSwitcher'
+
+import { useQuickstartCommands } from './Quickstarts'
+import { useDocsNavCommands } from '../../components/Navigation/Navigation.commands'
+
+const DocsCommandMenu = () => {
+ useDocsSearchCommands({
+ modify: (command) => ({ ...command, forceMount: true }),
+ options: { forceMountSection: true },
+ })
+ useDocsAiCommands({ modify: (command) => ({ ...command, forceMount: true }) })
+ useDocsNavCommands()
+ useQuickstartCommands()
+ useSupportCommands()
+ useChangelogCommand()
+ useThemeSwitcherCommands()
+
+ return (
+
+
+
+
+
+
+ )
+}
+
+export { DocsCommandMenu as default }
diff --git a/apps/docs/features/command/Quickstarts.tsx b/apps/docs/features/command/Quickstarts.tsx
new file mode 100644
index 00000000000..15cfa1812af
--- /dev/null
+++ b/apps/docs/features/command/Quickstarts.tsx
@@ -0,0 +1,58 @@
+import { ArrowRight } from 'lucide-react'
+import { useRegisterCommands } from 'ui-patterns/CommandMenu'
+
+interface Quickstart {
+ label: string
+ url: `/${string}`
+}
+
+const quickstarts = [
+ {
+ label: 'React',
+ url: '/guides/getting-started/quickstarts/reactjs',
+ },
+ {
+ label: 'Next.js',
+ url: '/guides/getting-started/quickstarts/nextjs',
+ },
+ {
+ label: 'Nuxt.js',
+ url: '/guides/getting-started/quickstarts/nuxtjs',
+ },
+ {
+ label: 'Flutter',
+ url: '/guides/getting-started/quickstarts/flutter',
+ },
+ {
+ label: 'Kotlin',
+ url: '/guides/getting-started/quickstarts/android',
+ },
+ {
+ label: 'SvelteKit',
+ url: '/guides/getting-started/quickstarts/sveltekit',
+ },
+ {
+ label: 'SolidJS',
+ url: '/guides/getting-started/quickstarts/solidjs',
+ },
+ {
+ label: 'Vue',
+ url: '/guides/getting-started/quickstarts/vue',
+ },
+] as Quickstart[]
+
+const useQuickstartCommands = () => {
+ useRegisterCommands(
+ 'Quick starts',
+ quickstarts.map(({ label, url }) => ({
+ id: label,
+ name: `Get started with ${label}`,
+ route: url,
+ defaultHidden: true,
+ value: `Quick starts: Get started with ${label}`,
+ icon: () => ,
+ }))
+ )
+}
+
+export { useQuickstartCommands }
diff --git a/apps/docs/features/command/index.tsx b/apps/docs/features/command/index.tsx
new file mode 100644
index 00000000000..198bba2b8ee
--- /dev/null
+++ b/apps/docs/features/command/index.tsx
@@ -0,0 +1,14 @@
+'use client'
+
+import dynamic from 'next/dynamic'
+
+import { useCommandMenuInitiated } from 'ui-patterns/CommandMenu'
+
+const LazyCommandMenu = dynamic(() => import('./CommandMenu'), { ssr: false })
+
+const DocsCommandMenu = () => {
+ const isInitiated = useCommandMenuInitiated()
+ return isInitiated &&
+}
+
+export { DocsCommandMenu }
diff --git a/apps/docs/features/directives/CodeSample.client.tsx b/apps/docs/features/directives/CodeSample.client.tsx
new file mode 100644
index 00000000000..8cde6cf1ae9
--- /dev/null
+++ b/apps/docs/features/directives/CodeSample.client.tsx
@@ -0,0 +1,67 @@
+'use client'
+
+import Link from 'next/link'
+import { useState, type PropsWithChildren } from 'react'
+
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui'
+
+export function CodeSampleWrapper({
+ children,
+ /**
+ * A GitHub URL to the source code file.
+ */
+ source: _source,
+}: PropsWithChildren<{ source: string | URL | (string | URL)[] }>) {
+ const source = Array.isArray(_source) ? _source : [_source]
+
+ if (source.length === 1) {
+ return {children}
+ }
+
+ if (source.length > 1) {
+ return {children}
+ }
+
+ return <>{children}>
+}
+
+function MultipleSources({ children, sources }: PropsWithChildren<{ sources: (string | URL)[] }>) {
+ return (
+ <>
+ {children}
+
+
+
+ View sources
+
+
+
+ {sources.map((source) => (
+ window.open(source.toString(), '_blank', 'noopener noreferrer')}
+ >
+ ...{source.toString().split('/').slice(-2).join('/')}
+
+ ))}
+
+
+ >
+ )
+}
+
+function SingleSource({ children, source }: PropsWithChildren<{ source: string | URL }>) {
+ return (
+ <>
+ {children}
+
+ View source
+
+ >
+ )
+}
diff --git a/apps/docs/features/directives/CodeSample.test.ts b/apps/docs/features/directives/CodeSample.test.ts
new file mode 100644
index 00000000000..65dd9bdb26d
--- /dev/null
+++ b/apps/docs/features/directives/CodeSample.test.ts
@@ -0,0 +1,576 @@
+import { afterAll, beforeAll, describe, it, expect, vi } from 'vitest'
+
+import { fromMarkdown } from 'mdast-util-from-markdown'
+import { mdxFromMarkdown, mdxToMarkdown } from 'mdast-util-mdx'
+import { toMarkdown } from 'mdast-util-to-markdown'
+import { mdxjs } from 'micromark-extension-mdxjs'
+
+import { _createElidedLine, codeSampleRemark } from './CodeSample'
+
+const fetchFromGitHubMock = vi.fn((_params) => Promise.resolve('ok'))
+const transformWithMock = codeSampleRemark({
+ fetchFromGitHub: fetchFromGitHubMock,
+})
+
+let env: NodeJS.Process['env']
+
+describe('$CodeSample', () => {
+ beforeAll(() => {
+ env = process.env
+ process.env = { NODE_ENV: 'test', NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA: '1234567890' }
+ })
+
+ afterAll(() => {
+ process.env = env
+ })
+
+ it('should replace code sample with source code', async () => {
+ const markdown = `
+# Embed code sample
+
+<$CodeSample path="/_internal/fixtures/javascript.js" lines={[[1, -1]]} />
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+ \`\`\`javascript
+ const A = 'A'
+ const B = 3
+
+ function add(a, b) {
+ return a + b
+ }
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ function min(a, b) {
+ return a < b ? a : b
+ }
+ \`\`\`
+
+
+Some more text.
+`.trimStart()
+
+ expect(output).toEqual(expected)
+ })
+
+ it('should replace code sample and elide lines', async () => {
+ const markdown = `
+# Embed code sample
+
+<$CodeSample path="/_internal/fixtures/javascript.js" lines={[[1, 2], [8, 10]]} />
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+ \`\`\`javascript
+ const A = 'A'
+ const B = 3
+
+ // ...
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ // ...
+ \`\`\`
+
+
+Some more text.
+`.trimStart()
+
+ expect(output).toEqual(expected)
+ })
+
+ it('should handle paths without leading slash', async () => {
+ const markdown = `
+# Embed code sample
+
+<$CodeSample path="_internal/fixtures/javascript.js" lines={[[1, -1]]} />
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+ \`\`\`javascript
+ const A = 'A'
+ const B = 3
+
+ function add(a, b) {
+ return a + b
+ }
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ function min(a, b) {
+ return a < b ? a : b
+ }
+ \`\`\`
+
+
+Some more text.
+`.trimStart()
+
+ expect(output).toEqual(expected)
+ })
+
+ it('should use correct language modifier', async () => {
+ const markdown = `
+# Embed code sample
+
+<$CodeSample path="/_internal/fixtures/python.py" lines={[[1, -1]]} />
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+ \`\`\`python
+ PI = 3.14159
+ E = 2.71828
+
+ def add_numbers(a, b):
+ return a + b
+
+ def concat_strings(str1, str2):
+ return str1 + str2
+
+ # Test cases
+ if __name__ == "__main__":
+ result1 = add_numbers(3, 5)
+ print(f"add_numbers(3, 5) = {result1}") # Expected output: 8
+ \`\`\`
+
+
+Some more text.
+`.trimStart()
+
+ expect(output).toEqual(expected)
+ })
+
+ it('should fetch external code samples remotely', async () => {
+ const markdown = `
+# Embed code sample
+
+<$CodeSample
+ external={true}
+ org="supabase"
+ repo="supabase"
+ commit="68d5s42hvs7p342kl65ldk90dsafdsa"
+ path="/path/to/file.ts"
+ lines={[[1, -1]]}
+/>
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+ \`\`\`typescript
+ ok
+ \`\`\`
+
+
+Some more text.
+`.trimStart()
+
+ expect(fetchFromGitHubMock).toHaveBeenCalledTimes(1)
+ expect(fetchFromGitHubMock).toHaveBeenCalledWith({
+ org: 'supabase',
+ repo: 'supabase',
+ path: '/path/to/file.ts',
+ branch: '68d5s42hvs7p342kl65ldk90dsafdsa',
+ options: { onError: expect.any(Function), fetch: expect.any(Function) },
+ })
+ expect(output).toEqual(expected)
+ })
+
+ it('should preserve meta as code block meta if given', async () => {
+ const markdown = `
+# Embed code sample
+
+<$CodeSample
+ path="/_internal/fixtures/javascript.js"
+ lines={[[1, 2], [8, 10]]}
+ meta="utils/client.ts"
+/>
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+ \`\`\`javascript utils/client.ts
+ const A = 'A'
+ const B = 3
+
+ // ...
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ // ...
+ \`\`\`
+
+
+Some more text.
+`.trimStart()
+
+ expect(output).toEqual(expected)
+ })
+
+ it('should wrap entire CodeHike if CodeHike descendant', async () => {
+ const markdown = `
+# Embed code sample
+
+
+
+<$CodeSample
+ path="/_internal/fixtures/javascript.js"
+ lines={[[1, -1]]}
+ meta="utils/client.ts"
+/>
+
+
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+
+ \`\`\`javascript utils/client.ts
+ const A = 'A'
+ const B = 3
+
+ function add(a, b) {
+ return a + b
+ }
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ function min(a, b) {
+ return a < b ? a : b
+ }
+ \`\`\`
+
+
+
+Some more text.
+`.trimStart()
+
+ expect(output).toEqual(expected)
+ })
+
+ it('should merge multiple CodeSampleWrappers', async () => {
+ const markdown = `
+# Embed code sample
+
+
+
+<$CodeSample
+path="/_internal/fixtures/javascript.js"
+lines={[[1, -1]]}
+meta="utils/client.ts"
+/>
+
+<$CodeSample
+path="/_internal/fixtures/python.py"
+lines={[[1, -1]]}
+meta="utils/python.py"
+/>
+
+
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+
+ \`\`\`javascript utils/client.ts
+ const A = 'A'
+ const B = 3
+
+ function add(a, b) {
+ return a + b
+ }
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ function min(a, b) {
+ return a < b ? a : b
+ }
+ \`\`\`
+
+ \`\`\`python utils/python.py
+ PI = 3.14159
+ E = 2.71828
+
+ def add_numbers(a, b):
+ return a + b
+
+ def concat_strings(str1, str2):
+ return str1 + str2
+
+ # Test cases
+ if __name__ == "__main__":
+ result1 = add_numbers(3, 5)
+ print(f"add_numbers(3, 5) = {result1}") # Expected output: 8
+ \`\`\`
+
+
+
+Some more text.
+`.trimStart()
+
+ expect(output).toEqual(expected)
+ })
+
+ it('should correctly replace multiple CodeHikes', async () => {
+ const markdown = `
+# Embed code sample
+
+
+
+<$CodeSample
+ path="/_internal/fixtures/javascript.js"
+ lines={[[1, -1]]}
+ meta="utils/client1.ts"
+/>
+
+<$CodeSample
+ path="/_internal/fixtures/javascript.js"
+ lines={[[1, -1]]}
+ meta="utils/client2.ts"
+/>
+
+
+
+Another one:
+
+
+
+<$CodeSample
+ path="/_internal/fixtures/javascript.js"
+ lines={[[1, -1]]}
+ meta="utils/client3.ts"
+/>
+
+
+
+Some more text.
+`.trim()
+
+ const mdast = fromMarkdown(markdown, {
+ mdastExtensions: [mdxFromMarkdown()],
+ extensions: [mdxjs()],
+ })
+ const transformed = await transformWithMock(mdast)
+ const output = toMarkdown(transformed, { extensions: [mdxToMarkdown()] })
+
+ const expected = `
+# Embed code sample
+
+
+
+ \`\`\`javascript utils/client1.ts
+ const A = 'A'
+ const B = 3
+
+ function add(a, b) {
+ return a + b
+ }
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ function min(a, b) {
+ return a < b ? a : b
+ }
+ \`\`\`
+
+ \`\`\`javascript utils/client2.ts
+ const A = 'A'
+ const B = 3
+
+ function add(a, b) {
+ return a + b
+ }
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ function min(a, b) {
+ return a < b ? a : b
+ }
+ \`\`\`
+
+
+
+Another one:
+
+
+
+ \`\`\`javascript utils/client3.ts
+ const A = 'A'
+ const B = 3
+
+ function add(a, b) {
+ return a + b
+ }
+
+ function max(a, b) {
+ return a > b ? a : b
+ }
+
+ function min(a, b) {
+ return a < b ? a : b
+ }
+ \`\`\`
+
+
+
+Some more text.
+`.trimStart()
+
+ expect(output).toEqual(expected)
+ })
+})
+
+describe('_createElidedLine', () => {
+ it('properly preserves indentation', () => {
+ const content = `
+def add_numbers(a, b):
+ return a + b
+
+def concat_strings(str1, str2):
+ return str1 + str2
+
+# Test cases
+if __name__ == "__main__":
+ result1 = add_numbers(3, 5)
+ print(f"add_numbers(3, 5) = {result1}") # Expected output: 8
+`.trim()
+
+ const output = _createElidedLine('python', content.split('\n'), 10, 10)
+
+ const expected = '\n // ...\n'
+ expect(output).toEqual(expected)
+ })
+
+ it('properly uses comment format in JSX and TSX', () => {
+ const content = `
+const one = 'one'
+const two = 'two'
+
+function One() {
+ return (
+
+
+
+
+ )
+}
+`.trim()
+
+ const output = _createElidedLine('tsx', content.split('\n'), 4, -1)
+
+ const expected = '\n// ...\n'
+ expect(output).toEqual(expected)
+
+ const outputJsx = _createElidedLine('tsx', content.split('\n'), 8, -1)
+ const expectedJsx = '\n {/* ... */}\n'
+ expect(outputJsx).toEqual(expectedJsx)
+ })
+})
diff --git a/apps/docs/features/directives/CodeSample.ts b/apps/docs/features/directives/CodeSample.ts
new file mode 100644
index 00000000000..d97f0c3fe79
--- /dev/null
+++ b/apps/docs/features/directives/CodeSample.ts
@@ -0,0 +1,476 @@
+/**
+ * The $CodeSample directive supports inclusion of code samples from a source
+ * code file, which may be internal to this repo or external from another
+ * GitHub repo.
+ *
+ * The syntax for internal references is:
+ *
+ * ```mdx
+ * <$CodeSample
+ * path="/path/to/file.ts"
+ * lines={[1, 2], [5, 7]} // -1 may be used in end position as an alias for the last line, e.g., [1, -1]
+ * meta="utils/client.ts" // Optional, for displaying a file path on the code block
+ * />
+ * ```
+ *
+ * The syntax for external references is:
+ *
+ * ```mdx
+ * <$CodeSample
+ * external={true} // Note you must set the boolean, React pattern of omitting for true doesn't work
+ * org="supabase"
+ * repo="wrappers"
+ * commit="68d5s42hvs7p342kl65ldk90dsafdsa"
+ * path="/path/to/file.ts"
+ * lines={[1, 2], [5, 7]} // -1 may be used in end position as an alias for the last line, e.g., [1, -1]
+ * meta="utils/client.ts" // Optional, for displaying a file path on the code block
+ * />
+ */
+
+import * as acorn from 'acorn'
+import tsPlugin from 'acorn-typescript'
+import { type BlockContent, type Code, type Root } from 'mdast'
+import type {
+ MdxJsxAttribute,
+ MdxJsxAttributeValueExpression,
+ MdxJsxExpressionAttribute,
+ MdxJsxFlowElement,
+ MdxJsxFlowElementHast,
+ MdxJsxTextElement,
+ MdxJsxTextElementHast,
+} from 'mdast-util-mdx-jsx'
+import { readFile } from 'node:fs/promises'
+import { join } from 'node:path'
+import { type Parent } from 'unist'
+import { visitParents } from 'unist-util-visit-parents'
+import { z, type SafeParseError } from 'zod'
+
+import { fetchWithNextOptions } from '~/features/helpers.fetch'
+import { EXAMPLES_DIRECTORY } from '~/lib/docs'
+
+const linesSchema = z.array(z.tuple([z.coerce.number(), z.coerce.number()]))
+const linesValidator = z
+ .string()
+ .default('[[1, -1]]')
+ .transform((v, ctx) => {
+ try {
+ const array = JSON.parse(v)
+ return linesSchema.parse(array)
+ } catch (e) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'Lines should be an array of [number, number] tuples',
+ })
+ return z.NEVER
+ }
+ })
+
+type AdditionalMeta = {
+ parent: Parent
+ codeHikeAncestor: Parent | null
+ codeHikeAncestorParent: Parent | null
+}
+
+const codeSampleExternalSchema = z.object({
+ external: z.coerce.boolean().refine((v) => v === true),
+ org: z.string(),
+ repo: z.string(),
+ commit: z.string(),
+ path: z.string().transform((v) => (v.startsWith('/') ? v : `/${v}`)),
+ lines: linesValidator,
+ meta: z.string().optional(),
+})
+type ICodeSampleExternal = z.infer & AdditionalMeta
+
+const codeSampleInternalSchema = z.object({
+ external: z.coerce
+ .boolean()
+ .refine((v) => v === false)
+ .optional(),
+ path: z.string().transform((v) => (v.startsWith('/') ? v : `/${v}`)),
+ lines: linesValidator,
+ meta: z.string().optional(),
+})
+type ICodeSampleInternal = z.infer & AdditionalMeta
+
+type CodeSampleMeta = ICodeSampleExternal | ICodeSampleInternal
+
+function isExternalSource(meta: CodeSampleMeta): meta is ICodeSampleExternal {
+ return !!meta.external
+}
+
+interface Dependencies {
+ fetchFromGitHub: (params: {
+ org: string
+ repo: string
+ path: string
+ branch: string
+ options: { onError: (error: unknown) => void; fetch: (url: string) => Promise }
+ }) => Promise
+}
+
+export function codeSampleRemark(deps: Dependencies) {
+ return async function transform(tree: Root) {
+ const contentMap = await fetchSourceCodeContent(tree, deps)
+ rewriteNodes(contentMap)
+
+ return tree
+ }
+}
+
+async function fetchSourceCodeContent(tree: Root, deps: Dependencies) {
+ const codeSampleNodes = [] as MdxJsxFlowElement[]
+ const metadata = [] as CodeSampleMeta[]
+ const pendingFetches = [] as Promise[]
+
+ visitParents(tree, 'mdxJsxFlowElement', (node: MdxJsxFlowElement, ancestors) => {
+ if (node.name !== '$CodeSample') return
+
+ const codeHikeAncestorIndex = ancestors.findLastIndex(
+ (ancestor) => ancestor.type === 'mdxJsxFlowElement' && ancestor.name === 'CH.Code'
+ )
+ const codeHikeAncestor = codeHikeAncestorIndex === -1 ? null : ancestors[codeHikeAncestorIndex]
+ const codeHikeAncestorParent =
+ codeHikeAncestorIndex <= 0 ? null : ancestors[codeHikeAncestorIndex - 1]
+ const parent = ancestors[ancestors.length - 1]
+
+ const isExternal = getAttributeValueExpression(getAttributeValue(node, 'external')) === 'true'
+
+ if (isExternal) {
+ const org = getAttributeValue(node, 'org')
+ const repo = getAttributeValue(node, 'repo')
+ const commit = getAttributeValue(node, 'commit')
+ const path = getAttributeValue(node, 'path')
+ const lines = getAttributeValueExpression(getAttributeValue(node, 'lines'))
+ const meta = getAttributeValue(node, 'meta')
+
+ const result = codeSampleExternalSchema.safeParse({
+ external: isExternal,
+ org,
+ repo,
+ commit,
+ path,
+ lines,
+ meta,
+ })
+
+ if (!result.success) {
+ throw new Error(
+ `Invalid $CodeSample directive: ${(result as SafeParseError).error.message}`
+ )
+ }
+
+ const fetchTask = deps.fetchFromGitHub({
+ org: result.data.org,
+ repo: result.data.repo,
+ path: result.data.path,
+ branch: result.data.commit,
+ options: {
+ onError: (error: unknown) => {
+ throw Error(
+ `Failed to fetch code sample from ${org}/${repo}@${commit} at path ${path}: ${error}`
+ )
+ },
+ fetch: fetchWithNextOptions({ cache: 'force-cache' }),
+ },
+ })
+
+ codeSampleNodes.push(node)
+ metadata.push({ ...result.data, parent, codeHikeAncestor, codeHikeAncestorParent })
+ pendingFetches.push(fetchTask)
+ } else {
+ const path = getAttributeValue(node, 'path')
+ const lines = getAttributeValueExpression(getAttributeValue(node, 'lines'))
+ const meta = getAttributeValue(node, 'meta')
+
+ const result = codeSampleInternalSchema.safeParse({
+ external: isExternal,
+ path,
+ lines,
+ meta,
+ })
+
+ if (!result.success) {
+ throw new Error(
+ `Invalid $CodeSample directive: ${(result as SafeParseError).error.message}`
+ )
+ }
+
+ const filePath = join(EXAMPLES_DIRECTORY, result.data.path)
+ if (!filePath.startsWith(EXAMPLES_DIRECTORY)) {
+ throw new Error(`Invalid $CodeSample settings: Path must be inside ${EXAMPLES_DIRECTORY}`)
+ }
+ const fetchTask = readFile(filePath, 'utf-8')
+
+ codeSampleNodes.push(node)
+ metadata.push({ ...result.data, parent, codeHikeAncestor, codeHikeAncestorParent })
+ pendingFetches.push(fetchTask)
+ }
+ })
+
+ const resolvedContent = await Promise.all(pendingFetches)
+
+ const nodeContentMap = new Map()
+ codeSampleNodes.forEach((node, index) => {
+ nodeContentMap.set(node, [metadata[index], resolvedContent[index]])
+ })
+
+ return nodeContentMap
+}
+
+function getAttributeValue(
+ node: MdxJsxFlowElement | MdxJsxFlowElementHast | MdxJsxTextElement | MdxJsxTextElementHast,
+ attributeName: string
+) {
+ return (
+ node.attributes.find(
+ (attr: MdxJsxAttribute | MdxJsxExpressionAttribute) =>
+ 'name' in attr && attr.name === attributeName
+ )?.value ?? undefined
+ )
+}
+
+function getAttributeValueExpression(node: MdxJsxAttributeValueExpression | string | undefined) {
+ if (typeof node === 'string' || node?.type !== 'mdxJsxAttributeValueExpression') return undefined
+ return node.value
+}
+
+function rewriteNodes(contentMap: Map) {
+ for (const [node, [meta, content]] of contentMap) {
+ const lang = matchLang(meta.path.split('.').pop())
+
+ const source = isExternalSource(meta)
+ ? `https://github.com/${meta.org}/${meta.repo}/blob/${meta.commit}${meta.path}`
+ : `https://github.com/supabase/supabase/blob/${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ?? 'master'}/examples${meta.path}`
+
+ const elidedContent = redactLines(content, meta.lines, lang)
+
+ const replacementContent: MdxJsxFlowElement | Code = meta.codeHikeAncestor
+ ? {
+ type: 'code',
+ lang,
+ meta: meta.meta,
+ value: elidedContent,
+ }
+ : {
+ type: 'mdxJsxFlowElement',
+ name: 'CodeSampleWrapper',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'source',
+ value: source,
+ },
+ ],
+ children: [
+ {
+ type: 'code',
+ lang,
+ meta: meta.meta,
+ value: elidedContent,
+ },
+ ],
+ }
+ meta.parent.children.splice(meta.parent.children.indexOf(node), 1, replacementContent)
+
+ if (meta.codeHikeAncestor && meta.codeHikeAncestorParent) {
+ const existingWrapper = meta.codeHikeAncestorParent.children.find(
+ (child) =>
+ child.type === 'mdxJsxFlowElement' &&
+ (child as MdxJsxFlowElement).name === 'CodeSampleWrapper' &&
+ (child as MdxJsxFlowElement).children?.[0] === meta.codeHikeAncestor
+ ) as MdxJsxFlowElement | undefined
+ if (existingWrapper) {
+ const existingSource = getAttributeValue(existingWrapper, 'source')
+ if (typeof existingSource === 'string' && existingSource !== source) {
+ const newSource = createArrayAttributeValueExpression(existingSource, source)
+ existingWrapper.attributes[0].value = newSource
+ } else if (
+ typeof existingSource !== 'string' &&
+ existingSource.type === 'mdxJsxAttributeValueExpression'
+ ) {
+ const existingSourceArray =
+ // @ts-ignore
+ existingSource.data.estree.body[0]?.expression?.elements?.map(
+ (element) => element.value
+ ) ?? []
+ const newSource = createArrayAttributeValueExpression(...existingSourceArray, source)
+ existingWrapper.attributes[0].value = newSource
+ }
+ } else {
+ const codeSampleWrapper: MdxJsxFlowElement = {
+ type: 'mdxJsxFlowElement',
+ name: 'CodeSampleWrapper',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'source',
+ value: source,
+ },
+ ],
+ children: [meta.codeHikeAncestor as BlockContent],
+ }
+ meta.codeHikeAncestorParent.children.splice(
+ meta.codeHikeAncestorParent.children.indexOf(meta.codeHikeAncestor),
+ 1,
+ codeSampleWrapper
+ )
+ }
+ }
+ }
+}
+
+function matchLang(lang: string) {
+ switch (lang) {
+ case 'tsx':
+ return 'tsx'
+ case 'ts':
+ return 'typescript'
+ case 'jsx':
+ return 'jsx'
+ case 'js':
+ return 'javascript'
+ case 'json':
+ return 'json'
+ case 'py':
+ return 'python'
+ case 'sh':
+ return 'bash'
+ case 'kt':
+ return 'kotlin'
+ case 'dart':
+ return 'dart'
+ case 'swift':
+ return 'swift'
+ case 'sql':
+ return 'sql'
+ default:
+ return null
+ }
+}
+
+function redactLines(
+ content: string,
+ lines: [number, number, ...unknown[]][],
+ lang: string | null
+) {
+ const contentLines = content.split('\n')
+ const preservedLines = lines.reduce((acc, [start, end], index, arr) => {
+ if (index !== 0 || start !== 1) {
+ acc.push(_createElidedLine(lang, contentLines, start, end))
+ }
+
+ // Start and end are 1-indexed and inclusive
+ acc.push(...contentLines.slice(start - 1, end === -1 ? contentLines.length : end))
+
+ if (index === arr.length - 1 && end !== -1 && end !== contentLines.length) {
+ acc.push(_createElidedLine(lang, contentLines, start, end))
+ }
+
+ return acc
+ }, [] as string[])
+
+ return preservedLines.join('\n').trim()
+}
+
+export function _createElidedLine(
+ lang: string | null,
+ lines: string[],
+ start: number,
+ end: number
+) {
+ const indentation = lines[start - 1].match(/^\s*/)?.[0] ?? ''
+
+ switch (lang) {
+ case 'sql':
+ return `\n${indentation}-- ...\n`
+ case 'jsx':
+ case 'tsx':
+ // @ts-ignore
+ const acornTree = acorn.Parser.extend(tsPlugin()).parse(lines.join('\n'), {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ locations: true,
+ })
+ const isWithinJsx = isContainedInJsx(acornTree, start)
+ if (isWithinJsx) {
+ return `\n${indentation}{/* ... */}\n`
+ } else {
+ return `\n${indentation}// ...\n`
+ }
+ default:
+ return `\n${indentation}// ...\n`
+ }
+}
+
+function isContainedInJsx(tree: acorn.Node, line: number) {
+ const acornNodeContainsLine = (node: acorn.Node, line) =>
+ node.loc?.start.line <= line && node.loc?.end.line >= line
+ if (!acornNodeContainsLine(tree, line)) {
+ return false
+ }
+
+ let candidateNarrowestContainingNode = tree
+
+ function getNarrowestContainingNode(node: acorn.Node, line: number) {
+ for (const key of Object.keys(node)) {
+ const value = node[key]
+ if (!value || typeof value !== 'object') {
+ continue
+ }
+
+ if (!Array.isArray(value)) {
+ if (acornNodeContainsLine(value, line)) {
+ candidateNarrowestContainingNode = value
+ getNarrowestContainingNode(value, line)
+ }
+ } else {
+ for (const child of value) {
+ if (!acornNodeContainsLine(child, line)) {
+ continue
+ } else {
+ if (
+ child.loc?.start?.line > candidateNarrowestContainingNode.loc?.start?.line ||
+ child.loc.end.line < candidateNarrowestContainingNode.loc?.end?.line ||
+ child.loc.start.column > candidateNarrowestContainingNode.loc?.start.column ||
+ child.loc.end.column < candidateNarrowestContainingNode.loc?.end.column
+ ) {
+ candidateNarrowestContainingNode = child
+ getNarrowestContainingNode(child, line)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ getNarrowestContainingNode(tree, line)
+ return candidateNarrowestContainingNode.type.startsWith('JSX')
+}
+
+function createArrayAttributeValueExpression(...arrayElements: string[]) {
+ const expression: MdxJsxAttributeValueExpression = {
+ type: 'mdxJsxAttributeValueExpression',
+ value: '[' + arrayElements.map((element) => `'${element}'`).join(', ') + ']',
+ data: {
+ estree: {
+ type: 'Program',
+ sourceType: 'module',
+ body: [
+ {
+ type: 'ExpressionStatement',
+ expression: {
+ type: 'ArrayExpression',
+ elements: arrayElements.map((element) => ({
+ type: 'Literal',
+ value: element,
+ raw: element,
+ })),
+ },
+ },
+ ],
+ },
+ },
+ }
+ return expression
+}
diff --git a/apps/docs/features/directives/README.md b/apps/docs/features/directives/README.md
new file mode 100644
index 00000000000..e033a4e09e1
--- /dev/null
+++ b/apps/docs/features/directives/README.md
@@ -0,0 +1,22 @@
+# Directives
+
+Directives are a custom feature of the Supabase docs content system, which allows you to extend MDX to provide custom functionality.
+
+## Why not a React component?
+
+MDX supports React components, and that is the preferred way to add new features. If your use case is supported by a React component alone, use that instead.
+
+Custom directives are used to implement features that need low-level parse or compile-time control over the MDX AST.
+
+## Syntax
+
+We reserve a special syntax for directives, which start with a `$` sign. For example:
+
+```mdx
+<$CodeSample />
+```
+
+This syntax was chosen because it is both:
+
+- Sufficiently standard to be supported by MDX parsers without needing to build a custom extension.
+- Sufficiently uncommon to avoid collisions with other React components used in docs.
diff --git a/apps/docs/features/directives/utils.ts b/apps/docs/features/directives/utils.ts
new file mode 100644
index 00000000000..eb67d138d91
--- /dev/null
+++ b/apps/docs/features/directives/utils.ts
@@ -0,0 +1,36 @@
+import { type Root } from 'mdast'
+import { fromMarkdown } from 'mdast-util-from-markdown'
+import { gfmFromMarkdown, gfmToMarkdown } from 'mdast-util-gfm'
+import { mdxFromMarkdown, mdxToMarkdown } from 'mdast-util-mdx'
+import { toMarkdown } from 'mdast-util-to-markdown'
+import { gfm } from 'micromark-extension-gfm'
+import { mdxjs } from 'micromark-extension-mdxjs'
+
+import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
+import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
+import { getGitHubFileContents } from '~/lib/octokit'
+import { codeSampleRemark } from './CodeSample'
+
+type Transformer = (ast: Root) => Root | Promise
+
+export async function preprocessMdx(mdx: string, transformers: Transformer[]) {
+ let mdast = fromMarkdown(mdx, {
+ mdastExtensions: [mdxFromMarkdown(), gfmFromMarkdown()],
+ extensions: [mdxjs(), gfm()],
+ })
+
+ for (const transform of transformers) {
+ mdast = await transform(mdast)
+ }
+
+ const output = toMarkdown(mdast, { extensions: [mdxToMarkdown(), gfmToMarkdown()] })
+ return output
+}
+
+export function preprocessMdxWithDefaults(mdx: string) {
+ return preprocessMdx(mdx, [
+ remarkMkDocsAdmonition(),
+ remarkPyMdownTabs(),
+ codeSampleRemark({ fetchFromGitHub: getGitHubFileContents }),
+ ])
+}
diff --git a/apps/docs/features/docs/GuidesMdx.client.tsx b/apps/docs/features/docs/GuidesMdx.client.tsx
new file mode 100644
index 00000000000..b0bc609650b
--- /dev/null
+++ b/apps/docs/features/docs/GuidesMdx.client.tsx
@@ -0,0 +1,16 @@
+'use client'
+
+/**
+ * The MDXProvider is necessary so that MDX partials will have access
+ * to components.
+ */
+
+import { MDXProvider } from '@mdx-js/react'
+import { type PropsWithChildren } from 'react'
+import { components } from '~/features/docs/MdxBase.shared'
+
+const MDXProviderGuides = ({ children }: PropsWithChildren) => (
+ {children}
+)
+
+export { MDXProviderGuides }
diff --git a/apps/docs/features/docs/GuidesMdx.template.tsx b/apps/docs/features/docs/GuidesMdx.template.tsx
new file mode 100644
index 00000000000..7e5bf96871e
--- /dev/null
+++ b/apps/docs/features/docs/GuidesMdx.template.tsx
@@ -0,0 +1,131 @@
+import { ExternalLink } from 'lucide-react'
+import { type SerializeOptions } from 'next-mdx-remote/dist/types'
+import { type ReactNode } from 'react'
+
+import { cn } from 'ui'
+
+import Breadcrumbs from '~/components/Breadcrumbs'
+import GuidesTableOfContents from '~/components/GuidesTableOfContents'
+import { MDXProviderGuides } from '~/features/docs/GuidesMdx.client'
+import { MDXRemoteBase } from '~/features/docs/MdxBase'
+import type { WithRequired } from '~/features/helpers.types'
+import { type GuideFrontmatter } from '~/lib/docs'
+
+const EDIT_LINK_SYMBOL = Symbol('edit link')
+interface EditLink {
+ [EDIT_LINK_SYMBOL]: true
+ link: string
+ includesProtocol: boolean
+}
+
+/**
+ * Create an object representing a link where the original content can be
+ * edited.
+ *
+ * Takes either a relative path, which will be prefixed with
+ * `https://github.com/`, or a full URL including protocol.
+ */
+const newEditLink = (str: string): EditLink => {
+ if (str.startsWith('/')) {
+ throw Error(`Edit links cannot start with slashes. Received: ${str}`)
+ }
+
+ /**
+ * Catch strings that provide FQDNS without https?:
+ *
+ * At the start of a string, before the first slash, there is a dot
+ * surrounded by non-slash characters.
+ */
+ if (/^[^\/]+\.[^\/]+\//.test(str)) {
+ throw Error(`Fully qualified domain names must start with 'https?'. Received: ${str}`)
+ }
+
+ return {
+ [EDIT_LINK_SYMBOL]: true,
+ link: str,
+ includesProtocol: str.startsWith('http://') || str.startsWith('https://'),
+ }
+}
+
+interface BaseGuideTemplateProps {
+ meta?: GuideFrontmatter
+ content?: string
+ children?: ReactNode
+ editLink: EditLink
+ mdxOptions?: SerializeOptions
+}
+
+type GuideTemplateProps =
+ | WithRequired
+ | WithRequired
+
+const GuideTemplate = ({ meta, content, children, editLink, mdxOptions }: GuideTemplateProps) => {
+ const hideToc = meta?.hideToc || meta?.hide_table_of_contents
+
+ return (
+
+ )
+}
diff --git a/apps/docs/features/docs/Reference.ui.shared.ts b/apps/docs/features/docs/Reference.ui.shared.ts
new file mode 100644
index 00000000000..b0db50fbc8e
--- /dev/null
+++ b/apps/docs/features/docs/Reference.ui.shared.ts
@@ -0,0 +1,5 @@
+export const API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES = {
+ KEY: 'data-sb-api-reference-request-body-schema',
+ VALUE_JSON: 'application/json',
+ VALUE_FORM_URLENCODED: 'application/x-www-form-urlencoded',
+}
diff --git a/apps/docs/features/docs/Reference.ui.tsx b/apps/docs/features/docs/Reference.ui.tsx
new file mode 100644
index 00000000000..93d4c18f588
--- /dev/null
+++ b/apps/docs/features/docs/Reference.ui.tsx
@@ -0,0 +1,878 @@
+import { isEqual } from 'lodash'
+import { ChevronRight, XCircle } from 'lucide-react'
+import type { HTMLAttributes, PropsWithChildren } from 'react'
+
+import {
+ Collapsible_Shadcn_,
+ CollapsibleContent_Shadcn_,
+ CollapsibleTrigger_Shadcn_,
+ cn,
+ CodeBlock,
+} from 'ui'
+
+import { MDXRemoteBase } from '~/features/docs/MdxBase'
+import { MDXRemoteRefs } from '~/features/docs/Reference.mdx'
+import type {
+ CustomTypePropertyType,
+ FunctionParameterType,
+ MethodTypes,
+ TypeDetails,
+} from '~/features/docs/Reference.typeSpec'
+import { TYPESPEC_NODE_ANONYMOUS } from '~/features/docs/Reference.typeSpec'
+import { ReferenceSectionWrapper } from '~/features/docs/Reference.ui.client'
+import { normalizeMarkdown } from '~/features/docs/Reference.utils'
+import { getTypeDisplayFromSchema, IApiEndPoint, type ISchema } from './Reference.api.utils'
+import { API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES } from './Reference.ui.shared'
+import ReactMarkdown from 'react-markdown'
+import ApiSchema from '~/components/ApiSchema'
+
+interface SectionProps extends PropsWithChildren {
+ link: string
+ slug?: string
+ columns?: 'single' | 'double'
+}
+
+function Section({ slug, link, columns = 'single', children }: SectionProps) {
+ const singleColumn = columns === 'single'
+
+ return (
+
+ {children}
+
+ )
+}
+
+function Details({ children }: PropsWithChildren) {
+ /*
+ * `min-w` is necessary because these are used as grid children, which have
+ * default `min-w-auto`
+ */
+ return
{children}
+}
+
+function Examples({ children }: PropsWithChildren) {
+ /*
+ * `min-w` is necessary because these are used as grid children, which have
+ * default `min-w-auto`
+ */
+ return