Merge branch 'master' into feat/move-realtime-publications

This commit is contained in:
Saxon Fletcher authored and GitHub committed 2024-12-09 07:58:02 +10:00
commit 00f7ae6981
899 files changed
+33159 -14868

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
name: '[Docs] Lint v2'
on:
pull_request:
paths:
- '.github/workflows/docs-lint-v2.yml'
- 'supa-mdx-lint.config.toml'
- 'apps/docs/content/**'
env:
CARGO_NET_GIT_FETCH_WITH_CLI: true
jobs:
supa-mdx-lint:
name: supa-mdx-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
sparse-checkout: |
supa-mdx-lint.config.toml
supa-mdx-lint
apps/docs/content
- name: cache cargo
id: cache-cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: 6435a4cd1eeea7c2bbd343731de7e8a5127cb2d1
- name: install linter
if: steps.cache-cargo.outputs.cache-hit != 'true'
run: cargo install --locked --git https://github.com/supabase-community/supa-mdx-lint --rev 6435a4cd1eeea7c2bbd343731de7e8a5127cb2d1
- name: install reviewdog
uses: reviewdog/action-setup@3f401fe1d58fe77e10d665ab713057375e39b887 # v1.3.0
with:
reviewdog_version: v0.20.2
- name: run linter
env:
BASE_REF: ${{ github.base_ref }}
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -o pipefail
git diff --name-only origin/$BASE_REF HEAD \
| { grep -E "^apps/docs/content/guides/(getting-started|ai|api|auth|database|deployment|functions)/" || test $? = 1; } \
| xargs -r supa-mdx-lint --format rdf \
| reviewdog -f=rdjsonl -reporter=github-pr-review
+11 -5
View File
@@ -11,15 +11,21 @@ jobs:
name: runner / vale
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
sparse-checkout: |
.vale.ini
vale
apps/docs/content
- name: Get changed files
id: changed-files
env:
BASE_REF: ${{ github.base_ref }}
SHA: ${{ github.sha }}
run: |
git fetch origin $BASE_REF
echo "CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/$BASE_REF $SHA | grep '^apps/docs/content/' | tr '\n' ',' | sed 's/,$//')" >> $GITHUB_OUTPUT
- uses: errata-ai/vale-action@reviewdog
with:
files: apps/docs/content
# [Charis] Temporary because we still have to go through giant backlog of existing errors
# Don't want people to worry about this (just want them looking at the specific PR comments)
# fail_on_error: true
files: '${{ steps.changed-files.outputs.CHANGED_FILES }}'
separator: ','
@@ -0,0 +1,25 @@
name: Self-Host Tests Smoke
on:
pull_request:
branches: ['master']
paths:
- 'docker/**/*'
# Cancel old builds on new commit for same workflow + branch/PR
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
docker/
- name: Run docker-compose up
# Ensure all services can be started and healthy with default config
run: cd docker && cp .env.example .env && docker compose up --wait
+1
View File
@@ -7,6 +7,7 @@ apps/**/out
# prettier-plugin-sql-cst only supports sqlite syntax
**/supabase/migrations/*.sql
apps/www/schema.sql
apps/www/public/images/*
apps/docs/**/generated/*
examples/slack-clone/nextjs-slack-clone/full-schema.sql
# ignore files with custom js formatting
+4 -2
View File
@@ -167,7 +167,7 @@ const PI = 3.14
```
````
Of, you can use the `<$CodeSample />` component to include code samples from a source code file.
Or, 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:
@@ -183,7 +183,7 @@ meta="display/path.js"
/>
```
If the file is within some other GitHub repo (note that the repo must be public):
If the file is within some other GitHub repo:
```mdx
<$CodeSample
@@ -197,6 +197,8 @@ meta="display/path.js"
/>
```
The repo must be public, the org must be on the allow list, and the commit must be an immutable SHA (not a mutable tag or branch name).
### Icons
The following icons are available. They can be styled with [Tailwind](https://tailwindcss.com/) classes:
@@ -5,6 +5,8 @@ import {
} from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
export const dynamicParams = false
type Params = { slug: string[] }
const GuidePage = async ({ params }: { params: Params }) => {
@@ -0,0 +1,96 @@
import type { Code, Heading, Root } from 'mdast'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { toMarkdown } from 'mdast-util-to-markdown'
import { readdir, readFile, stat } from 'node:fs/promises'
import { basename, extname, join } from 'node:path'
import { cache } from 'react'
import { visit, EXIT } from 'unist-util-visit'
import { EXAMPLES_DIRECTORY } from '~/lib/docs'
const PROMPTS_DIRECTORY = join(EXAMPLES_DIRECTORY, 'prompts')
function parseMarkdown(markdown: string) {
const mdast = fromMarkdown(markdown)
let heading = ''
visit(mdast, 'heading', (node: Heading) => {
if (node.depth === 1) {
if ('value' in node.children[0]) {
heading = node.children[0].value
}
return EXIT
}
})
const codeBlock: Code = {
type: 'code',
lang: 'markdown',
value: markdown,
}
const root: Root = {
type: 'root',
children: [codeBlock],
}
const content = toMarkdown(root)
return { heading, content }
}
async function getAiPromptsImpl() {
const directoryContents = await readdir(PROMPTS_DIRECTORY)
const prompts = directoryContents
.filter(async (file) => {
if (extname(file) !== '.md') {
return false
}
const fileStats = await stat(join(PROMPTS_DIRECTORY, file))
const isFile = fileStats.isFile()
return isFile
})
.map(async (filename) => {
const rawContent = await readFile(join(PROMPTS_DIRECTORY, filename), 'utf-8')
const { heading, content } = parseMarkdown(rawContent)
return {
filename: basename(filename, '.md'),
heading,
content,
}
})
return (await Promise.all(prompts)).sort((a, b) => b.filename.localeCompare(a.filename))
}
export const getAiPrompts = cache(getAiPromptsImpl)
async function getAiPromptImpl(prompt: string) {
const filePath = join(PROMPTS_DIRECTORY, `${prompt}.md`)
try {
const rawContent = await readFile(filePath, 'utf-8')
const { heading, content } = parseMarkdown(rawContent)
return { heading, content }
} catch (err) {
console.error('Failed to fetch prompt from repo: %o', err)
}
}
export const getAiPrompt = cache(getAiPromptImpl)
export async function generateAiPromptMetadata({ params: { slug } }: { params: { slug: string } }) {
const prompt = await getAiPrompt(slug)
return {
title: `AI Prompt: ${prompt.heading} | Supabase Docs`,
}
}
export async function generateAiPromptsStaticParams() {
const prompts = await getAiPrompts()
return prompts.map((prompt) => {
return {
slug: prompt.filename,
}
})
}
@@ -0,0 +1,26 @@
import Link from 'next/link'
import { GlassPanel } from 'ui-patterns'
import { getAiPrompts } from './AiPrompts.utils'
export async function AiPromptsIndex() {
const prompts = await getAiPrompts()
return (
<div className="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-6 not-prose">
{prompts.map((prompt) => (
<Link
key={prompt.filename}
href={`/guides/getting-started/ai-prompts/${prompt.filename}`}
passHref
>
<GlassPanel
key={prompt.filename}
title={prompt.heading ?? prompt.filename.replaceAll('-', ' ')}
/>
</Link>
))}
</div>
)
}
@@ -0,0 +1,34 @@
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import {
generateAiPromptMetadata,
generateAiPromptsStaticParams,
getAiPrompt,
} from './AiPrompts.utils'
export const dynamicParams = false
export default async function AiPromptsPage({ params: { slug } }: { params: { slug: string } }) {
let { heading, content } = await getAiPrompt(slug)
content = `
## How to use
Copy the prompt to a file in your repo.
Use the "include file" feature from your AI tool to include the prompt when chatting with your AI assistant. For example, with GitHub Copilot, use \`#<filename>\`, in Cursor, use \`@Files\`, and in Zed, use \`/file\`.
## Prompt
${content}
`.trim()
return (
<GuideTemplate
meta={{ title: `API Prompt: ${heading}` }}
content={content}
editLink={newEditLink(`supabase/supabase/blob/master/examples/prompts/${slug}.md`)}
/>
)
}
export const generateMetadata = generateAiPromptMetadata
export const generateStaticParams = generateAiPromptsStaticParams
+40 -10
View File
@@ -1,31 +1,61 @@
import { type PropsWithChildren } from 'react'
import { cache, type PropsWithChildren } from 'react'
import { IS_PLATFORM } from 'common'
import { supabaseMisc } from '~/lib/supabaseMisc'
import { NavMenuSection } from '~/components/Navigation/Navigation.types'
import Layout from '~/layouts/guides'
import { supabaseMisc } from '~/lib/supabaseMisc'
import { getAiPrompts } from './getting-started/ai-prompts/[slug]/AiPrompts.utils'
// Revalidate occasionally to pick up changes to partners
// 60 seconds/minute * 60 minutes/hour * 24 hours/day
export const revalidate = 86_400
const GuidesLayout = async ({ children }: PropsWithChildren) => {
const partners = IS_PLATFORM ? await getPartners() : []
const partnerNavItems = partners.map((partner) => ({
name: partner.title,
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
}))
const [partnerNavItems, promptNavItems] = await Promise.all([getPartners(), getPrompts()])
return <Layout additionalNavItems={partnerNavItems}>{children}</Layout>
const additionalNavItems =
partnerNavItems.length > 0 || promptNavItems.length > 0
? { integrations: partnerNavItems, prompts: promptNavItems }
: undefined
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
}
async function getPartners() {
async function getPrompts() {
const prompts = await getAiPrompts()
return prompts.map(
(prompt) =>
({
name: prompt.heading,
url: `/guides/getting-started/ai-prompts/${prompt.filename}`,
}) as Partial<NavMenuSection>
)
}
const getPartners = cache(getPartnersImpl)
async function getPartnersImpl() {
if (!IS_PLATFORM) return []
const { data, error } = await supabaseMisc()
.from('partners')
.select('slug, title')
.eq('approved', true)
.eq('type', 'technology')
.order('title')
if (error) {
console.error(new Error('Error fetching partners', { cause: error }))
}
return data ?? []
const partnerNavItems = (data ?? []).map(
(partner) =>
({
name: partner.title,
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
}) as Partial<NavMenuSection>
)
return partnerNavItems
}
export default GuidesLayout
@@ -14,7 +14,7 @@ export default async function TroubleshootingEntryPage({
params: { slug: string }
}) {
const allTroubleshootingEntries = await getAllTroubleshootingEntries()
const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry.data) === slug)
const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry) === slug)
if (!entry) {
notFound()
@@ -25,7 +25,7 @@ export default async function TroubleshootingEntryPage({
export const generateMetadata = async ({ params: { slug } }: { params: { slug: string } }) => {
const allTroubleshootingEntries = await getAllTroubleshootingEntries()
const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry.data) === slug)
const entry = allTroubleshootingEntries.find((entry) => getArticleSlug(entry) === slug)
return {
title: 'Supabase Docs | Troubleshooting' + (entry ? ` | ${entry.data.title}` : ''),
@@ -37,5 +37,5 @@ export const generateMetadata = async ({ params: { slug } }: { params: { slug: s
export const generateStaticParams = async () => {
const allTroubleshootingEntries = await getAllTroubleshootingEntries()
return allTroubleshootingEntries.map((entry) => ({ slug: getArticleSlug(entry.data) }))
return allTroubleshootingEntries.map((entry) => ({ slug: getArticleSlug(entry) }))
}
+45 -9
View File
@@ -1,6 +1,6 @@
import { type Metadata, type ResolvingMetadata } from 'next'
import Link from 'next/link'
import { IconBackground, TextLink } from 'ui'
import { cn, IconBackground, TextLink } from 'ui'
import { IconPanel } from 'ui-patterns/IconPanel'
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
@@ -29,6 +29,7 @@ const products = [
href: '/guides/database/overview',
description:
'Supabase provides a full Postgres database for every project with Realtime functionality, database backups, extensions, and more.',
span: 'col-span-12 md:col-span-6',
},
{
title: 'Auth',
@@ -37,6 +38,7 @@ const products = [
href: '/guides/auth',
description:
'Add and manage email and password, passwordless, OAuth, and mobile logins to your project through a suite of identity providers and APIs.',
span: 'col-span-12 md:col-span-6',
},
{
title: 'Storage',
@@ -46,13 +48,6 @@ const products = [
description:
'Store, organize, transform, and serve large files—fully integrated with your Postgres database with Row Level Security access policies.',
},
{
title: 'AI & Vectors',
icon: 'ai',
hasLightIcon: true,
href: '/guides/ai',
description: 'Use Supabase to store and search embedding vectors.',
},
{
title: 'Realtime',
icon: 'realtime',
@@ -71,6 +66,27 @@ const products = [
},
]
const postgresIntegrations = [
{
title: 'AI & Vectors',
icon: 'ai',
href: '/guides/ai',
description: 'AI toolkit to manage embeddings',
},
{
title: 'Cron',
icon: 'cron',
href: '/guides/cron',
description: 'Schedule and monitor recurring Jobs',
},
{
title: 'Queues',
icon: 'queues',
href: '/guides/queues',
description: 'Durable Message Queues with guaranteed delivery',
},
]
const selfHostingOptions = [
{
title: 'Auth',
@@ -161,7 +177,7 @@ const HomePage = () => (
<ul className="grid grid-cols-12 gap-6 not-prose [&_svg]:text-brand-600">
{products.map((product) => {
return (
<li key={product.title} className="col-span-12 md:col-span-4">
<li key={product.title} className={cn(product.span ?? 'col-span-12 md:col-span-4')}>
<Link href={product.href} passHref>
<GlassPanelWithIconPicker {...product}>
{product.description}
@@ -172,6 +188,26 @@ const HomePage = () => (
})}
</ul>
<div className="flex flex-col lg:grid grid-cols-12 gap-6 py-12 border-b">
<div className="col-span-4">
<h2 id="postgres-integrations" className="scroll-mt-24 m-0">
Postgres Modules
</h2>
</div>
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
{postgresIntegrations.map((integration) => (
<Link
href={integration.href}
key={integration.title}
passHref
className="col-span-6 md:col-span-4"
>
<IconPanelWithIconPicker {...integration} />
</Link>
))}
</div>
</div>
<div className="flex flex-col lg:grid grid-cols-12 gap-6 py-12 border-b">
<div className="col-span-4 flex flex-col gap-1 [&_h2]:m-0 [&_h3]:m-0">
<div className="md:max-w-xs 2xl:max-w-none">
+9
View File
@@ -166,6 +166,15 @@ function useBreadcrumbs() {
return breadcrumbs
}
const isAiPromptsPage = pathname.startsWith('/guides/getting-started/ai-prompts')
if (isAiPromptsPage) {
const breadcrumbs = [
{ name: 'Getting started', url: '/guides/getting-started' },
{ name: 'AI Prompts', url: '/guides/getting-started/ai-prompts' },
]
return breadcrumbs
}
const menuId = getMenuId(pathname)
const menu = NavItems[menuId]
return findMenuItemByUrl(menu, pathname, [])
+31 -13
View File
@@ -1,7 +1,7 @@
'use client'
import { usePathname } from 'next/navigation'
import { useEffect, useState } from 'react'
import { Fragment, useEffect, useState } from 'react'
import { cn } from 'ui'
import { ExpandableVideo } from 'ui-patterns/ExpandableVideo'
import { proxy, useSnapshot } from 'valtio'
@@ -20,23 +20,32 @@ const formatSlug = (slug: string) => {
return slug
}
const formatTOCHeader = (content: string) => {
let begin = false
const res: Array<string> = []
function formatTOCHeader(content: string) {
let insideInlineCode = false
const res: Array<{ type: 'text'; value: string } | { type: 'code'; value: string }> = []
for (const x of content) {
if (x === '`') {
if (!begin) {
begin = true
res.push(`<code class="text-xs border rounded bg-muted">`)
if (!insideInlineCode) {
insideInlineCode = true
res.push({ type: 'code', value: '' })
} else {
begin = false
res.push(`</code>`)
insideInlineCode = false
}
} else {
res.push(x)
if (insideInlineCode) {
res[res.length - 1].value += x
} else {
if (res.length === 0 || res[res.length - 1].type === 'code') {
res.push({ type: 'text', value: x })
} else {
res[res.length - 1].value += x
}
}
}
}
return res.join('')
return res
}
const tocRenderSwitch = proxy({
@@ -140,8 +149,17 @@ const GuidesTableOfContents = ({
<a
href={`#${formatSlug(item.link)}`}
className="text-foreground-lighter hover:text-brand-link transition-colors"
dangerouslySetInnerHTML={{ __html: formatTOCHeader(removeAnchor(item.text)) }}
/>
>
{formatTOCHeader(removeAnchor(item.text)).map((x, index) => (
<Fragment key={index}>
{x.type === 'code' ? (
<code className="text-xs border rounded bg-muted">{x.value}</code>
) : (
x.value
)}
</Fragment>
))}
</a>
</li>
))}
</ul>
+40 -16
View File
@@ -1,11 +1,32 @@
'use client'
import { useBreakpoint } from 'common'
import { ChevronRight, Play, Sparkles } from 'lucide-react'
import Link from 'next/link'
import { IconBackground } from 'ui'
import { useBreakpoint } from 'common'
import { cn, IconBackground } from 'ui'
import { IconPanel } from 'ui-patterns/IconPanel'
import DocsCoverLogo from './DocsCoverLogo'
import { Play } from 'lucide-react'
function AiPrompt({ className }: { className?: string }) {
return (
<Link
className={cn(
'group',
'w-fit rounded-full border px-3 py-1 flex gap-2 items-center text-foreground-light text-sm',
'hover:border-brand hover:text-brand focus-visible:text-brand',
'transition-colors',
className
)}
href="/guides/getting-started/ai-prompts"
>
<Sparkles size={14} />
Start with Supabase AI prompts
<ChevronRight size={14} className="group-hover:translate-x-1 transition-transform" />
</Link>
)
}
const HomePageCover = (props) => {
const isXs = useBreakpoint(639)
@@ -74,7 +95,7 @@ const HomePageCover = (props) => {
p-5 md:p-8
"
>
<div className="col-span-full flex flex-col md:flex-row xl:flex-col justify-between gap-1 md:gap-3">
<div className="col-span-full flex flex-col md:flex-row xl:flex-col justify-between gap-3">
<div className="md:max-w-xs shrink w-fit xl:max-w-none">
<div className="flex items-center gap-3 mb-3">
<IconBackground>
@@ -83,20 +104,23 @@ const HomePageCover = (props) => {
<h2 className="text-2xl m-0 text-foreground">Getting Started</h2>
</div>
<p className="text-foreground-light text-sm">
Discover how to set up a database to an app making queries in just a few minutes.
Set up and connect a database in just a few minutes.
</p>
</div>
<div className="flex shrink-0 flex-wrap md:grid md:grid-cols-5 gap-2 sm:gap-3">
{frameworks.map((framework, i) => (
<Link key={i} href={framework.href} passHref className="no-underline">
<IconPanel
iconSize={iconSize}
hideArrow
tooltip={framework.tooltip}
icon={framework.icon}
/>
</Link>
))}
<div className="shrink-0">
<div className="flex flex-wrap md:grid md:grid-cols-5 gap-2 sm:gap-3">
{frameworks.map((framework, i) => (
<Link key={i} href={framework.href} passHref className="no-underline">
<IconPanel
iconSize={iconSize}
hideArrow
tooltip={framework.tooltip}
icon={framework.icon}
/>
</Link>
))}
</div>
<AiPrompt className="mt-6" />
</div>
</div>
</div>
@@ -17,6 +17,7 @@ To supplement HTTP status codes, Supabase Auth returns a string error code which
| `email_provider_disabled` | Signups are disabled for email and password. |
| `flow_state_expired` | PKCE flow state to which the API request relates has expired. Ask the user to sign in again. |
| `flow_state_not_found` | PKCE flow state to which the API request relates no longer exists. Flow states expire after a while and are progressively cleaned up, which can cause this error. Retried requests can cause this error, as the previous request likely destroyed the flow state. Ask the user to sign in again. |
| `hook_payload_invalid_content_type` | Payload from Auth does not have a valid Content-Type header. |
| `hook_payload_over_size_limit` | Payload from Auth exceeds maximum size limit. |
| `hook_timeout` | Unable to reach hook within maximum time allocated. |
| `hook_timeout_after_retry` | Unable to reach hook after maximum number of retries. |
@@ -30,13 +31,15 @@ To supplement HTTP status codes, Supabase Auth returns a string error code which
| `mfa_factor_name_conflict` | MFA factors for a single user should not have the same friendly name. |
| `mfa_factor_not_found` | MFA factor no longer exists. |
| `mfa_ip_address_mismatch` | The enrollment process for MFA factors must begin and end with the same IP address. |
| `mfa_phone_enroll_not_enabled` | Enrollment of MFA Phone factors is disabled. |
| `mfa_phone_verify_not_enabled` | Login via Phone factors and verification of new Phone factors is disabled. |
| `mfa_totp_enroll_not_enabled` | Enrollment of MFA TOTP factors is disabled. |
| `mfa_totp_verify_not_enabled` | Login via TOTP factors and verification of new TOTP factors is disabled. |
| `mfa_verification_failed` | MFA challenge could not be verified -- wrong TOTP code. |
| `mfa_verification_rejected` | Further MFA verification is rejected. Only returned if the [MFA verification attempt hook](https://supabase.com/docs/guides/auth/auth-hooks?language=add-admin-role#hook-mfa-verification-attempt) returns a reject decision. |
| `mfa_verified_factor_exists` | Verified phone factor already exists for a user. Unenroll existing verified phone factor to continue. |
| `mfa_totp_enroll_disabled` | Enrollment of MFA TOTP factors is disabled. |
| `mfa_totp_verify_disabled` | Login via TOTP factors and verification of new TOTP factors is disabled. |
| `mfa_phone_enroll_disabled` | Enrollment of MFA Phone factors is disabled. |
| `mfa_phone_verify_disabled` | Login via Phone factors and verification of new Phone factors is disabled. |
| `mfa_web_authn_enroll_not_enabled` | Enrollment of MFA Web Authn factors is disabled. |
| `mfa_web_authn_verify_not_enabled` | Login via WebAuthn factors and verification of new WebAuthn factors is disabled. |
| `no_authorization` | This HTTP request requires an `Authorization` header, which is not provided. |
| `not_admin` | User accessing the API is not admin, i.e. the JWT does not contain a `role` claim that identifies them as an admin of the Auth server. |
| `oauth_provider_not_supported` | Using an OAuth provider which is disabled on the Auth server. |
@@ -52,6 +55,8 @@ To supplement HTTP status codes, Supabase Auth returns a string error code which
| `provider_email_needs_verification` | Not all OAuth providers verify their user's email address. Supabase Auth requires emails to be verified, so this error is sent out when a verification email is sent after completing the OAuth flow. |
| `reauthentication_needed` | A user needs to reauthenticate to change their password. Ask the user to reauthenticate by calling the `supabase.auth.reauthenticate()` API. |
| `reauthentication_not_valid` | Verifying a reauthentication failed, the code is incorrect. Ask the user to enter a new code. |
| `refresh_token_not_found` | Session containing the refresh token not found. |
| `refresh_token_already_used` | Refresh token has been revoked and falls outside the refresh token reuse interval. See the [documentation on sessions](https://supabase.com/docs/guides/auth/sessions) for further information. |
| `request_timeout` | Processing the request took too long. Retry the request. |
| `same_password` | A user that is updating their password must use a different password than the one currently used. |
| `saml_assertion_no_email` | SAML assertion (user information) was received after sign in, but no email address was found in it, which is required. Check the provider's attribute mapping and/or configuration. |
@@ -63,6 +68,7 @@ To supplement HTTP status codes, Supabase Auth returns a string error code which
| `saml_provider_disabled` | Using [Enterprise SSO with SAML 2.0](https://supabase.com/docs/guides/auth/enterprise-sso/auth-sso-saml) is not enabled on the Auth server. |
| `saml_relay_state_expired` | SAML relay state is an object that tracks the progress of a `supabase.auth.signInWithSSO()` request. The SAML identity provider should respond after a fixed amount of time, after which this error is shown. Ask the user to sign in again. |
| `saml_relay_state_not_found` | SAML relay states are progressively cleaned up after they expire, which can cause this error. Ask the user to sign in again. |
| `session_expired` | Session to which the API request relates has expired. This can occur if an inactivity timeout is configured, or the session entry has exceeded the configured timebox value. See the [documentation on sessions](https://supabase.com/docs/guides/auth/sessions) for more information. |
| `session_not_found` | Session to which the API request relates no longer exists. This can occur if the user has signed out, or the session entry in the database was deleted in some other way. |
| `signup_disabled` | Sign ups (new account creation) are disabled on the server. |
| `single_identity_not_deletable` | Every user must have at least one identity attached to it, so deleting (unlinking) an identity is not allowed if it's the only one for the user. |
@@ -1,4 +1,4 @@
import { Heart, Server } from 'lucide-react'
import { Clock, Heart, Server, SquareStack } from 'lucide-react'
import {
IconBranching,
@@ -93,6 +93,10 @@ function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, c
return <Heart width={width} height={height} className={className} />
case 'deployment':
return <Server width={width} height={height} className={className} />
case 'cron':
return <Clock width={width} height={height} className={className} />
case 'queues':
return <SquareStack width={width} height={height} className={className} />
default:
return <IconMenuPlatform width={width} height={height} className={className} />
}
@@ -1,4 +1,4 @@
import { products } from 'shared-data'
import { PRODUCT_MODULES, products } from 'shared-data'
type HomeMenuIcon = {
width?: number
@@ -450,7 +450,7 @@ export function IconMenuAI({ width = 16, height = 16, className }: HomeMenuIcon)
xmlns="http://www.w3.org/2000/svg"
>
<path
d={products.vector.icon[16]}
d={PRODUCT_MODULES.vector.icon[16]}
stroke="currentColor"
strokeMiterlimit="10"
strokeLinecap="round"
@@ -48,12 +48,27 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
href: '/guides/realtime',
level: 'realtime',
},
],
[
{ label: 'Postgres Modules' },
{
label: 'AI & Vectors',
icon: 'ai',
href: '/guides/ai',
level: 'ai',
},
{
label: 'Cron',
icon: 'cron',
href: '/guides/cron',
level: 'cron',
},
{
label: 'Queues',
icon: 'queues',
href: '/guides/queues',
level: 'queues',
},
],
],
},
@@ -69,6 +84,12 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
href: '/guides/local-development',
level: 'local_development',
},
{
label: 'Deployment',
icon: 'deployment',
href: '/guides/deployment',
level: 'deployment',
},
{
label: 'Self-Hosting',
icon: 'self-hosting',
@@ -82,12 +103,6 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
href: '/guides/integrations',
level: 'integrations',
},
{
label: 'Deployment',
icon: 'deployment',
href: '/guides/deployment',
level: 'deployment',
},
],
],
},
@@ -349,6 +364,16 @@ export const gettingstarted: NavMenuConstant = {
},
],
},
{
name: 'AI Prompts',
url: undefined,
items: [
{
name: 'Overview',
url: '/guides/getting-started/ai-prompts',
},
],
},
],
}
@@ -701,6 +726,50 @@ export const auth = {
],
}
const ormQuickstarts: NavMenuSection = {
name: 'ORM Quickstarts',
url: undefined,
items: [
{
name: 'Prisma',
url: '/guides/database/prisma',
items: [
{
name: 'Prisma troubleshooting',
url: '/guides/database/prisma/prisma-troubleshooting',
},
],
},
{
name: 'Drizzle',
url: '/guides/database/drizzle',
},
{
name: 'Postgres.js',
url: '/guides/database/postgres-js',
},
],
}
const guiQuickstarts: NavMenuSection = {
name: 'GUI quickstarts',
url: undefined,
items: [
{
name: 'pgAdmin',
url: '/guides/database/pgadmin',
},
{
name: 'PSQL',
url: '/guides/database/psql',
},
{
name: 'DBeaver',
url: '/guides/database/dbeaver',
},
],
}
export const database: NavMenuConstant = {
icon: 'database',
title: 'Database',
@@ -714,12 +783,6 @@ export const database: NavMenuConstant = {
{
name: 'Connecting to your database',
url: '/guides/database/connecting-to-postgres',
items: [
{
name: 'Serverless Drivers',
url: '/guides/database/connecting-to-postgres/serverless-drivers',
},
],
},
{ name: 'Importing data', url: '/guides/database/import-data' },
{ name: 'Securing your data', url: '/guides/database/secure-data' },
@@ -780,6 +843,16 @@ export const database: NavMenuConstant = {
},
],
},
{
name: 'OrioleDB',
url: undefined,
items: [
{
name: 'Overview',
url: '/guides/database/orioledb',
},
],
},
{
name: 'Access and security',
url: undefined,
@@ -864,48 +937,8 @@ export const database: NavMenuConstant = {
},
],
},
{
name: 'ORM Quickstarts',
url: undefined,
items: [
{
name: 'Prisma',
url: '/guides/database/prisma',
items: [
{
name: 'Prisma troubleshooting',
url: '/guides/database/prisma/prisma-troubleshooting',
},
],
},
{
name: 'Drizzle',
url: '/guides/database/drizzle',
},
{
name: 'Postgres.js',
url: '/guides/database/postgres-js',
},
],
},
{
name: 'GUI quickstarts',
url: undefined,
items: [
{
name: 'pgAdmin',
url: '/guides/database/pgadmin',
},
{
name: 'PSQL',
url: '/guides/database/psql',
},
{
name: 'DBeaver',
url: '/guides/database/dbeaver',
},
],
},
ormQuickstarts,
guiQuickstarts,
{
name: 'Extensions',
url: undefined,
@@ -944,7 +977,7 @@ export const database: NavMenuConstant = {
url: '/guides/database/extensions/pgrouting',
},
{
name: 'pg_cron: Job Scheduling',
name: 'pg_cron: Schedule Recurring Jobs',
url: '/guides/database/extensions/pg_cron',
},
{
@@ -1096,6 +1129,42 @@ export const database: NavMenuConstant = {
],
}
export const cron: NavMenuConstant = {
icon: 'cron',
title: 'Cron',
url: '/guides/cron',
items: [
{ name: 'Overview', url: '/guides/cron' },
{
name: 'Getting Started',
url: undefined,
items: [
{ name: 'Install', url: '/guides/cron/install' },
{ name: 'Quickstart', url: '/guides/cron/quickstart' },
],
},
],
}
export const queues: NavMenuConstant = {
icon: 'queues',
title: 'Queues',
url: '/guides/queues',
items: [
{ name: 'Overview', url: '/guides/queues' },
{
name: 'Getting Started',
url: undefined,
items: [{ name: 'Quickstart', url: '/guides/queues/quickstart' }],
},
{
name: 'References',
url: undefined,
items: [{ name: 'API', url: '/guides/queues/api' }],
},
],
}
export const api: NavMenuConstant = {
icon: 'rest',
title: 'REST API',
@@ -1254,6 +1323,14 @@ export const functions: NavMenuConstant = {
name: 'Background Tasks',
url: '/guides/functions/background-tasks',
},
{
name: 'Ephemeral Storage',
url: '/guides/functions/ephemeral-storage',
},
{
name: 'WebSockets',
url: '/guides/functions/websockets',
},
{
name: 'Running AI Models',
url: '/guides/functions/ai-models',
@@ -2124,6 +2201,7 @@ export const deployment: NavMenuConstant = {
name: 'Environments',
items: [
{ name: 'Managing environments', url: '/guides/deployment/managing-environments' },
{ name: 'Database migrations', url: '/guides/deployment/database-migrations' },
{ name: 'Branching', url: '/guides/deployment/branching' },
],
},
@@ -2540,4 +2618,6 @@ export const navDataForMdx = {
nativeMobileLoginItems: NativeMobileLoginItems,
phoneLoginsItems: PhoneLoginsItems,
socialLoginItems: SocialLoginItems,
ormQuickstarts,
guiQuickstarts,
}
@@ -15,6 +15,8 @@ enum MenuId {
Realtime = 'realtime',
Storage = 'storage',
Ai = 'ai',
Cron = 'cron',
Queues = 'queues',
Platform = 'platform',
Deployment = 'deployment',
MonitoringTroubleshooting = 'monitoring_troubleshooting',
@@ -77,6 +79,10 @@ const menus: Menu[] = [
id: MenuId.Graphql,
type: 'guide',
},
{
id: MenuId.Queues,
type: 'guide',
},
{
id: MenuId.Auth,
type: 'guide',
@@ -101,6 +107,7 @@ const menus: Menu[] = [
id: MenuId.Ai,
type: 'guide',
},
{ id: MenuId.Cron, type: 'guide' },
{
id: MenuId.Platform,
type: 'guide',
@@ -256,7 +263,7 @@ const NavigationMenu = ({
additionalNavItems,
}: {
menuId: MenuId
additionalNavItems?: Partial<NavMenuSection>[]
additionalNavItems?: Record<string, Partial<NavMenuSection>[]>
}) => {
const level = menuId
const menu = getMenuById(level)
@@ -109,8 +109,8 @@ export const getMenuId = (pathname: string | null) => {
return MenuId.Api
case pathname.startsWith('auth'):
return MenuId.Auth
case pathname.startsWith('local-development'):
return MenuId.LocalDevelopment
case pathname.startsWith('cron'):
return MenuId.Cron
case pathname.startsWith('database'):
return MenuId.Database
case pathname.startsWith('deployment'):
@@ -123,10 +123,14 @@ export const getMenuId = (pathname: string | null) => {
return MenuId.Graphql
case pathname.startsWith('integrations'):
return MenuId.Integrations
case pathname.startsWith('local-development'):
return MenuId.LocalDevelopment
case pathname.startsWith('monitoring-troubleshooting'):
return MenuId.MonitoringTroubleshooting
case pathname.startsWith('platform'):
return MenuId.Platform
case pathname.startsWith('queues'):
return MenuId.Queues
case pathname.startsWith('realtime'):
return MenuId.Realtime
case pathname.startsWith('resources'):
@@ -6,20 +6,23 @@ import { type NavMenuSection } from '../Navigation.types'
import * as NavItems from './NavigationMenu.constants'
import NavigationMenuGuideListItems from './NavigationMenuGuideListItems'
import { usePathname } from 'next/navigation'
import { PropsWithChildren } from 'react'
import { MenuId } from './NavigationMenu'
const NavigationMenuGuideList = ({
id,
additionalNavItems,
}: {
id: string
additionalNavItems?: Partial<NavMenuSection>[]
additionalNavItems?: Record<string, Partial<NavMenuSection>[]>
}) => {
const pathname = usePathname()
const firstLevelRoute = pathname?.split('/')?.slice(0, 4)?.join('/')
// eslint-disable-next-line import/namespace -- dynamic access, can't lint properly
let menu = NavItems[id]
if (id === 'integrations' && additionalNavItems) {
if (id === MenuId.Integrations && additionalNavItems?.integrations) {
const integrationsListIndex = menu.items.findIndex((item) => item.name === 'Integrations')
if (integrationsListIndex !== -1) {
menu = {
@@ -28,7 +31,7 @@ const NavigationMenuGuideList = ({
...menu.items.slice(0, integrationsListIndex),
{
...menu.items[integrationsListIndex],
items: [...menu.items[integrationsListIndex].items, ...additionalNavItems],
items: [...menu.items[integrationsListIndex].items, ...additionalNavItems.integrations],
},
...menu.items.slice(integrationsListIndex + 1),
],
@@ -36,6 +39,38 @@ const NavigationMenuGuideList = ({
}
}
if (id === MenuId.GettingStarted && additionalNavItems?.prompts) {
const promptsSectionIndex = menu.items.findIndex((item) => item.name === 'AI Prompts')
if (promptsSectionIndex !== -1) {
menu = {
...menu,
items: [
...menu.items.slice(0, promptsSectionIndex),
{
...menu.items[promptsSectionIndex],
items: [...menu.items[promptsSectionIndex].items, ...additionalNavItems.prompts],
},
...menu.items.slice(promptsSectionIndex + 1),
],
}
}
}
return (
<NavigationMenuGuideListWrapper id={id} firstLevelRoute={firstLevelRoute}>
<NavigationMenuGuideListItems menu={menu} id={id} />
</NavigationMenuGuideListWrapper>
)
}
export function NavigationMenuGuideListWrapper({
id,
firstLevelRoute,
children,
}: PropsWithChildren<{
id: string
firstLevelRoute?: string
}>) {
return (
<Accordion.Root
collapsible={true}
@@ -44,7 +79,7 @@ const NavigationMenuGuideList = ({
value={firstLevelRoute}
className="transition-all duration-150 ease-out opacity-100 ml-0 delay-150"
>
<NavigationMenuGuideListItems menu={menu} id={id} />
{children}
</Accordion.Root>
)
}
@@ -28,7 +28,7 @@ Then initialize a new project:
poetry new aws_bedrock_image_search
```
## Spin up a Postgres Database with pgvector
## Spin up a Postgres database with pgvector
If you haven't already, head over to [database.new](https://database.new) and create a new project. Every Supabase project comes with a full Postgres database and the [pgvector extension](/docs/guides/database/extensions/pgvector) preconfigured.
@@ -10,7 +10,7 @@ tocVideo: 'A3uND5sgiO0'
This guide will walk you through an example using Amazon Bedrock SDK with `vecs`. We will create embeddings using the Amazon Titan Embeddings G1 – Text v1.2 (amazon.titan-embed-text-v1) model, insert these embeddings into a PostgreSQL database using vecs, and then query the collection to find the most similar sentences to a given query sentence.
## Create an Environment
## Create an environment
First, you need to set up your environment. You will need Python 3.7+ with the `vecs` and `boto3` libraries installed.
@@ -25,7 +25,7 @@ You'll also need:
- [Credentials to your AWS account](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html)
- [A Postgres Database with the pgvector extension](hosting.md)
## Create Embeddings
## Create embeddings
Next, we will use Amazon’s Titan Embedding G1 - Text v1.2 model to create embeddings for a set of sentences.
@@ -67,7 +67,7 @@ for sentence in dataset:
```
### Store the Embeddings with vecs
### Store the embeddings with vecs
Now that we have our embeddings, we can insert them into a PostgreSQL database using vecs.
@@ -90,7 +90,7 @@ sentences.upsert(records=embeddings)
sentences.create_index()
```
### Querying for Most Similar Sentences
### Querying for most similar sentences
Now, we query the `sentences` collection to find the most similar sentences to a sample query sentence. First need to create an embedding for the query sentence. Next, we query the collection we created earlier to find the most similar sentences.
@@ -24,7 +24,7 @@ values
('79409372-7556-4ccc-ab8f-5786a6cfa4f7', array[0.1, 0.2, 0.3], 'Hello world', '/hello-world');
```
Notice that we've associated two pieces of metadata, `content` and `url`, with the embedding. Those fields can be filtered, constrained, indexed, and generally operated on using the full power of SQL. Structured metadata fits naturally with a traditional Supabase application, and can be managed via database [migrations](/docs/guides/getting-started/local-development#database-migrations).
Notice that we've associated two pieces of metadata, `content` and `url`, with the embedding. Those fields can be filtered, constrained, indexed, and generally operated on using the full power of SQL. Structured metadata fits naturally with a traditional Supabase application, and can be managed via database [migrations](/docs/guides/deployment/database-migrations).
## Unstructured
@@ -37,13 +37,13 @@ npx supabase init
Generate types for your project to produce the `database.types.ts` file:
```bash
npx supabase gen types --lang=typescript --project-id "$PROJECT_REF" --schema public > database.types.ts
npx supabase gen types typescript --project-id "$PROJECT_REF" --schema public > database.types.ts
```
or in case of local development:
```bash
npx supabase gen types --lang=typescript --local > database.types.ts
npx supabase gen types typescript --local > database.types.ts
```
These types are generated from your database schema. Given a table `public.movies`, the generated types will look like:
+1 -1
View File
@@ -30,7 +30,7 @@ const { data, error } = await supabase
.limit(20)
```
## Select statement with complex Boolean Logic clause
## Select statement with complex Boolean logic clause
Select all columns from a single table with a complex where clause: OR AND OR
@@ -102,7 +102,7 @@ response = supabase.auth.sign_in_anonymously()
Converting an anonymous user to a permanent user requires [linking an identity](/docs/guides/auth/auth-identity-linking#manual-linking-beta) to the user. This requires you to [enable manual linking](/dashboard/project/_/settings/auth) in your Supabase project.
### Link an Email / Phone identity
### Link an email / phone identity
<Tabs
scrollable
@@ -148,10 +148,10 @@ try await supabase.auth.updateUser(
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
You can use the [`modifyUser()`](/docs/reference/kotlin/auth-updateuser) method to link an email or phone identity to the anonymous user.
You can use the [`updateUser()`](/docs/reference/kotlin/auth-updateuser) method to link an email or phone identity to the anonymous user.
```kotlin
supabase.auth.modifyUser {
supabase.auth.updateUser {
email = "example@email.com"
}
```
@@ -1,13 +1,13 @@
---
id: 'auth-captcha'
title: 'Enable Captcha Protection'
description: 'Add Captcha Protection to your Supabase project'
title: 'Enable CAPTCHA Protection'
description: 'Add CAPTCHA Protection to your Supabase project'
tocVideo: 'em1cpOAXknM'
---
Supabase provides you with the option of adding captcha to your sign-in, sign-up, and password reset forms. This keeps your website safe from bots and malicious scripts. Supabase authentication has support for [hCaptcha](https://www.hcaptcha.com/) and [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/).
Supabase provides you with the option of adding CAPTCHA to your sign-in, sign-up, and password reset forms. This keeps your website safe from bots and malicious scripts. Supabase authentication has support for [hCaptcha](https://www.hcaptcha.com/) and [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/).
## Sign up for Captcha
## Sign up for CAPTCHA
<Tabs
scrollable
@@ -39,13 +39,13 @@ In the Settings page, look for the **Sitekey** section and copy the key.
</TabPanel>
</Tabs>
## Enable Captcha protection for your Supabase project
## Enable CAPTCHA protection for your Supabase project
Navigate to the **[Auth](https://supabase.com/dashboard/project/_/settings/auth)** section of your Project Settings in the Supabase Dashboard and find the **Enable Captcha protection** toggle under Settings > Authentication > Bot and Abuse Protection > Enable Captcha protection.
Select your CAPTCHA provider from the dropdown, enter your Captcha **Secret key**, and click **Save**.
## Add the Captcha frontend component
## Add the CAPTCHA frontend component
The frontend requires some changes to provide the captcha on-screen for the user. This example uses React and the corresponding Captcha React component, but both Captcha providers can be used with any JavaScript framework.
@@ -146,7 +146,7 @@ const { error } = await supabase.auth.verifyOtp({ token_hash, type })
Email one-time passwords (OTP) are a form of passwordless login where users key in a six digit code sent to their email address to log in to their accounts.
### Enabling Email OTP
### Enabling email OTP
Email authentication methods, including Email OTPs, are enabled by default.
@@ -162,7 +162,7 @@ Modify the template to include the `{{ .Token }}` variable, for example:
By default, a user can only request an OTP once every <SharedData data="config">auth.rate_limits.otp.period</SharedData> and they expire after <SharedData data="config">auth.rate_limits.otp.validity</SharedData>. This is configurable via `Auth > Providers > Email > Email OTP Expiration`. An expiry duration of more than 86400 seconds (one day) is disallowed to guard against brute force attacks. The longer an OTP remains valid, the more time an attacker has to attempt brute force attacks. If the OTP is valid for several days, an attacker might have more opportunities to guess the correct OTP through repeated attempts.
### Signing in with Email OTP
### Signing in with email OTP
#### Step 1: Send the user an OTP code
@@ -29,7 +29,7 @@ The templating system provides the following variables for use:
On hosted Supabase projects, edit your email templates on the [Email Templates](/dashboard/project/_/auth/templates) page. On self-hosted projects or in local development, edit your [configuration files](/docs/guides/local-development/customizing-email-templates).
## Mobile Deep Linking
## Mobile deep linking
For mobile applications, you might need to link or redirect to a specific page within your app. See the [Mobile Deep Linking guide](/docs/guides/auth/native-mobile-deep-linking) to set this up.
@@ -478,7 +478,7 @@ Setting `showLinks` to `false` will hide the following links:
- Send a magic link email
- Forgot your password?
### Sign in and Sign up views
### Sign in and sign up views
Add `sign_in` or `sign_up` views with the `view` prop:
+2 -2
View File
@@ -46,7 +46,7 @@ A HTTP Hook is an endpoint which takes in a JSON event payload and returns a JSO
</TabPanel>
</Tabs>
## Security Model
## Security model
Sign the payload and grant permissions selectively in order to guard the integrity of the payload.
@@ -261,7 +261,7 @@ Note that payloads are sent uncompressed in order to accurately track Content Le
In the dashboard, navigate to [`Authentication > Hooks`](/dashboard/project/_/auth/hooks) and select the appropriate function type (SQL or HTTP) from the dropdown menu.
### Error Handling
### Error handling
You should return an error when facing a runtime error. Runtime errors are specific to your application and arise from specific business rules rather than programmer errors.
@@ -6,7 +6,7 @@ subtitle: 'Use a custom email provider to send authentication messages'
The Send Email Hook runs before an email is sent and allows for flexibility around email sending. You can use this hook to configure a back-up email provider or add internationalization to your emails.
## Email Sending Behavior
## Email sending behavior
Email sending depends on two settings: Email Provider and Auth Hook status.
@@ -306,7 +306,7 @@ function AuthMFA() {
the background automatically and finally call the `onSuccess` callback, which
will show the authenticated `App` component on screen.
### Security Configuration
### 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).
@@ -5,11 +5,11 @@ description: 'Supabase Auth Error Codes'
subtitle: 'Learn about the Auth error codes and how to resolve them'
---
## Auth Error Codes
## 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
## Error types
Supabase Auth errors are generally categorized into two main types:
@@ -59,25 +59,26 @@ Errors originating from the server API classed as `AuthApiError` always have a `
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
All errors originating from the `supabase.auth` namespace of the JavaScript client library will be wrapped by the `AuthError` class.
All exceptions originating from the `supabase.auth` namespace of the Kotlin client library will be a subclass of `RestException`.
Error objects are split in a few classes:
Rest exceptions are split into 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.
- `AuthRestException` -- exceptions which originate from the Supabase Auth API and have a `errorCode` property that can be used to identify the error returned by the server.
- `AuthWeakPasswordException` -- an `AuthRestException` which indicates that the password is too weak.
- `AuthSessionMissingException` -- an `AuthRestException` which indicates that the session is missing, if the user was logged out or deleted.
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 instances and subclasses of a `AuthRestException` have a `errorCode` property that can be used to identify the error returned by the server.
</TabPanel>
</Tabs>
## HTTP Status Codes
## HTTP status codes
Below are the most common HTTP status codes you might encounter, along with their meanings in the context of Supabase Auth:
{/* supa-mdx-lint-disable Rule001HeadingCase */}
### [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.
@@ -98,13 +99,15 @@ Indicate that the Auth server's service is degraded. Most often it points to iss
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.
{/* supa-mdx-lint-enable Rule001HeadingCase */}
## Auth Error Codes Table
## Auth error codes table
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.
<AuthErrorCodesTable />
## Best Practices for Error Handling
## 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.
@@ -11,7 +11,7 @@ Many Auth methods involve a redirect to your app. For example:
With Deep Linking, you can configure this redirect to open a specific page. This is necessary if, for example, you need to display a form for password reset, or to manually exchange a token hash.
## Setting up Deep Linking
## Setting up deep linking
<Tabs
scrollable
+10 -1
View File
@@ -15,7 +15,7 @@ Phone OTP login can:
<CostWarning />
## Enabling Phone Login
## Enabling phone login
Enable phone authentication on the [Auth Providers page](/dashboard/project/_/auth/providers) for hosted Supabase projects.
@@ -215,6 +215,15 @@ try await supabase.auth.updateUser(
)
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.auth.updateUser {
phone = "123456789"
}
```
</TabPanel>
<TabPanel id="python" label="Python">
@@ -100,7 +100,7 @@ For mobile applications you can use deep linking URIs. For example, for your `SI
Read more about deep linking and find code examples for different frameworks [here](/docs/guides/auth/native-mobile-deep-linking).
## Error Handling
## Error handling
When authentication fails, the user will still be redirected to the redirect URL provided. However, the error details will be returned as query fragments in the URL. You can parse these query fragments and show a custom error message to the user. For example:
@@ -38,6 +38,8 @@ To improve experience for your users, we recommend redirecting users to one spec
This is not necessary. Both the access token and refresh token are designed to be passed around to different components in your application. The browser-based side of your application needs access to the refresh token to properly maintain a browser session anyway.
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
### My server is getting invalid refresh token errors. What's going on?
It is likely that the refresh token sent from the browser to your server is stale. Make sure the `onAuthStateChange` listener callback is free of bugs and is registered relatively early in your application's lifetime
@@ -276,24 +276,24 @@ Set up a listener for Auth events on the client, to handle session refreshes and
```svelte src/routes/+layout.svelte
<script>
import { invalidate } from '$app/navigation';
import { onMount } from 'svelte';
import { invalidate } from '$app/navigation'
import { onMount } from 'svelte'
export let data;
$: ({ session, supabase } = data);
let { data, children } = $props()
let { session, supabase } = $derived(data)
onMount(() => {
const { data } = supabase.auth.onAuthStateChange((_, newSession) => {
if (newSession?.expires_at !== session?.expires_at) {
invalidate('supabase:auth');
}
});
onMount(() => {
const { data } = supabase.auth.onAuthStateChange((_, newSession) => {
if (newSession?.expires_at !== session?.expires_at) {
invalidate('supabase:auth')
}
})
return () => data.subscription.unsubscribe();
});
return () => data.subscription.unsubscribe()
})
</script>
<slot />
{@render children()}
```
</StepHikeCompact.Code>
@@ -327,15 +327,15 @@ export const load: PageServerLoad = async ({ locals: { supabase } }) => {
```svelte src/routes/+page.svelte
<script>
export let data;
$: ({ countries } = data);
let { data } = $props()
let { countries } = $derived(data)
</script>
<h1>Welcome to Supabase!</h1>
<ul>
{#each countries as country}
<li>{country.name}</li>
{/each}
{#each countries as country}
<li>{country.name}</li>
{/each}
</ul>
```
@@ -408,26 +408,31 @@ export const actions: Actions = {
```svelte src/routes/auth/+page.svelte
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email" />
</label>
<label>
Password
<input name="password" type="password" />
</label>
<button>Login</button>
<button formaction="?/signup">Sign up</button>
<label>
Email
<input name="email" type="email" />
</label>
<label>
Password
<input name="password" type="password" />
</label>
<button>Login</button>
<button formaction="?/signup">Sign up</button>
</form>
```
```svelte src/routes/auth/+layout.svelte
<script>
let { children } = $props()
</script>
<header>
<nav>
<a href="/">Home</a>
</nav>
<nav>
<a href="/">Home</a>
</nav>
</header>
<slot />
{@render children()}
```
```svelte src/routes/auth/error/+page.svelte
@@ -512,25 +517,25 @@ To ensure that `hooks.server.ts` runs for every nested path, put a `+layout.serv
```svelte src/routes/private/+layout.svelte
<script>
export let data;
$: ({ supabase } = data);
let { data, children } = $props()
let { supabase } = $derived(data)
$: logout = async () => {
const { error } = await supabase.auth.signOut();
if (error) {
console.error(error);
}
};
const logout = async () => {
const { error } = await supabase.auth.signOut()
if (error) {
console.error(error)
}
}
</script>
<header>
<nav>
<a href="/">Home</a>
</nav>
<button on:click={logout}>Logout</button>
<nav>
<a href="/">Home</a>
</nav>
<button onclick={logout}>Logout</button>
</header>
<main>
<slot />
{@render children()}
</main>
```
@@ -561,55 +566,54 @@ using ((select auth.uid()) = user_id);
```
```svelte src/routes/private/+page.server.ts
import type { PageServerLoad } from './$types';
import type { PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ depends, locals: { supabase } }) => {
depends('supabase:db:notes');
const { data: notes } = await supabase.from('notes').select('id,note').order('id');
return { notes: notes ?? [] };
};
depends('supabase:db:notes')
const { data: notes } = await supabase.from('notes').select('id,note').order('id')
return { notes: notes ?? [] }
}
```
```svelte src/routes/private/+page.svelte
<script lang="ts">
import { invalidate } from '$app/navigation';
import type { EventHandler } from 'svelte/elements';
import { invalidate } from '$app/navigation'
import type { EventHandler } from 'svelte/elements'
import type { PageData } from './$types';
import type { PageData } from './$types'
export let data: PageData;
$: ({ notes, supabase, user } = data);
let { data } = $props()
let { notes, supabase, user } = $derived(data)
let handleSubmit: EventHandler<SubmitEvent, HTMLFormElement>;
$: handleSubmit = async (evt) => {
evt.preventDefault();
if (!evt.target) return;
const handleSubmit: EventHandler<SubmitEvent, HTMLFormElement> = async (evt) => {
evt.preventDefault()
if (!evt.target) return
const form = evt.target as HTMLFormElement;
const form = evt.target as HTMLFormElement
const note = (new FormData(form).get('note') ?? '') as string;
if (!note) return;
const note = (new FormData(form).get('note') ?? '') as string
if (!note) return
const { error } = await supabase.from('notes').insert({ note });
if (error) console.error(error);
const { error } = await supabase.from('notes').insert({ note })
if (error) console.error(error)
invalidate('supabase:db:notes');
form.reset();
};
invalidate('supabase:db:notes')
form.reset()
}
</script>
<h1>Private page for user: {user?.email}</h1>
<h2>Notes</h2>
<ul>
{#each notes as note}
<li>{note.note}</li>
{/each}
{#each notes as note}
<li>{note.note}</li>
{/each}
</ul>
<form on:submit={handleSubmit}>
<label>
Add a note
<input name="note" type="text" />
</label>
<form onsubmit={handleSubmit}>
<label>
Add a note
<input name="note" type="text" />
</label>
</form>
```
+1 -1
View File
@@ -119,7 +119,7 @@ When a user signs out, the sessions affected by the logout are removed from the
Note that sessions are not proactively terminated when their maximum lifetime (time-box) or inactivity timeout are reached. These sessions are cleaned up progressively 24 hours after reaching that status. This allows you to tweak the values or roll back changes without causing unintended user friction.
### Can I use HTTP-Only cookies to store the access and refresh tokens?
### Can I use HTTP-only cookies to store the access and refresh tokens?
This is possible, but only for apps that use the traditional server-only web app approach where all of the application logic is implemented on the server and it returns rendered HTML only.
+1 -1
View File
@@ -140,7 +140,7 @@ user_pool_id = "<id>"
user_pool_region = "<region>"
```
## Use a Pre-Token Generation Trigger to assign the authenticated role
## Use a pre-token generation trigger to assign the authenticated role
Your Supabase project inspects the `role` claim present in all JWTs sent to it, to assign the correct Postgres role when using the Data API, Storage or Realtime authorization.
+34
View File
@@ -0,0 +1,34 @@
---
title: 'Cron'
subtitle: 'Schedule Recurring Jobs with Cron Syntax in Postgres'
---
Supabase Cron is a Postgres Module that simplifies scheduling recurring Jobs with cron syntax and monitoring Job runs inside Postgres.
Cron Jobs can be created via SQL or the Cron interface inside of Supabase Dashboard and can run anywhere from every second to once a year depending on your use case.
Every Job can run SQL snippets or database functions with zero network latency or make an HTTP request, such as invoking a Supabase Edge Function, with ease.
<Admonition type="note">
For best performance, we recommend no more than 8 Jobs run concurrently. Each Job should run no more than 10 minutes.
</Admonition>
## How does Cron work?
Under the hood, Supabase Cron uses the [`pg_cron`](https://github.com/citusdata/pg_cron) Postgres database extension which is the scheduling and execution engine for your Jobs.
<Admonition type="note">
`pg_cron` is not fully supported on Fly Postgres. Learn more about [Fly Postgres limitations](/docs/guides/platform/fly-postgres#limitations).
</Admonition>
The extension creates a `cron` schema in your database and all Jobs are stored on the `cron.job` table. Every Job's run and its status is recorded on the `cron.job_run_details` table.
The Supabase Dashboard provides and interface for you to schedule Jobs and monitor Job runs. You can also do the same with SQL.
## Resources
- [`pg_cron` GitHub Repository](https://github.com/citusdata/pg_cron)
+44
View File
@@ -0,0 +1,44 @@
---
title: 'Install'
---
Install the Supabase Cron Postgres Module to begin scheduling recurring Jobs.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
queryGroup="database-method"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Cron Postgres Module](/dashboard/project/_/integrations/cron/overview) under Integrations in the Dashboard.
2. Enable the `pg_cron` extension.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
create extension pg_cron with schema pg_catalog;
grant usage on schema cron to postgres;
grant all privileges on all tables in schema cron to postgres;
```
</TabPanel>
</Tabs>
## Uninstall
Uninstall Supabase Cron by disabling the `pg_cron` extension:
```sql
drop extension if exists pg_cron;
```
<Admonition type="warning">
Disabling the `pg_cron` extension will permanently delete all Jobs.
</Admonition>
@@ -0,0 +1,337 @@
---
title: 'Quickstart'
---
<Admonition type="note">
Job names are case sensitive and cannot be edited once created.
Attempting to create a second Job with the same name (and case) will overwrite the first Job.
</Admonition>
## Schedule a Job
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard-schedule-job"
queryGroup="database-method"
>
<TabPanel id="dashboard-schedule-job" label="Dashboard">
1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section to schedule your first Job.
2. Click on `Create job` button or navigate to the new Cron Job form [here](/dashboard/project/_/integrations/cron/jobs?dialog-shown=true).
3. Name your Cron Job.
4. Choose a schedule for your Job by inputting cron syntax (refer to the syntax chart in the form) or natural language.
5. Input SQL snippet or select a Database function, HTTP request, or Supabase Edge Function.
<Image
alt="Cron Create"
src="/docs/img/guides/database/cron/cron-create.png"
className="max-w-[700px] !mx-auto border rounded-md"
zoomable
/>
</TabPanel>
<TabPanel id="sql-schedule-job" label="SQL">
```sql
-- Cron Job name cannot be edited
select cron.schedule('permanent-cron-job-name', '30 seconds', 'CALL do_something()');
```
</TabPanel>
</Tabs>
<Accordion
type="default"
openBehaviour="multiple"
chevronAlign="right"
justified
size="medium"
className="text-foreground-light mt-8 mb-6"
>
<div className="border-b mt-3 pb-3">
<AccordionItem
header="Cron syntax"
id="item-1"
>
```
┌───────────── min (0 - 59)
│ ┌────────────── hour (0 - 23)
│ │ ┌─────────────── day of month (1 - 31)
│ │ │ ┌──────────────── month (1 - 12)
│ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to
│ │ │ │ │ Saturday, or use names; 7 is also Sunday)
│ │ │ │ │
│ │ │ │ │
* * * * *
```
You can use [1-59] seconds (e.g. `30 seconds`) as the cron syntax to schedule sub-minute Jobs.
</AccordionItem>
</div>
</Accordion>
<Admonition type="note">
You can input seconds for your Job schedule interval as long as you're on Postgres version 15.1.1.61 or later.
</Admonition>
## Edit a Job
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard-edit-job"
queryGroup="database-method"
>
<TabPanel id="dashboard-edit-job" label="Dashboard">
1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section and find the Job you'd like to edit.
2. Click on the three vertical dots menu on the right side of the Job and click `Edit cron job`.
3. Make your changes and then click `Save cron job`.
<Image
alt="Cron Edit"
src="/docs/img/guides/database/cron/cron-edit.png"
className="max-w-[700px] !mx-auto border rounded-md"
zoomable
/>
</TabPanel>
<TabPanel id="sql-edit-job" label="SQL">
```sql
select cron.alter_job(
job_id := (select jobid from cron.job where jobname = 'permanent-cron-job-name'),
schedule := '*/5 * * * *'
);
```
Full options for the `cron.alter_job()` function are:
```sql
cron.alter_job(
job_id bigint,
schedule text default null,
command text default null,
database text default null,
username text default null,
active boolean default null
)
```
It is also possible to modify a job by using the `cron.schedule()` function by inputting the same job name. This will replace the existing job via upsert.
</TabPanel>
</Tabs>
## Activate/Deactivate a Job
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard-unschedule-job"
queryGroup="database-method"
>
<TabPanel id="dashboard-unschedule-job" label="Dashboard">
1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section and find the Job you'd like to unschedule.
2. Toggle the `Active`/`Inactive` switch next to Job name.
<Image
alt="Cron Toggle"
src="/docs/img/guides/database/cron/cron-toggle.png"
className="max-w-[700px] !mx-auto border rounded-md"
zoomable
/>
</TabPanel>
<TabPanel id="sql-unschedule-job" label="SQL">
```sql
-- Activate Job
select cron.alter_job(
job_id := (select jobid from cron.job where jobname = 'permanent-cron-job-name'),
active := true
);
-- Deactivate Job
select cron.alter_job(
job_id := (select jobid from cron.job where jobname = 'permanent-cron-job-name'),
active := false
);
```
</TabPanel>
</Tabs>
## Unschedule a Job
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard-delete-job"
queryGroup="database-method"
>
<TabPanel id="dashboard-delete-job" label="Dashboard">
1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section and find the Job you'd like to delete.
2. Click on the three vertical dots menu on the right side of the Job and click `Delete cron job`.
3. Confirm deletion by entering the Job name.
<Image
alt="Cron Unschedule"
src="/docs/img/guides/database/cron/cron-unschedule.png"
className="max-w-[700px] !mx-auto border rounded-md"
zoomable
/>
</TabPanel>
<TabPanel id="sql-delete-job" label="SQL">
```sql
select cron.unschedule('permanent-cron-job-name');
```
<Admonition type="warning">
Unscheduling a Job will permanently delete the Job from `cron.job` table but its run history remain in `cron.job_run_details` table.
</Admonition>
</TabPanel>
</Tabs>
## Inspecting Job Runs
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard-runs-job"
queryGroup="database-method"
>
<TabPanel id="dashboard-runs-job" label="Dashboard">
1. Go to the [Jobs](/dashboard/project/_/integrations/cron/jobs) section and find the Job you want to see the runs of.
2. Click on the `History` button next to the Job name.
<Image
alt="Cron Job Runs"
src="/docs/img/guides/database/cron/cron-history.png"
className="max-w-[700px] !mx-auto border rounded-md"
zoomable
/>
</TabPanel>
<TabPanel id="sql-runs-job" label="SQL">
```sql
select
*
from cron.job_run_details
where jobid = (select jobid from cron.job where jobname = 'permanent-cron-job-name')
order by start_time desc
limit 10;
```
<Admonition type="warning">
The records in the `cron.job_run_details` table are not cleaned up automatically. They are also not removed when jobs are unscheduled, which will take up disk space in your database.
</Admonition>
</TabPanel>
</Tabs>
## Examples
### Delete data every week
{/* <!-- vale off --> */}
Delete old data every Saturday at 3:30AM (GMT):
{/* <!-- vale on --> */}
```sql
select cron.schedule (
'saturday-cleanup', -- name of the cron job
'30 3 * * 6', -- Saturday at 3:30AM (GMT)
$$ delete from events where event_time < now() - interval '1 week' $$
);
```
### Run a vacuum every day
{/* <!-- vale off --> */}
Vacuum every day at 3:00AM (GMT):
{/* <!-- vale on --> */}
```sql
select cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM');
```
### Call a database function every 5 minutes
Create a [`hello_world()`](/docs/guides/database/functions?language=sql#simple-functions) database function and then call it every 5 minutes:
```sql
select cron.schedule('call-db-function', '*/5 * * * *', 'SELECT hello_world()');
```
### Call a database stored procedure
To use a stored procedure, you can call it like this:
```sql
select cron.schedule('call-db-procedure', '*/5 * * * *', 'CALL my_procedure()');
```
### Invoke Supabase Edge Function every 30 seconds
Make a POST request to a Supabase Edge Function every 30 seconds:
```sql
select
cron.schedule(
'invoke-function-every-half-minute',
'30 seconds',
$$
select
net.http_post(
url:='https://project-ref.supabase.co/functions/v1/function-name',
headers:=jsonb_build_object('Content-Type','application/json', 'Authorization', 'Bearer ' || 'YOUR_ANON_KEY'),
body:=jsonb_build_object('time', now() ),
timeout_milliseconds:=5000
) as request_id;
$$
);
```
<Admonition type="note">
This requires the [`pg_net` extension](/docs/guides/database/extensions/pg_net) to be enabled.
</Admonition>
## Caution: Scheduling System Maintenance
Be extremely careful when setting up Jobs for system maintenance tasks as they can have unintended consequences.
For instance, scheduling a command to terminate idle connections with `pg_terminate_backend(pid)` can disrupt critical background processes like nightly backups. Often, there is an existing Postgres setting, such as `idle_session_timeout`, that can perform these common maintenance tasks without the risk.
Reach out to [Supabase Support](https://supabase.com/support) if you're unsure if that applies to your use case.
@@ -1,89 +1,138 @@
---
id: 'connecting-to-postgres'
title: 'Connecting to your database'
description: 'Explore the options for connecting to your Postgres database.'
subtitle: 'Explore the options for connecting to your Postgres database.'
title: 'Connect to your database'
description: 'Connect to Postgres from your frontend, backend, or serverless environment'
subtitle: 'Connect to Postgres from your frontend, backend, or serverless environment'
---
Supabase provides several options for programmatically connecting to your Postgres database:
## Quick summary
1. The Data APIs
1. Using one of the many [Client Libraries](/docs/reference)
1. Direct connections using the built-in Postgres connection system
1. Connection pooling for scalable connections
How you connect to your database depends on where you're connecting from:
## Data APIs
- For frontend applications, use the [Data API](#data-apis-and-client-libraries)
- For Postgres clients, use a connection string
- For persistent clients (for example, long-running servers or database GUIs) use the [direct connection string](#direct-connection) if your environment supports IPv6, and [Supavisor session mode](#supavisor-session-mode) if not
- For temporary clients (for example, serverless or edge functions) use [Supavisor transaction mode](#supavisor-transaction-mode)
Supabase's Data APIs are the easiest way to get started if you are managing data (fetching, inserting, updating). We provide several types of API to suit your preferences:
## Quickstarts
- [REST](/docs/guides/api): interact with your database through a REST interface.
- [GraphQL](/docs/guides/graphql/api): interact with your database through a GraphQL interface.
- [Realtime](/docs/guides/realtime#realtime-api): listen to database changes over websockets.
<div className="grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] gap-6 not-prose">
<NavData data="ormQuickstarts">
{(data) =>
data.items?.map((quickstart) => (
<Link key={quickstart.url} href={quickstart.url} passHref>
<GlassPanel
key={quickstart.name}
title={quickstart.name}
className="[&>div]:p-2 flex justify-center [&_p]:text-foreground-light"
/>
</Link>
))
}
</NavData>
<NavData data="guiQuickstarts">
{(data) =>
data.items?.map((quickstart) => (
<Link key={quickstart.url} href={quickstart.url} passHref>
<GlassPanel
key={quickstart.name}
title={quickstart.name}
className="[&>div]:p-2 flex justify-center [&_p]:text-foreground-light"
/>
</Link>
))
}
</NavData>
</div>
## Client libraries
## Data APIs and client libraries
Supabase offers [client libraries](/docs/reference) for popular programming languages. These libraries provide a convenient wrapper around the Data APIs, making it easier to interact with your database using your preferred language.
The Data APIs allow you to interact with your database using REST or GraphQL requests. You can use these APIs to fetch and insert data from the frontend, as long as you have [RLS](/docs/guides/database/postgres/row-level-security) enabled.
## Connecting to external libraries and tools
- [REST](/docs/guides/api)
- [GraphQL](/docs/guides/graphql/api)
Beyond the [Supabase client-libraries](/docs/guides/api/rest/client-libs), Supabase provides three connection strings for direct database access, compatible with all postgres libraries, such as [Prisma](/docs/guides/database/prisma) and [Drizzle](/docs/guides/database/drizzle), as well as tools like [PSQL](/docs/guides/database/psql) and [pgAdmin](/docs/guides/database/pgadmin).
For convenience, you can also use the Supabase client libraries, which wrap the Data APIs with a developer-friendly interface and automatically handle authentication:
You can find these connection strings in your [Database Settings](https://supabase.com/dashboard/project/_/settings/database).
- [JavaScript](/docs/reference/javascript)
- [Flutter](/docs/reference/dart)
- [Swift](/docs/reference/swift)
- [Python](/docs/reference/python)
- [C#](/docs/reference/csharp)
- [Kotlin](/docs/reference/kotlin)
### Direct connections:
## Direct connection
It connects directly to your Postgres instance. It is ideal for persistent servers, such as virtual machines (VMs) and long-lasting containers. Examples include AWS EC2 machines, Fly.io VMs, and DigitalOcean Droplets.
The direct connection string connects directly to your Postgres instance. It is ideal for persistent servers, such as virtual machines (VMs) and long-lasting containers. Examples include AWS EC2 machines, Fly.io VMs, and DigitalOcean Droplets.
<Admonition type="caution">
Direct connections use IPv6 by default. If your environment doesn't support IPv6 or you're unsure but still want to use a direct connection, check out the [networking guide](/docs/guides/platform/ipv4-address#supabase-and-ipv6-compatibility) for more info.
Direct connections use IPv6 by default. If your environment doesn't support IPv6, use [Supavisor session mode](#supavisor-session-mode) or get the [IPv4 addon](/docs/guides/platform/ipv4-address).
</Admonition>
```md
// Example String
The connection string looks like this:
```
postgresql://postgres:[YOUR-PASSWORD]@db.apbkobhfnmcqqzqeeqss.supabase.co:5432/postgres
```
You can find the direct connection string in the [Database settings](https://supabase.com/dashboard/project/_/settings/database) inside the dashboard:
Get your project's direct string from the [Database Settings](/dashboard/project/_/settings/database) page:
1. Go to the `Settings` section.
2. Click `Database`.
3. Under `Connection string`, make sure `Use connection pooling` is unchecked. Copy the URI.
3. Under `Connection string`, make sure `Display connection pooler` is unchecked. Copy the URI.
### Supavisor session mode (port 5432):
## Supavisor session mode
In Session mode, Supavisor acts as an IPv4 proxy. It allows developers in IPv4-only environments to access a direct-connection experience without needing the IPv4 add-on.
The session mode connection string connects to your Postgres instance via a proxy. This is ideal for persistent servers when IPv4 is not supported.
```md
// Example String
The connection string looks like this:
```
postgres://postgres.apbkobhfnmcqqzqeeqss:[YOUR-PASSWORD]@aws-0-ca-central-1.pooler.supabase.com:5432/postgres
```
### Transaction mode (port 6543):
Get your project's session mode string from the [Database Settings](/dashboard/project/_/settings/database) page:
1. Go to the `Settings` section.
2. Click `Database`.
3. Under `Connection string`, make sure `Display connection pooler` is checked and `Session mode` is selected. Copy the URI.
## Supavisor transaction mode
The transaction mode connection string connects to your Postgres instance via a proxy which serves as a connection pooler. This is deal for serverless or edge functions, which require many transient connections.
<Admonition type="caution">
Transaction mode does not support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html). To avoid errors, [turn off prepared statements](https://github.com/orgs/supabase/discussions/28239) for your specific connection library.
Transaction mode does not support [prepared statements](https://postgresql.org/docs/current/sql-prepare.html). To avoid errors, [turn off prepared statements](https://github.com/orgs/supabase/discussions/28239) for your connection library.
</Admonition>
Supavisor in transaction mode forms hot connections with the database and only allows clients to access them when a query is pending. It has amazing scaling capacity, and is best used with temporary servers, such as serverless/Edge Functions or auto-scaling servers.
The connection string looks like this:
```md
// Example String
```
postgres://postgres.apbkobhfnmcqqzqeeqss:[YOUR-PASSWORD]@aws-0-ca-central-1.pooler.supabase.com:6543/postgres
```
## Connection pooling in-depth
Get your project's transaction mode string from the [Database Settings](/dashboard/project/_/settings/database) page:
### Application side poolers
1. Go to the `Settings` section.
2. Click `Database`.
3. Under `Connection string`, make sure `Display connection pooler` is checked and `Transaction mode` is selected. Copy the URI.
## More about connection pooling
Connection pooling improves database performance by reusing existing connections between queries. This reduces the overhead of establishing connections and improves scalability.
You can use an application-side pooler or a server-side pooler (Supabase automatically provides one called Supavisor), depending on whether your backend is persistent or serverless.
### Application-side poolers
Application-side poolers are built into connection libraries and API servers, such as Prisma, SQLAlchemy, and PostgREST. They maintain several active connections with Postgres or a server-side pooler, reducing the overhead of establishing connections between queries. When deploying to static architecture, such as long-standing containers or VMs, application-side poolers are satisfactory on their own.
### Serverside poolers
Postgres connections are like WebSocket, once established, they are preserved until the client (application server) disconnects. A server might only make a single 10 ms query, but needlessly reserve its database connection for seconds or longer.
Postgres connections are like a WebSocket. Once established, they are preserved until the client (application server) disconnects. A server might only make a single 10 ms query, but needlessly reserve its database connection for seconds or longer.
Serverside-poolers, such as Supabase's [Supavisor](https://github.com/supabase/supavisor) in transaction mode, sit between clients and the database and can be thought of as load balancers for Postgres connections.
@@ -108,34 +157,6 @@ You can obtain your connection info and Server root certificate from your applic
![Connection Info and Certificate.](/docs/img/database/database-settings-ssl.png)
## Choosing a connection method
- **Data APIs:** Best for operations supported by the Data APIs.
- **Supavisor Pooler:**
- **Transaction mode (port 6543):** Best for serverless/edge functions and rapidly auto-scaling servers
- **Session mode (port 5432):** For long-lived servers, such as virtual machines (VM) and enduring containers (if IPv6 is unsupported)
- **Direct Connections:** For long-lived servers, such as VMs and enduring containers (if IPv6 is supported or the IPv4 Add-On is enabled).
## Quickstart connection guides
<Admonition type="tip">
If you'd like us to add a new quickstart guide, share your [feedback](https://supabase.com/dashboard/support/new), and we'll try to add it going forwards.
</Admonition>
### External libraries
- [Prisma](/docs/guides/database/prisma)
- [Drizzle](/docs/guides/database/drizzle)
- [Postgres.js](/docs/guides/database/postgres-js)
### External tools
- [pgAdmin](/docs/guides/database/pgadmin)
- [PSQL](/docs/guides/database/psql)
- [DBeaver](/docs/guides/database/dbeaver)
## Resources
- [Connection management](/docs/guides/database/connection-management)
@@ -54,7 +54,7 @@ In addition to the pre-configured extensions, you can also install your own SQL
</Admonition>
### Upgrade Extensions
### Upgrade extensions
If a new version of an extension becomes available on Supabase, you need to initiate a software upgrade in the [Infrastructure Settings](https://supabase.com/dashboard/project/_/settings/infrastructure) to access it. Software upgrades can also be initiated by restarting your server in the [General Settings](https://supabase.com/dashboard/project/_/settings/general).
@@ -1,194 +1,7 @@
---
id: 'pg_cron'
title: 'pg_cron: Job Scheduling'
description: 'pgnet: a simple cron-based job scheduler for PostgreSQL that runs inside the database.'
title: 'pg_cron: Schedule Recurring Jobs with Cron Syntax in Postgres'
description: 'pg_cron: schedule recurring Jobs with cron syntax in Postgres.'
---
The `pg_cron` extension is a simple cron-based job scheduler for PostgreSQL that runs inside the database.
<Admonition type="note">
pg_cron is not fully supported on Fly Postgres. Read more about this [Fly Postgres limitation here](/docs/guides/platform/fly-postgres#limitations).
</Admonition>
## Usage
### Enable the extension
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
queryGroup="database-method"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard.
2. Click on **Extensions** in the sidebar.
3. Search for "pg_cron" and enable the extension.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Example: enable the "pg_cron" extension
create extension pg_cron with schema pg_catalog;
grant usage on schema cron to postgres;
grant all privileges on all tables in schema cron to postgres;
-- Example: disable the "pg_cron" extension
drop extension if exists pg_cron;
```
</TabPanel>
</Tabs>
### Syntax
The schedule uses the standard cron syntax, in which \* means "run every time period", and a specific number means "but only at this time":
```bash
┌───────────── min (0 - 59)
│ ┌────────────── hour (0 - 23)
│ │ ┌─────────────── day of month (1 - 31)
│ │ │ ┌──────────────── month (1 - 12)
│ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to
│ │ │ │ │ Saturday, or use names; 7 is also Sunday)
│ │ │ │ │
│ │ │ │ │
* * * * *
```
You can use `[1-59] seconds` as the cron syntax to schedule sub-minute jobs. This is available on `pg_cron` v1.5.0+; upgrade your existing Supabase project to use this syntax.
Head over to [crontab.guru](https://crontab.guru/) to validate your cron schedules.
### Scheduling system maintenance
Be extremely careful when setting up `pg_cron` jobs for system maintenance tasks as they can have unintended consequences. For instance, scheduling a command to terminate idle connections with `pg_terminate_backend(pid)` can disrupt critical background processes like nightly backups. Often, there is an existing Postgres setting e.g. `idle_session_timeout` that can perform these common maintenance tasks without the risk.
Reach out to [Supabase Support](https://supabase.com/support) if you're unsure if that applies to your use case.
## Examples
### Delete data every week
Delete old data every Saturday at 3:30am (GMT):
```sql
select cron.schedule (
'saturday-cleanup', -- name of the cron job
'30 3 * * 6', -- Saturday at 3:30am (GMT)
$$ delete from events where event_time < now() - interval '1 week' $$
);
```
### Run a vacuum every day
Vacuum every day at 3:00am (GMT):
```sql
select cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM');
```
### Call a database function every 5 minutes
Create a [`hello_world()`](https://supabase.com/docs/guides/database/functions?language=sql#simple-functions) database function and then call it every 5 minutes:
```sql
select cron.schedule('call-db-function', '*/5 * * * *', 'SELECT hello_world()');
```
### Call a database stored procedure
To use a stored procedure, you can call it like this:
```sql
select cron.schedule('call-db-procedure', '*/5 * * * *', 'CALL my_procedure()');
```
### Invoke Supabase Edge Function every 30 seconds
<Admonition type="note">
This requires `pg_cron` v1.5.0+ and the [`pg_net` extension](/docs/guides/database/extensions/pgnet) to be enabled.
</Admonition>
Make a POST request to a Supabase Edge Function every 30 seconds:
```sql
select
cron.schedule(
'invoke-function-every-half-minute',
'30 seconds',
$$
select
net.http_post(
url:='https://project-ref.supabase.co/functions/v1/function-name',
headers:=jsonb_build_object('Content-Type','application/json', 'Authorization', 'Bearer ' || 'YOUR_ANON_KEY'),
body:=jsonb_build_object('time', now() ),
timeout_milliseconds:=5000
) as request_id;
$$
);
```
### Edit a job
Changes the frequency of a job called `'vacuum'` to once every 5 minutes.
```sql
select cron.alter_job(
job_id := (select jobid from cron.job where jobname = 'vacuum'),
schedule := '*/5 * * * *'
);
```
Full options for the `cron.alter_job()` function are:
```sql
cron.alter_job(
job_id bigint,
schedule text default null,
command text default null,
database text default null,
username text default null,
active boolean default null
)
```
It is also possible to modify a job by using the `cron.schedule()` function, with the same job name. This will replace the existing job, in the manner of an upsert.
### Unschedule a job
Unschedules a job called `'nightly-vacuum'`
```sql
select cron.unschedule('nightly-vacuum');
```
### Viewing previously ran jobs
View the last ten jobs that have ran
```sql
select
*
from cron.job_run_details
order by start_time desc
limit 10;
```
<Admonition type="note">
The records in cron.job_run_details are not cleaned automatically which will take up disk space in your database.
</Admonition>
## Resources
- [`pg_cron` GitHub Repository](https://github.com/citusdata/pg_cron)
See the [Supabase Cron docs](/docs/guides/cron).
@@ -279,11 +279,11 @@ begin
) as request_id;
-- Optionally Log row data or other data for inspection in Supabase Dashboard's Postgres Logs
-- (https://supabase.com/dashboard/project/_/logs/postgres-logs)
raise log 'Logging an entire row as JSON (%)', row_data_var;
-- return the row data to inspect
-- return row data to inspect
return row_data_var;
-- Handle exceptions here if needed
exception
when others then
@@ -315,7 +315,7 @@ Supabase supports reconfiguring pg_net starting from v0.12.0+. For the latest re
</Admonition>
The extension is configured to execute up to 200 requests per second reliably. The response messages are stored for only 6 hours to prevent needless buildup. The default behavior can be modified by rewriting config variables.
The extension is configured to reliably execute up to 200 requests per second. The response messages are stored for only 6 hours to prevent needless buildup. The default behavior can be modified by rewriting config variables.
### Get current settings
@@ -332,7 +332,7 @@ where "name" like 'pg_net%';
Change variables:
```sql
alter role "postgres" set pg_net.ttl to '24 hour';
alter role "postgres" set pg_net.ttl to '24 hours';
alter role "postgres" set pg_net.batch_size to 500;
```
@@ -416,7 +416,7 @@ with "selected_table_rows" as (
-- Converts all the rows into a JSONB array
jsonb_agg(to_jsonb(<table_name>.*)) as JSON_payload
from <table_name>
-- Generally good practice to LIMIT the max amount of rows
-- good practice to LIMIT the max amount of rows
)
select
net.http_post(
@@ -426,7 +426,7 @@ select
FROM "selected_table_rows";
```
More examples can be seen in the [Extension's GitHub page](https://github.com/supabase/pg_net/)
More examples can be seen on the [Extension's GitHub page](https://github.com/supabase/pg_net/)
## Limitations
@@ -435,7 +435,7 @@ More examples can be seen in the [Extension's GitHub page](https://github.com/su
- Can only make POST requests with JSON data. No other data formats are supported
- Intended to handle at most 200 requests per second. Increasing the rate can introduce instability
- Does not have support for PATCH/PUT requests
- Can only work with one database at a time in PostgreSQL. It defaults to the postgres database.
- Can only work with one database at a time. It defaults to the postgres database.
## Resources
@@ -58,7 +58,7 @@ drop extension if exists rum;
### Syntax
#### For type: tsvector
#### For type: `tsvector`
To understand the following you may need first to see [Official PostgreSQL documentation on text
search](https://www.postgresql.org/docs/current/functions-textsearch.html)
@@ -116,7 +116,7 @@ SELECT id, d, d `<=>` '2016-05-16 14:21:25' FROM tsts WHERE t @@ 'wr&qh' ORDER B
(5 rows)
```
#### For type: anyarray
#### For type: `anyarray`
`rum_anyarray_ops`
@@ -0,0 +1,137 @@
---
id: 'orioledb'
title: 'OrioleDB Overview'
description: "A storage extension for PostgreSQL which uses PostgreSQL's pluggable storage system"
---
The [OrioleDB](https://www.orioledb.com/) Postgres extension provides a drop-in replacement storage engine for the default heap storage method. It is designed to improve Postgres' scalability and performance.
OrioleDB addresses PostgreSQL's scalability limitations by removing bottlenecks in the shared memory cache under high concurrency. It also optimizes write-ahead-log (WAL) insertion through row-level WAL logging. These changes lead to significant improvements in the industry standard TPC-C benchmark, which approximates a real-world transactional workload. The following benchmark was performed on a c7g.metal instance and shows OrioleDB's performance outperforming the default PostgreSQL heap method with a 3.3x speedup.
<Image
alt="TPC-C (warehouses = 500)"
src="/docs/img/database/orioledb-tpc-c-500-warehouse.png"
className="max-w-[550px] !mx-auto border rounded-md"
zoomable
/>
<Admonition type="info">
OrioleDB is in active development and currently has [certain limitations](https://www.orioledb.com/docs/usage/getting-started#current-limitations). Currently, only B-tree indexes are supported, so features like pg_vector's HNSW indexes are not yet available. An Index Access Method bridge to unlock support for all index types used with heap storage is under active development. In the Supabase OrioleDB image the default storage method has been updated to use OrioleDB, granting better performance out of the box.
</Admonition>
## Concepts
### Index-organized tables
OrioleDB uses index-organized tables, where table data is stored in the index structure. This design eliminates the need for separate heap storage, reduces overhead and improves lookup performance for primary key queries.
### No buffer mapping
In-memory pages are connected to the storage pages using direct links. This allows OrioleDB to bypass PostgreSQL's shared buffer pool and eliminate the associated complexity and contention in buffer mapping.
### Undo log
Multi-Version Concurrency Control (MVCC) is implemented using an undo log. The undo log stores previous row versions and transaction information, which enables consistent reads while removing the need for table vacuuming completely.
### Copy-on-write checkpoints
OrioleDB implements copy-on-write checkpoints to persist data efficiently. This approach writes only modified data during a checkpoint, reducing the I/O overhead compared to traditional PostgreSQL checkpointing and allowing row-level WAL logging.
## Usage
### Creating OrioleDB project
You can get started with OrioleDB by enabling the extension in your Supabase dashboard.
To get started with OrioleDB you need to [create a new Supabase project](https://supabase.com/dashboard/new/_) and choose `OrioleDB Public Alpha` Postgres version.
<Image
alt="Creating OrioleDB project"
src={{
light: '/docs/img/database/orioledb-creating-project--light.png',
dark: '/docs/img/database/orioledb-creating-project.png',
}}
className="max-w-[550px] !mx-auto border rounded-md"
zoomable
/>
### Creating tables
To create a table using the OrioleDB storage engine just execute the standard `CREATE TABLE` statement. By default it will create a table using OrioleDB storage engine. For example:
```sql
-- Create a table
create table blog_post (
id int8 not null,
title text not null,
body text not null,
author text not null,
published_at timestamptz not null default CURRENT_TIMESTAMP,
views bigint not null,
primary key (id)
);
```
### Creating indexes
OrioleDB tables always have a primary key. If it wasn't defined explicitly, a hidden primary key is created using the `ctid` column.
Additionally you can create secondary indexes.
<Admonition type="info">
Currently, only B-tree indexes are supported, so features like pg_vector's HNSW indexes are not yet available.
</Admonition>
```sql
-- Create an index
create index blog_post_published_at on blog_post (published_at);
create index blog_post_views on blog_post (views) where (views > 1000);
```
### Data manipulation
You can query and modify data in OrioleDB tables using standard SQL statements, including `SELECT`, `INSERT`, `UPDATE`, `DELETE` and `INSERT ... ON CONFLICT`.
```sql
INSERT INTO blog_post (id, title, body, author, views)
VALUES (1, 'Hello, World!', 'This is my first blog post.', 'John Doe', 1000);
SELECT * FROM blog_post ORDER BY published_at DESC LIMIT 10;
id │ title │ body │ author │ published_at │ views
────┼───────────────┼─────────────────────────────┼──────────┼───────────────────────────────┼───────
1 │ Hello, World! │ This is my first blog post. │ John Doe │ 2024-11-15 12:04:18.756824+01 │ 1000
```
### Viewing query plans
You can see the execution plan using standard `EXPLAIN` statement.
```sql
EXPLAIN SELECT * FROM blog_post ORDER BY published_at DESC LIMIT 10;
QUERY PLAN
────────────────────────────────────────────────────────────────────────────────────────────────────────────
Limit (cost=0.15..1.67 rows=10 width=120)
-> Index Scan Backward using blog_post_published_at on blog_post (cost=0.15..48.95 rows=320 width=120)
EXPLAIN SELECT * FROM blog_post WHERE id = 1;
QUERY PLAN
──────────────────────────────────────────────────────────────────────────────────
Index Scan using blog_post_pkey on blog_post (cost=0.15..8.17 rows=1 width=120)
Index Cond: (id = 1)
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM blog_post ORDER BY published_at DESC LIMIT 10;
QUERY PLAN
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Limit (cost=0.15..1.67 rows=10 width=120) (actual time=0.052..0.054 rows=1 loops=1)
-> Index Scan Backward using blog_post_published_at on blog_post (cost=0.15..48.95 rows=320 width=120) (actual time=0.050..0.052 rows=1 loops=1)
Planning Time: 0.186 ms
Execution Time: 0.088 ms
```
## Resources
- [Official OrioleDB documentation](https://www.orioledb.com/docs)
- [OrioleDB GitHub repository](https://github.com/orioledb/orioledb)
@@ -12,7 +12,7 @@ PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/
roles.
</Admonition>
## Policies at the Row Level
## Policies at the row level
Policies in Row Level Security (RLS) are used to restrict access to rows in a table. Think of them like adding a `WHERE` clause to every query.
@@ -35,7 +35,7 @@ update
However, this gives the post owner full access to update the row, including all of the columns.
## Privileges at the Column Level
## Privileges at the column level
To restrict access to columns, you can use [Privileges](https://www.postgresql.org/docs/current/ddl-priv.html).
@@ -80,9 +80,9 @@ You can view and edit the privileges in the [Supabase Studio](https://supabase.c
![Column level privileges](/docs/img/guides/privileges/column-level-privileges-2.png)
## Manage column privileges in Migrations
## Manage column privileges in migrations
While you can manage privileges directly from the Dashboard, as your project grows you may want to manage them in your migrations. Read about database migrations in the [Local Development](https://supabase.com/docs/guides/getting-started/local-development#database-migrations) guide.
While you can manage privileges directly from the Dashboard, as your project grows you may want to manage them in your migrations. Read about database migrations in the [Local Development](https://supabase.com/docs/guides/deployment/database-migrations) guide.
<StepHikeCompact>
@@ -46,7 +46,7 @@ When you created your project you were also asked to enter a password. This is a
Changing the password does not result in any downtime. All connected services, such as postgrest, pgbouncer, and other Supabase managed services, are automatically updated to use the latest password to ensure availability. However, if you have any external services connecting to the Supabase database using hardcoded username/password credentials, a manual update will be required.
## Granting Permissions
## Granting permissions
Roles can be granted various permissions on database objects using the `GRANT` command. Permissions include `SELECT`, `INSERT`, `UPDATE`, and `DELETE`. You can configure access to almost any object inside your database - including tables, views, functions, and triggers.
@@ -3,9 +3,11 @@ title: Timeouts
subtitle: Extend database timeouts to execute longer transactions
---
Requests made through the Supabase API and Dashboard have a timeout of 60 seconds. The database also has a global default timeout of 2 minutes.
<Admonition type="note">
To execute longer transactions, connect to your database using [Supavisor](/docs/guides/database/connecting-to-postgres#connection-pooler) or the [direct connection string](/docs/guides/database/connecting-to-postgres#direct-connections), and change the timeout settings.
Dashboard and [Client](/docs/guides/api/rest/client-libs) queries have a max-configurable timeout of 60 seconds. For longer transactions, use [Supavisor or direct connections](/docs/guides/database/connecting-to-postgres#quick-summary).
</Admonition>
## Change Postgres timeout
@@ -124,9 +126,9 @@ language sql;
The Supabase Dashboard contains tools to help you identify timed-out and long-running queries.
### Using the Log Explorer
### Using the Logs Explorer
Go to the [Log Explorer](/dashboard/project/_/logs/explorer), and run the following query to identify timed-out events (`statement timeout`) and queries that successfully run for longer than 10 seconds (`duration`).
Go to the [Logs Explorer](/dashboard/project/_/logs/explorer), and run the following query to identify timed-out events (`statement timeout`) and queries that successfully run for longer than 10 seconds (`duration`).
```sql
select
+3 -3
View File
@@ -184,7 +184,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Create your migrations">
If you have already modified your Supabase database, synchronize it with your migration file. Otherwise create new tables for your database
@@ -373,7 +373,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Install the prisma client">
Install the prisma client and generate its model
</StepHikeCompact.Details>
@@ -414,7 +414,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={7}>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Details title="Test your API">
Create a index.ts file and run it to test your connection
</StepHikeCompact.Details>
@@ -27,6 +27,8 @@ connection_string.../postgres?KEY1=VALUE&KEY2=VALUE&KEY3=VALUE
# Errors
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
## ... prepared statement already exists
Supavisor in transaction mode (port 6543) does not support [prepared statements](https://www.postgresql.org/docs/current/sql-prepare.html), which Prisma will try to create in the background.
@@ -162,7 +164,7 @@ An alternative strategy to reference these tables is to duplicate values into Pr
<details>
<summary>Show/Hide Details</summary>
```sql table_in_public
-- Create the 'profiles' table in the 'public' schema
create table public.profiles (
@@ -34,7 +34,7 @@ limit 10;
In this query, there are several parts that indexes could likely help in optimizing the performance:
### `where` Clause:
### `where` clause:
The `where` clause filters rows based on certain conditions, and indexing the columns involved can improve this process:
@@ -47,7 +47,7 @@ create index idx_customers_sign_up_date on customers (sign_up_date);
create index idx_orders_status on orders (status);
```
### `join` Columns
### `join` columns
Indexes on the columns used for joining tables can help Postgres avoid scanning tables in their entirety when connecting tables.
@@ -58,7 +58,7 @@ Indexes on the columns used for joining tables can help Postgres avoid scanning
create index idx_orders_customer_id on orders (customer_id);
```
### `order by` Clause
### `order by` clause
Sorting can also be optimized by indexing:
@@ -339,7 +339,7 @@ You can create new migrations either [locally](#develop-locally) or [remotely](#
>
<TabPanel id="local-dev" label="Local Development">
The Supabase CLI provides two options: [manual migrations](https://supabase.com/docs/guides/cli/local-development#database-migrations) and [generated migrations](https://supabase.com/docs/guides/cli/local-development#diffing-changes) using Supabase's local studio and the `supabase db-diff` command. Let's use the latter and push the change to our Preview Branch:
The Supabase CLI provides two options: [manual migrations](https://supabase.com/docs/guides/deployment/database-migrations) and [generated migrations](https://supabase.com/docs/guides/deployment/database-migrations#diffing-changes) using Supabase's local studio and the `supabase db-diff` command. Let's use the latter and push the change to our Preview Branch:
<StepHikeCompact>
@@ -580,9 +580,9 @@ With the Supabase branching integration, you can sync the Git branch used by the
### Vercel
<Admonition type="note" label="Vercel Branching support is in development.">
<Admonition type="note" label="Vercel Marketplace support.">
The Vercel Integration for Supabase branching is under development. To express your interest, [join the discussion on GitHub discussions](https://github.com/orgs/supabase/discussions/18938).
The Vercel Integration for Supabase branching is working only with Supabase managed projects. There is currently no support for Vercel Marketplace managed resources, however the support is planned in the future.
</Admonition>
@@ -598,10 +598,12 @@ For branching to work with Vercel, you also need the [Vercel GitHub integration]
</Admonition>
Supabase automatically updates your Vercel project with the correct environment variables for the corresponding preview branches.
The synchronization happens at the time of Pull Request being opened, not at the time of branch creation.
As branching integration is tied to the Preview Deployments feature in Vercel, there are possible race conditions between Supabase setting correct variables, and Vercel running a deployment process. Because of that, Supabase is always automatically re-deploying the most recent deployment of the given pull request.
### Netlify
## Other Git providers
A Netlify integration is under consideration. If you're interested in branching with Netlify, [join the GitHub discussion](https://github.com/orgs/supabase/discussions/18938).
There are multiple alternative Git providers under consideration. If you're interested in branching for GitLab, Bitbucket, or some other provider, [join the GitHub discussion](https://github.com/orgs/supabase/discussions/18938).
## Alternatives to branching
@@ -0,0 +1,321 @@
---
id: 'database-migrations'
title: 'Database Migrations'
description: 'How to manage schema migrations for your Supabase project.'
subtitle: 'How to manage schema migrations for your Supabase project.'
video: 'https://www.youtube-nocookie.com/v/Kx5nHBmIxyQ'
tocVideo: 'Kx5nHBmIxyQ'
---
Database migrations are SQL statements that create, update, or delete your existing database schemas. They are a common way of tracking changes to your database over time.
## Schema migrations
For this guide, we'll create a table called `employees` and see how we can make changes to it.
You will need to [install](/docs/guides/local-development#quickstart) the Supabase CLI and start the local development stack.
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Create your first migration file">
To get started, generate a [new migration](/docs/reference/cli/supabase-migration-new) to store the SQL needed to create our `employees` table.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase migration new create_employees_table
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Add the SQL to your migration file">
This creates a new migration file in supabase/migrations directory.
To that file, add the SQL to create this `employees` table.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql supabase/migrations/<timestamp>_create_employees_table.sql
create table if not exists employees (
id bigint primary key generated always as identity,
name text not null,
email text,
created_at timestamptz default now()
);
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Apply your first migration">
Run this migration to create the `employees` table.
Now you can visit your new `employees` table in the local Dashboard.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase migration up
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Modify your employees table">
Next, modify your `employees` table by adding a column for `department`.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase migration new add_department_column
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Add a new column to your table">
To that new migration file, add the SQL to create a new `department` column.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql supabase/migrations/<timestamp>_add_department_column.sql
alter table if exists public.employees
add department text default 'Hooli';
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Details title="Apply your second migration">
Run this migration to update your existing `employees` table.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase migration up
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
Finally, you should see the `department` column added to your `employees` table in the local Dashboard.
<Admonition type="info">
View the [complete code](https://github.com/supabase/supabase/tree/master/examples/database/employees) for this example on GitHub.
</Admonition>
### Seeding data
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.
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Populate your table">
Create a seed script in supabase/seed.sql.
To that file, add the SQL to insert data into your `employees` table.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql supabase/seed.sql
insert into public.employees
(name)
values
('Erlich Bachman'),
('Richard Hendricks'),
('Monica Hall');
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Reset your database">
Reset your database to reapply migrations and populate with seed data.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase db reset
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
You should now see the `employees` table, along with your seed data in the Dashboard! All of your database changes are captured in code, and you can reset to a known state at any time, complete with seed data.
### Diffing changes
This workflow is great if you know SQL and are comfortable creating tables and columns. If not, you can still use the Dashboard to create tables and columns, and then use the CLI to diff your changes and create migrations.
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Create your table from the Dashboard">
Create a new table called `cities`, with columns `id`, `name` and `population`.
Then generate a [schema diff](/docs/reference/cli/supabase-db-diff).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase db diff -f create_cities_table
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Add schema diff as a migration">
A new migration file is created for you.
Alternately, you can copy the table definitions directly from the Table Editor.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql supabase/migrations/<timestamp>_create_cities_table.sql
create table "public"."cities" (
"id" bigint primary key generated always as identity,
"name" text,
"population" bigint
);
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Test your migration">
Test your new migration file by resetting your local database.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase db reset
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
The last step is deploying these changes to a live Supabase project.
## Deploy your 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.
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Log in to the Supabase CLI">
[Login](/docs/reference/cli/supabase-login) to the Supabase CLI using an auto-generated Personal Access Token.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase login
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Link your project">
[Link](/docs/reference/cli/supabase-link) to your remote project by selecting from the on-screen prompt.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase link
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Deploy database changes">
[Push](/docs/reference/cli/supabase-db-push) your migrations to the remote database.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
supabase db push
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
Visiting your live project on [Supabase](https://supabase.com/dashboard/project/_), you'll see a new `employees` table, complete with the `department` column you added in the second migration above.
@@ -8,7 +8,7 @@ Supabase is great for building something very fast _and_ for scaling up. However
## Prototyping
The Dashboard is a quick and easy tool for building applications while you are prototyping. That said, we strongly recommend using [Migrations](/docs/guides/getting-started/local-development#database-migrations) to manage your database changes. You can use our CLI to [capture any changes](/docs/reference/cli/supabase-db-diff) you have made on the Dashboard so that you can commit them a version control system, like git.
The Dashboard is a quick and easy tool for building applications while you are prototyping. That said, we strongly recommend using [Migrations](/docs/guides/deployment/database-migrations) to manage your database changes. You can use our CLI to [capture any changes](/docs/reference/cli/supabase-db-diff) you have made on the Dashboard so that you can commit them a version control system, like git.
## Collaborating
@@ -16,7 +16,7 @@ As soon as you start collaborating with team members, all project changes should
Resources:
- [Database migrations](/docs/guides/getting-started/local-development#database-migrations)
- [Database migrations](/docs/guides/deployment/database-migrations)
- [Managing access on the Dashboard](/docs/guides/platform/access-control)
- [PGAudit for Postgres](/docs/guides/database/extensions/pgaudit)
@@ -33,7 +33,7 @@ Once your application is live, you should never change your database using the D
Resources:
- [Database migrations](/docs/guides/getting-started/local-development#database-migrations)
- [Database migrations](/docs/guides/deployment/database-migrations)
- [Managing access on the Dashboard](/docs/guides/platform/access-control)
- [PGAudit for Postgres](/docs/guides/database/extensions/pgaudit)
- [Managing environments](/docs/guides/cli/managing-environments)
@@ -3,39 +3,59 @@ id: 'function-background-tasks'
title: 'Background Tasks'
description: 'How to run background tasks in an Edge Function outside of the request handler'
subtitle: 'How to run background tasks in an Edge Function outside of the request handler'
tocVideo: 'rSKBTJxG9VA'
---
Edge Function instances can process background tasks outside of the request handler. Background tasks are useful for asynchronous operations like uploading a file to Storage, updating a database, or sending events to a logging service. You can respond to the request immediately and leave the task running in the background.
### How long a background task can run
### How it works
A background task can run until the Edge Function instance hits its wall-clock limit or reaches CPU/Memory limit. Check [limits](/docs/guides/functions/limits) for current caps.
You can use `EdgeRuntime.waitUntil(promise)` to explicitly mark background tasks. The Function instance continues to run until the promise provided to `waitUntil` completes.
The maximum duration is capped based on the wall-clock, CPU, and memory limits. The Function will shutdown when it reaches one of these [limits](/docs/guides/functions/limits).
You can listen to the `beforeunload` event handler to be notified when Function invocation is about to be shut down.
### Example
Here's an example of defining a background task using a custom event.
Here's an example of using `EdgeRuntime.waitUntil` to run a background task and using `beforeunload` event to be notified when the instance is about to be shut down.
```ts
// Define a custom event type for the background task.
class MyBackgroundTaskEvent extends Event {
readonly taskPromise: Promise<Response>
constructor(taskPromise: Promise<Response>) {
super('myBackgroundTask')
this.taskPromise = taskPromise
}
async function longRunningTask() {
// do work here
}
globalThis.addEventListener('myBackgroundTask', async (event) => {
const res = await (event as MyBackgroundTaskEvent).taskPromise
console.log(await res.json())
// Mark the longRunningTask's returned promise as a background task.
// note: we are not using await because we don't want it to block.
EdgeRuntime.waitUntil(longRunningTask())
// Use beforeunload event handler to be notified when function is about to shutdown
addEventListener('beforeunload', (ev) => {
console.log('Function will be shutdown due to', ev.detail?.reason)
// save state or log the current progress
})
// Invoke the function using a HTTP request.
// This will start the background task
Deno.serve(async (req) => {
const fetchPromise = fetch('https://httpbin.org/json')
const event = new MyBackgroundTaskEvent(fetchPromise)
globalThis.dispatchEvent(event)
return new Response('ok')
})
```
### Starting a background task in the request handler
You can call `EdgeRuntime.waitUntil` in the request handler too. This will not block the request.
```ts
async function fetchAndLog(url: string) {
const response = await fetch(url)
console.log(response)
}
Deno.serve(async (req) => {
// this will not block the request,
// instead it will run in the background
EdgeRuntime.waitUntil(fetchAndLog('https://httpbin.org/json'))
return new Response('ok')
})
@@ -0,0 +1,54 @@
---
id: 'function-ephemeral-storage'
title: 'Ephemeral Storage'
description: 'Read and write from temporary directory'
subtitle: 'Read and write from temporary directory'
---
Edge Functions provides ephemeral file storage. You can read and write files to the `/tmp` directory.
Ephemeral storage will reset on each function invocation. This means the files you write during an invocation can only be read within the same invocation.
### Use cases
Here are some use cases where ephemeral storage can be useful:
- Unzip an archive of CSVs and then add them as records to the DB
- Custom image manipulation workflows (using [MagickWasm](https://supabase.com/docs/guides/functions/examples/image-manipulation))
You can use [Background Tasks](https://supabase.com/docs/guides/functions/background-tasks) to handle slow file processing outside of a request.
### How to use
You can use [Deno File System APIs](https://docs.deno.com/api/deno/file-system) or the [`node:fs` module](https://docs.deno.com/api/node/fs/) to access the `/tmp` path.
### Example
Here is an example of how to write a user-uploaded zip file into temporary storage for further processing.
```js
Deno.serve(async (req) => {
if (req.headers.get('content-type') !== 'application/zip') {
return new Response('file must be a zip file', {
status: 400,
})
}
const uploadId = crypto.randomUUID()
await Deno.writeFile('/tmp/' + uploadId, req.body)
// do something with the written zip file
return new Response('ok')
})
```
### Unavailable APIs
Currently, the synchronous APIs (eg: `Deno.writeFileSync` or `Deno.mkdirSync`) for creating or writing files are not supported.
You can use sync variations of read APIs (eg: `Deno.readFileSync`).
### Limits
In the hosted platform, a free project can write up to 256MB of data to ephemeral storage. A paid project can write up to 512MB.
@@ -114,7 +114,7 @@ Deno.serve(async (req) => {
})
```
### 3. Create React Email Templates
### 3. Create React Email templates
Create a new folder `_templates` and create a new file `magic-link.tsx` with the following code:
@@ -253,7 +253,7 @@ const code = {
<Admonition type="info">
You can find a selection of React Email templates in the [React Email Eamples](https://react.email/examples).
You can find a selection of React Email templates in the [React Email Examples](https://react.email/examples).
</Admonition>
@@ -296,7 +296,7 @@ supabase secrets set --env-file supabase/functions/.env
That's it, now your Supabase Edge Function will be triggered anytime an Auth Email needs to be send to the user!
## More Resources
## More resources
- [Send Email Hooks](/docs/guides/auth/auth-hooks/send-email-hook)
- [Auth Hooks](/docs/guides/auth/auth-hooks)
@@ -138,7 +138,7 @@ function hexToUint8Array(hex: string) {
}
```
## Deploy the Slash Command handler
## Deploy the slash command handler
```bash
supabase functions deploy discord-bot --no-verify-jwt
@@ -154,7 +154,7 @@ Navigate to your Function details in the Supabase Dashboard to get your Endpoint
The application is now ready. Let's proceed to the next section to install it.
## Install the Slash Command on your Discord server
## Install the slash command on your Discord server
So to use the `hello` Slash Command, we need to install our Greeter application on our Discord server. Here are the steps:
@@ -18,14 +18,14 @@ Push notifications are an important part of any mobile app. They allow you to se
Find the example code on [GitHub](https://github.com/supabase/supabase/blob/master/examples/user-management/expo-push-notifications/).
## Supabase Setup
## Supabase setup
- [Create a new Supabase project](https://database.new).
- Link your project: `supabase link --project-ref your-supabase-project-ref`
- Start supabase locally: `supabase start`
- Push up the schema: `supabase db push` (schema is defined in [supabase/migrations](https://github.com/supabase/supabase/blob/master/examples/user-management/expo-push-notifications/supabase/migrations/))
## Expo Setup
## Expo setup
To utilize Expo's push notification service, you must configure your app by installing a set of libraries, implementing functions to handle notifications, and setting up credentials for Android and iOS. Follow the official [Expo Push Notifications Setup Guide](https://docs.expo.dev/push-notifications/push-notifications-setup/) to get the credentials for Android and iOS. This project uses [Expo's EAS build](https://docs.expo.dev/build/introduction/) service to simplify this part.
@@ -126,7 +126,7 @@ Push notifications are an important part of any mobile app. They allow you to se
This guide will show you how to send push notifications to your app when a new row is inserted into a table using FCM, Supabase Edge Functions, and database web hooks.
## Supabase Setup
## Supabase setup
We will create two tables. One to store the user's FCM token and a `notifications` table. The edge function will be triggered when a new row is inserted into the `notifications` table and sends a push notification to the user.
@@ -258,7 +258,7 @@ Push notifications are an important part of any mobile app. They allow you to se
}
```
## FCM Setup
## FCM setup
1. Follow the official [FCM Setup Guide](https://firebase.google.com/docs/cloud-messaging) to set up FCM for your client side application.
1. Generate a new service account private key from the Firebase console `Project Settings > Service Accounts > Generate new private key`.
@@ -61,7 +61,7 @@ Deno.serve(async (req) => {
})
```
## Create a Postgres Function and RPC
## Create a Database Function and RPC
With the embeddings now stored in your Postgres database table, you can query them from Supabase Edge Functions by utilizing [Remote Procedure Calls (RPC)](/docs/guides/database/functions?language=js).
@@ -79,7 +79,7 @@ supabase functions deploy sentryfied --no-verify-jwt
Find the complete example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/sentryfied/index.ts).
## Working With Scopes
## Working with scopes
Sentry Deno SDK currently do not support `Deno.serve` instrumentation, which means that there is no scope separation between requests.
Because of that, when the Edge Functions runtime is reused between multiple requests, all globally captured breadcrumbs and contextual data
@@ -12,7 +12,7 @@ Developing with Edge Functions is similar to developing with Node.js, but with a
There are two ways to manage your dependencies in Supabase Edge Functions:
### Using deno.json (Recommended)
### Using deno.json (recommended)
<Admonition type="info">
@@ -50,7 +50,7 @@ The recommended file structure when using `deno.json`:
└── config.toml
```
### Using Import Maps (Legacy)
### Using import maps (legacy)
Import Maps are a legacy way to manage dependencies, similar to a `package.json` file. While still supported, we recommend using `deno.json`. If both exist, `deno.json` takes precedence.
@@ -112,7 +112,7 @@ Supabase Edge Functions support several ways to import dependencies:
- Built-in [Node APIs](https://docs.deno.com/runtime/manual/node/compatibility)
- Third party modules published to [JSR](https://jsr.io/) or [deno.land/x](https://deno.land/x)
### NPM Modules
### NPM modules
You can import npm modules using the `npm:` specifier:
@@ -120,7 +120,7 @@ You can import npm modules using the `npm:` specifier:
import { createClient } from 'npm:@supabase/supabase-js@2'
```
### Node.js Built-ins
### Node.js built-ins
For Node.js built-in APIs, use the `node:` specifier:
@@ -130,7 +130,7 @@ import process from 'node:process'
Learn more about npm specifiers and Node built-in APIs in [Deno's documentation](https://docs.deno.com/runtime/manual/node/npm_specifiers).
### Importing from Private Registries
### Importing from private registries
<Admonition type="info">
@@ -8,10 +8,13 @@ subtitle: "Limits applied Edge Functions in Supabase's hosted platform."
## Runtime limits
- Maximum Memory: 256MB
- Maximum Duration (Wall clock limit): 400s (this is the duration an Edge Function worker will stay active. During this period, a worker can serve multiple requests)
- Maximum CPU Time: 2s
- Request idle timeout: 150s (if an Edge Function doesn't send a response before the timeout, 504 Gateway Timeout will be returned)
- Maximum Function Size (after bundling via CLI): 10MB
- Maximum Duration (Wall clock limit):
This is the duration an Edge Function worker will stay active. During this period, a worker can serve multiple requests or process background tasks.
- Free plan: 150s
- Paid plans: 400s
- Maximum CPU Time: 2s (Amount of actual time spent on the CPU per request - does not include async I/O.)
- Request idle timeout: 150s (If an Edge Function doesn't send a response before the timeout, 504 Gateway Timeout will be returned)
- Maximum Function Size: 20MB (After bundling using CLI)
- Maximum log message length: 10,000 characters
- Log event threshold: 100 events per 10 seconds
@@ -19,6 +22,5 @@ subtitle: "Limits applied Edge Functions in Supabase's hosted platform."
- Outgoing connections to ports `25` and `587` are not allowed.
- Serving of HTML content is only supported with [custom domains](/docs/reference/cli/supabase-domains) (Otherwise `GET` requests that return `text/html` will be rewritten to `text/plain`).
- Deno and Node file system APIs are not available.
- Web Worker API (or Node `vm` API) are not available.
- Node Libraries that require multithreading are not supported. Examples: [libvips](https://github.com/libvips/libvips), [sharp](https://github.com/lovell/sharp).
@@ -61,9 +61,9 @@ Deno.serve(async (req) => {
})
```
## Logging Tips
## Logging tips
### Logging Request Headers
### Logging request headers
When debugging Edge Functions, a common mistake is to try to log headers to the developer console via code like this:
@@ -88,7 +88,7 @@ The reason behind this behavior is that [Headers](https://developer.mozilla.org/
However, `Headers` objects are iterable. You can utilize this feature to craft a couple of succinct one-liners for debugging and printing headers.
### Convert headers into an object with Object.fromEntries:
### Convert headers into an object with `Object.fromEntries`:
You can use `Object.fromEntries` which is a call to convert the headers into an object:
@@ -15,7 +15,7 @@ To combine multiple endpoints into a single Edge Function, you can use web appli
Let's dive into some examples.
## Routing with Frameworks
## Routing with frameworks
Here's a simple hello world example using some popular web frameworks.
@@ -158,7 +158,7 @@ curl --request POST 'https://<project_ref>.supabase.co/functions/v1/hello-world'
We should see a response printing `Hello Foo!`.
## Using Route Parameters
## Using route parameters
We can use route parameters to capture values at specific URL segments (eg: `/tasks/:taskId/notes/:noteId`).
@@ -415,7 +415,7 @@ Deno.serve(async (req) => {
</TabPanel>
</Tabs>
## URL Patterns API
## URL patterns API
If you prefer not to use a web framework, you can directly use [URLPattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) within your Edge Functions to implement routing.
This is ideal for small apps with only couple of routes and you want to have a custom matching algorithm.
@@ -13,7 +13,7 @@ description: 'Schedule Edge Functions with pg_cron.'
></iframe>
</div>
The hosted Supabase Platform supports the [`pg_cron` extension](/docs/guides/database/extensions/pgcron), a simple cron-based job scheduler for PostgreSQL that runs inside the database.
The hosted Supabase Platform supports the [`pg_cron` extension](/docs/guides/database/extensions/pgcron), a recurring job scheduler in Postgres.
In combination with the [`pg_net` extension](/docs/guides/database/extensions/pgnet), this allows us to invoke Edge Functions periodically on a set schedule.
@@ -5,6 +5,8 @@ description: 'Edge Functions can return following status codes.'
subtitle: 'Edge Functions can return following status codes.'
---
{/* supa-mdx-lint-disable Rule001HeadingCase */}
## 2XX Success
A successful Edge Function Response
@@ -30,6 +30,8 @@ If your Edge Function takes too long to respond or times out:
- If the boot times are similar, it’s likely an issue with your function’s code, such as a large dependency, a slow API call, or a complex computation. You can try to optimize your code, reduce the size of your dependencies, or use caching techniques to improve the performance of your function.
- If only some of the `booted` events are slow, find the affected `region` in the metadata and submit a support request via the "Help" button at the top.
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
### Receiving 546 Error Response
The 546 error response might occur because:
@@ -47,7 +49,7 @@ The 546 error response might occur because:
## Advanced techniques
### Monitoring Edge Function Resource Usage
### Monitoring Edge Function resource usage
To determine how much memory and CPU your Edge Function consumes, follow these steps:
@@ -62,7 +64,7 @@ To determine how much memory and CPU your Edge Function consumes, follow these s
resources.
</Admonition>
### Understanding CPU soft and hard Limits
### Understanding CPU soft and hard limits
An isolate is like a worker that can handle multiple requests for a function. It works until a time limit of 400 seconds is reached. Now, there are two types of limits for the CPU.
@@ -89,7 +91,7 @@ When analyzing dependencies for your Supabase Edge Functions, it's essential to
By selectively importing only the required submodules, you can effectively reduce the size of your function's dependencies and optimize its performance.
Before finalizing your imports, ensure to review both Deno and NPM dependencies, checking for any unnecessary or redundant dependencies that can be removed. Additionally, check for outdated dependencies and update to the latest versions if possible.
#### Deno Dependencies
#### Deno dependencies
Run `deno info`, providing the path to your input map if you use one.
Review the dependencies listed in the output. Pay attention to any significantly large dependencies, as they can contribute to increased bundle size and potential boot time issues.
@@ -99,7 +101,7 @@ Examine if there are any unnecessary or redundant dependencies that can be remov
deno info --import-map=/path/to/import_map.json /path/to/function/index.ts
```
#### NPM Dependencies
#### NPM dependencies
Additionally, if you utilize NPM modules in your Edge Functions, it's crucial to be mindful of their size and impact on the overall bundle size. While importing NPM modules, consider using the notation `import { submodule } from 'npm:package/submodule'` to selectively import specific submodules rather than importing the entire package. This approach can help minimize unnecessary overhead and streamline the execution of your function.
@@ -0,0 +1,236 @@
---
id: 'function-websockets'
title: 'Handling WebSockets'
description: 'How to handle WebSocket connections in Edge Functions'
subtitle: 'How to handle WebSocket connections in Edge Functions'
---
Edge Functions supports hosting WebSocket servers that can facilitate bi-directional communications with browser clients.
You can also establish outgoing WebSocket client connections to another server from Edge Functions (e.g., [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview)). You can find an example OpenAI Realtime Relay Server implementation on the [supabase-community GitHub account](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server).
### Writing a WebSocket server
Here are some basic examples of setting up WebSocket servers using Deno and Node.js APIs.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="deno"
queryGroup="runtime"
>
<TabPanel id="deno" label="Deno">
```ts
Deno.serve(req => {
const upgrade = req.headers.get("upgrade") || "";
if (upgrade.toLowerCase() != "websocket") {
return new Response("request isn't trying to upgrade to websocket.", { status: 400 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onopen = () => console.log("socket opened");
socket.onmessage = (e) => {
console.log("socket message:", e.data);
socket.send(new Date().toString());
};
socket.onerror = e => console.log("socket errored:", e.message);
socket.onclose = () => console.log("socket closed");
return response;
});
````
</TabPanel>
<TabPanel id="node" label="Node.js">
```ts
import { createServer } from "node:http";
import { WebSocketServer } from "npm:ws";
const server = createServer();
// Since we manually created the HTTP server,
// turn on the noServer mode.
const wss = new WebSocketServer({ noServer: true });
wss.on("connection", ws => {
console.log("socket opened");
ws.on("message", (data /** Buffer */, isBinary /** bool */) => {
if (isBinary) {
console.log("socket message:", data);
} else {
console.log("socket message:", data.toString());
}
ws.send(new Date().toString());
});
ws.on("error", err => {
console.log("socket errored:", err.message);
});
ws.on("close", () => console.log("socket closed"));
});
server.on("upgrade", (req, socket, head) => {
wss.handleUpgrade(req, socket, head, ws => {
wss.emit("connection", ws, req);
});
});
server.listen(8080);
````
</TabPanel>
</Tabs>
### Outbound WebSockets
You can also establish an outbound WebSocket connection to another server from an Edge Function.
Combining it with incoming WebSocket servers, it's possible to use Edge Functions as a WebSocket proxy, for example as a [relay server](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server) for the [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview).
<$CodeSample
external={true}
org="supabase-community"
repo="openai-realtime-console"
commit="0f93657a71670704fbf77c48cf54d6c9eb956698"
path="/supabase/functions/relay/index.ts"
meta="supabase/functions/relay/index.ts"
lines={[[1, 3], [5, -1]]}
/>
### Authentication
WebSocket browser clients don't have the option to send custom headers. Because of this, Edge Functions won't be able to perform the usual authorization header check to verify the JWT.
You can skip the default authorization header checks by explicitly providing `--no-verify-jwt` when serving and deploying functions.
To authenticate the user making WebSocket requests, you can pass the JWT in URL query params or via a custom protocol.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="query"
queryGroup="auth"
>
<TabPanel id="query" label="Using query params">
```ts
import { createClient } from "jsr:@supabase/supabase-js@2";
const supabase = createClient(
Deno.env.get("SUPABASE_URL"),
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"),
);
Deno.serve(req => {
const upgrade = req.headers.get("upgrade") || "";
if (upgrade.toLowerCase() != "websocket") {
return new Response("request isn't trying to upgrade to websocket.", { status: 400 });
}
// Please be aware query params may be logged in some logging systems.
const url = new URL(req.url);
const jwt = url.searchParams.get("jwt");
if (!jwt) {
console.error("Auth token not provided");
return new Response("Auth token not provided", { status: 403 });
}
const { error, data } = await supabase.auth.getUser(jwt);
if (error) {
console.error(error);
return new Response("Invalid token provided", { status: 403 });
}
if (!data.user) {
console.error("user is not authenticated");
return new Response("User is not authenticated", { status: 403 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onopen = () => console.log("socket opened");
socket.onmessage = (e) => {
console.log("socket message:", e.data);
socket.send(new Date().toString());
};
socket.onerror = e => console.log("socket errored:", e.message);
socket.onclose = () => console.log("socket closed");
return response;
});
````
</TabPanel>
<TabPanel id="protocol" label="Using custom protocol">
```ts
import { createClient } from "jsr:@supabase/supabase-js@2";
const supabase = createClient(
Deno.env.get("SUPABASE_URL"),
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"),
);
Deno.serve(req => {
const upgrade = req.headers.get("upgrade") || "";
if (upgrade.toLowerCase() != "websocket") {
return new Response("request isn't trying to upgrade to websocket.", { status: 400 });
}
// Sec-WebScoket-Protocol may return multiple protocol values `jwt-TOKEN, value1, value 2`
const customProtocols = (req.headers.get("Sec-WebSocket-Protocol") ?? '').split(',').map(p => p.trim())
const jwt = customProtocols.find(p => p.startsWith('jwt')).replace('jwt-', '')
if (!jwt) {
console.error("Auth token not provided");
return new Response("Auth token not provided", { status: 403 });
}
const { error, data } = await supabase.auth.getUser(jwt);
if (error) {
console.error(error);
return new Response("Invalid token provided", { status: 403 });
}
if (!data.user) {
console.error("user is not authenticated");
return new Response("User is not authenticated", { status: 403 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onopen = () => console.log("socket opened");
socket.onmessage = (e) => {
console.log("socket message:", e.data);
socket.send(new Date().toString());
};
socket.onerror = e => console.log("socket errored:", e.message);
socket.onclose = () => console.log("socket closed");
return response;
});
````
</TabPanel>
</Tabs>
### Limits
The maximum duration is capped based on the wall-clock, CPU, and memory limits. The Function will shutdown when it reaches one of these [limits](/docs/guides/functions/limits).
### Testing WebSockets locally
When testing Edge Functions locally with Supabase CLI, the instances are terminated automatically after a request is completed. This will prevent keeping WebSocket connections open.
To prevent that, you can update the `supabase/config.toml` with the following settings:
```toml
[edge_runtime]
policy = "per_worker"
```
When running with `per_worker` policy, Function won't auto-reload on edits. You will need to manually restart it by running `supabase functions serve`.
@@ -0,0 +1,16 @@
---
title: AI Prompts
subtitle: Prompts for working with Supabase using AI-powered IDE tools
---
We've curated a selection of prompts to help you work with Supabase using your favorite AI-powered IDE tools, such as GitHub Copilot or Cursor.
## How to use
Copy the prompt to a file in your repo.
Use the "include file" feature from your AI tool to include the prompt when chatting with your AI assistant. For example, with GitHub Copilot, use `#<filename>`, in Cursor, use `@Files`, and in Zed, use `/file`.
## Prompts
<AiPromptsIndex />
@@ -16,7 +16,7 @@ Every project is a full Postgres database. [Docs](/docs/guides/database).
Store vector embeddings right next to the rest of your data. [Docs](/docs/guides/ai).
### Auto-generated Rest API via PostgREST
### Auto-generated REST API via PostgREST
RESTful APIs are auto-generated from your database, without a single line of code. [Docs](/docs/guides/api#rest-api-overview).
@@ -66,13 +66,13 @@ Deploy read-only databases across multiple regions, for lower latency and better
Launch the Supabase stack on Fly.io edge network. [Docs](/docs/guides/platform/fly-postgres).
### Log Drains
### Log drains
Export Supabase logs at to 3rd party providers and external tooling. [Docs](/docs/guides/platform/log-drains).
## Studio
### Studio Single Sign On
### Studio Single Sign-On
Login to the Supabase dashboard via SSO. [Docs](/docs/guides/platform/sso).
@@ -118,7 +118,7 @@ Control the data each user can access with Postgres Policies. [Docs](/docs/guide
Add Captcha to your sign-in, sign-up, and password reset forms. [Docs](/docs/guides/auth/auth-captcha).
### Server-side Auth
### Server-Side Auth
Helpers for implementing user authentication in popular server-side languages and frameworks like Next.js, SvelteKit and Remix. [Docs](/docs/guides/auth/server-side).
@@ -183,11 +183,11 @@ Unofficial libraries are supported by the community.
Supabase Features are in 4 different states - Private Alpha, Public Alpha, Beta and Generally Available.
### Private Alpha
### Private alpha
Features are initially launched as a private alpha to gather feedback from the community. To join our early access program, send an email to product-ops@supabase.io.
### Public Alpha
### Public alpha
The alpha stage indicates that the API might change in the future, not that the service isn’t stable. Even though the [uptime Service Level Agreement](https://supabase.com/sla) does not cover products in Alpha, we do our best to have the service as stable as possible.
@@ -195,7 +195,7 @@ The alpha stage indicates that the API might change in the future, not that the
Features in Beta are tested by an external penetration tester for security issues. The API is guaranteed to be stable and there is a strict communication process for breaking changes.
### Generally Available
### Generally available
In addition to the Beta requirements, features in GA are covered by the [uptime SLA](https://supabase.com/sla).
@@ -116,7 +116,7 @@ hideToc: true
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```prisma api/prisma/schema.prisma
```prisma api/db/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
@@ -96,6 +96,8 @@ plugins {
}
```
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
### Set up Hilt for dependency injection
In the `build.gradle` (app) file, add the following:
@@ -134,6 +136,8 @@ class MainActivity : ComponentActivity() {
}
```
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
### Provide Supabase instances with Hilt
To make the app easier to test, create a `SupabaseModule.kt` file as follows:
@@ -301,7 +301,7 @@ meta="utils/supabase/middleware.ts"
## Set up a login page
### Login and Signup form
### Login and signup form
Create a login/signup page for your application:
@@ -97,7 +97,7 @@ You can find the full contents of this file [here](https://raw.githubusercontent
In order for us to add login and user profile pages in this App, we have to tweak the `<Refine />` component inside `App.tsx`.
### The `<Refine />` Component
### The `<Refine />` component
The `App.tsx` file initially looks like this:
@@ -293,6 +293,8 @@ Update the entry point to the newly created `AppView`. Run in Xcode to launch yo
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
photos and videos.
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
### Add PhotosPicker
Let's add support for the user to pick an image from the library and upload it.
@@ -332,6 +334,8 @@ enum TransferError: Error {
}
```
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
#### Add PhotosPicker to profile page
```swift ProfileView.swift
@@ -10,7 +10,7 @@ The Vercel Marketplace is a feature that allows you to manage third-party resour
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.
For more information, see [Introducing the Vercel Marketplace](https://vercel.com/blog/introducing-the-vercel-marketplace) blog post.
<Admonition type="note">
@@ -131,7 +131,7 @@ If you have any Supabase containers running locally, stop them and delete their
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 diff -f my_schema
supabase db dump --local --data-only > supabase/seed.sql
supabase stop --no-backup
```
@@ -335,6 +335,29 @@ This will upload files from `supabase/images` directory to a bucket named `image
supabase seed buckets
```
### Sync any schema with `--schema`
You can synchronize your database with a specific schema using the `--schema` option as follows:
```bash
supabase db pull --schema <schema_name>
```
<Admonition type="warning">
Using `--schema`
If the local `supabase/migrations` directory is empty, the db pull command will ignore the `--schema` parameter.
To fix this, you can pull twice:
```bash
supabase db pull
supabase db pull --schema <schema_name>
```
</Admonition>
## Limitations and considerations
The local development environment is not as feature-complete as the Supabase Platform. Here are some of the differences:
@@ -135,6 +135,41 @@ 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.
## Restore to a new project
Supabase provides a convenient way to restore data from an existing project into a completely new one. Whether you're using physical backups or Point-in-Time recovery (PITR), this feature allows you to duplicate project data with ease, perform testing safely, or recover data for analysis. Access to this feature is exclusive to users on paid plans and requires that physical backups are enabled for the source project.
<Admonition type="note">
PITR is an additional add-on available for organizations on a paid plan with physical backups enabled.
</Admonition>
To begin, switch to the source project—the project containing the data you wish to restore—and go to the [database backups](https://supabase.com/dashboard/project/_/database/backups/restore-to-new-project) page. Select the **Restore to a New Project** tab.
A list of available backups is displayed. Select the backup you want to use and click the "Restore" button. For projects with PITR enabled, use the date and time selector to specify the exact point in time from which you wish to restore data.
Once you’ve made your choice, Supabase takes care of the rest. A new project is automatically created, replicating key configurations from the original, including the compute instance size, disk attributes, SSL enforcement settings, and network restrictions. The data will remain in the same region as the source project to ensure compliance with data residency requirements. The entire process is fully automated.
<Admonition type="note">
The time required to complete the restoration can vary depending largely on the volume of data involved. If you have a large amount of data you can opt for higher performing disk attributes on the source project _before_ starting a clone operation. These disk attributes will be replicated to the new project. This incurs additional costs which will be displayed before starting.
</Admonition>
There are a few important restrictions to be aware of with the "Restore to a New Project" process:
- Projects that are created through the restoration process cannot themselves be used as a source for further clones at this time.
- The feature is only accessible to paid plan users with physical backups enabled, ensuring that the necessary resources and infrastructure are available for the restore process.
Before starting the restoration, you’ll be presented with an overview of the costs associated with creating the new project. The new project will incur additional monthly expenses based on the mirrored resources from the source project. It’s important to review these costs carefully before proceeding.
Once the restoration is complete, the new project will be available in your dashboard and will include all data, tables, schemas, and selected settings from the chosen backup source. It is recommended to thoroughly review the new project and perform any necessary tests to ensure everything has been restored as expected.
New projects are completely independent of their source, and as such can be modified and used as desired.
Restoring to a new project is an excellent way to manage environments more effectively. You can use this feature to create staging environments for testing, experiment with changes without risk to production data, or swiftly recover from unexpected data loss scenarios.
## Troubleshooting
### Logical backups
@@ -10,11 +10,9 @@ Every project on the Supabase Platform comes with its own dedicated Postgres ins
The following table describes the base instances, Nano (free plan) and Micro (paid plans), with additional compute instance sizes available if you need extra performance when scaling up.
<Admonition type="note" label="Upgrade to Micro Instances at No Additional Charge for Paid Projects">
<Admonition type="note" label="Nano instances in Pro organizations">
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-and-disk#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.
In Pro organizations, Nano Compute are billed at the same price as Micro Compute. It is recommended to upgrade your Project from Nano Compute to Micro Compute when it's convenient for you. Compute sizes are not auto-upgraded because of the downtime incurred. See [Supabase Pricing](https://supabase.com/pricing) for more information.
</Admonition>
@@ -110,13 +108,14 @@ Be aware that increasing IOPS or throughput incurs additional charges.
When selecting your disk, it's essential to focus on the performance needs of your workload. Here's a comparison of our available disk types:
| | General Purpose SSD (gp3) | High Performance SSD (io2) |
| ----------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| **Use Case** | General workloads, development environments, small to medium databases | High-performance needs, large-scale databases, mission-critical applications |
| **Max Disk Size** | 16 TB | 60 TB |
| **Max IOPS** | 16,000 IOPS (at 32 GB disk size) | 80,000 IOPS (at 80 GB disk size) |
| **Throughput** | 125 MiB/s (default) to 1,000 MiB/s (maximum) | Automatically scales with IOPS |
| **Best For** | Great value for most use cases | Low latency and very high IOPS requirements |
| | General Purpose SSD (gp3) | High Performance SSD (io2) |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Use Case** | General workloads, development environments, small to medium databases | High-performance needs, large-scale databases, mission-critical applications |
| **Max Disk Size** | 16 TB | 60 TB |
| **Max IOPS** | 16,000 IOPS (at 32 GB disk size) | 80,000 IOPS (at 80 GB disk size) |
| **Throughput** | 125 MiB/s (default) to 1,000 MiB/s (maximum) | Automatically scales with IOPS |
| **Best For** | Great value for most use cases | Low latency and very high IOPS requirements |
| **Pricing** | Disk: 8 GB included, then $0.125 per GB<br/>IOPS: 3,000 included, then $0.024 per IOPS<br/>Throughput: 125 Mbps included, then $0.95 per Mbps | Disk: $0.195 per GB<br/>IOPS: $0.119 per IOPS<br/>Throughput: Scales with IOPS at no additional cost |
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.
@@ -139,8 +139,22 @@ Once you have reclaimed space, you can run the following to disable [read-only](
set default_transaction_read_only = 'off';
```
### Disk Size Distribution
You can check the distribution of your disk size on your [project's compute and disk page](/dashboard/_/settings/compute-and-disk).
![Disk Size Distribution](/docs/img/guides/platform/database-size/disk-size-distribution.png)
Your disk size usage falls in three categories:
- **Database** - Disk usage by the database. This includes the actual data, indexes, materialized views, ...
- **WAL** - Disk usage by the write-ahead log. The usage depends on your WAL settings and the amount of data being written to the database.
- **System** - Disk usage reserved by the system to ensure the database can operate smoothly. Users cannot modify this and it should only take very little space.
### Reducing disk size
Disks don't automatically downsize during normal operation. Once you have [reduced your database size](/docs/guides/platform/database-size#database-size), they _will_ automatically "right-size" during a [project upgrade](/docs/guides/platform/upgrading). The final disk size after the upgrade is 1.2x the size of the database with a minimum of 8 GB. For example, if your database size is 100GB, and you have a 200GB disk, the size after a project upgrade will be 120 GB.
In case you have a large WAL directory, you may [modify WAL settings](/docs/guides/database/custom-postgres-config) such as `max_wal_size`. Use at your own risk as changing these settings can have side effects. To query your current WAL size, use `SELECT SUM(size) FROM pg_ls_waldir()`.
In the event that your project is already on the latest version of Postgres and cannot be upgraded, a new version of Postgres will be released approximately every week which you can then upgrade to once it becomes available.
+34
View File
@@ -0,0 +1,34 @@
---
title: Supabase Queues
subtitle: 'Durable Message Queues with Guaranteed Delivery in Postgres'
---
Supabase Queues is a Postgres-native durable Message Queue system with guaranteed delivery built on the [pgmq database extension](https://github.com/tembo-io/pgmq). It offers developers a seamless way to persist and process Messages in the background while improving the resiliency and scalability of their applications and services.
Queues couples the reliability of Postgres with the simplicity Supabase's platform and developer experience, enabling developers to manage Background Tasks with zero configuration.
## Features
- **Postgres Native**
<br />
Built on top of the `pgmq` database extension, create and manage Queues with any Postgres tooling.
- **Guaranteed Message Delivery**
<br />
Messages added to Queues are guaranteed to be delivered to your consumers.
- **Exactly Once Message Delivery**
<br />A Message is delivered exactly once to a consumer within a customizable visibility window.
- **Message Durability and Archival**
<br />
Messages are stored in Postgres and you can choose to archive them for analytical or auditing purposes.
- **Granular Authorization**
<br />
Control client-side consumer access to Queues with API permissions and Row Level Security (RLS) policies.
- **Queue Management and Monitoring**
<br />
Create, manage, and monitor Queues and Messages in the Supabase Dashboard.
## Resources
- [Quickstart](/docs/guides/queues/quickstart)
- [API Reference](/docs/guides/queues/api)
- [`pgmq` GitHub Repository](https://github.com/tembo-io/pgmq)
+62
View File
@@ -0,0 +1,62 @@
---
title: API
---
{/* <!-- vale off --> */}
When you create a Queue in Supabase, you can choose to create helper database functions in the `pgmq_public` schema. This schema exposes operations to manage Queue Messages to consumers client-side, but does not expose functions for creating or dropping Queues.
Database functions in `pgmq_public` can be exposed via Supabase Data API so consumers client-side can call them. Visit the [Quickstart](/docs/guides/queues/quickstart) for an example.
## `pgmq_public.pop(queue_name)`
Retrieves the next available message and deletes it from the specified Queue.
- `queue_name` (text): Queue name
---
### `pgmq_public.send(queue_name, message, sleep_seconds)`
Adds a Message to the specified Queue, optionally delaying its visibility to all consumers by a number of seconds.
- `queue_name` (text): Queue name
- `message` (jsonb): Message payload to send
- `sleep_seconds` (integer, optional): Delay message visibility by specified seconds. Defaults to 0
---
### `pgmq_public.send_batch(queue_name, messages, sleep_seconds)`
Adds a batch of Messages to the specified Queue, optionally delaying their availability to all consumers by a number of seconds.
- `queue_name` (text): Queue name
- `messages` (jsonb[]): Array of message payloads to send
- `sleep_seconds` (integer, optional): Delay messages visibility by specified seconds. Defaults to 0
---
### `pgmq_public.archive(queue_name, message_id)`
Archives a Message by moving it from the Queue table to the Queue's archive table.
- `queue_name` (text): Queue name
- `message_id` (bigint): ID of the Message to archive
---
### `pgmq_public.delete(queue_name, message_id)`
Permanently deletes a Message from the specified Queue.
- `queue_name` (text): Queue name
- `message_id` (bigint): ID of the Message to delete
---
### `pgmq_public.read(queue_name, sleep_seconds, n)`
Reads up to "n" Messages from the specified Queue with an optional "sleep_seconds" (visibility timeout).
- `queue_name` (text): Queue name
- `sleep_seconds` (integer): Visibility timeout in seconds
- `n` (integer): Maximum number of Messages to read
@@ -0,0 +1,195 @@
---
title: Quickstart
subtitle: 'Learn how to use Supabase Queues to add and read messages'
---
{/* <!-- vale off --> */}
This guide is an introduction to interacting with Supabase Queues via the Dashboard and official client library. Check out [Queues API Reference](/docs/guides/queues/api) for more details on our API.
## Concepts
Supabase Queues is a pull-based Message Queue consisting of three main components: Queues, Messages, and Queue Types.
### Pull-Based Queue
A pull-based Queue is a Message storage and delivery system where consumers actively fetch Messages when they're ready to process them - similar to constantly refreshing a webpage to display the latest updates. Our pull-based Queues process Messages in a First-In-First-Out (FIFO) manner without priority levels.
### Message
A Message in a Queue is a json object that is stored until a consumer explicitly processes and removes it, like a task waiting in a to-do list until someone checks and completes it.
### Queue Types
Supabase Queues offers three types of Queues:
- **Basic Queue**: A durable Queue that stores Messages in a logged table.
- **Unlogged Queue**: A transient Queue that stores Messages in an unlogged table for better performance but may result in loss of Queue Messages.
- **Partitioned Queue** (_Coming Soon_): A durable and scalable Queue that stores Messages in multiple table partitions for better performance.
## Create Queues
To get started, navigate to the [Supabase Queues](/dashboard/project/_/integrations/queues/overview) Postgres Module under Integrations in the Dashboard and enable the `pgmq` extension.
<Admonition type="note">
`pgmq` extension is available in Postgres version 15.6.1.143 or later.
</Admonition>
<Image
alt="Supabase Dashboard Integrations page, showing the Queues Postgres Module"
src={{
dark: '/docs/img/queues-quickstart-install.png',
light: '/docs/img/queues-quickstart-install.png',
}}
/>
On the [Queues page](/dashboard/project/_/integrations/queues/queues):
- Click **Add a new queue** button
<Admonition type="note">
If you've already created a Queue click the **Create a queue** button instead.
</Admonition>
- Name your queue
<Admonition type="note">
Queue names can only be lowercase and hyphens and underscores are permitted.
</Admonition>
- Select your [Queue Type](#queue-types)
<Image
alt="Create a Queue from the Supabase Dashboard"
src={{
dark: '/docs/img/queues-quickstart-create.png',
light: '/docs/img/queues-quickstart-create.png',
}}
zoomable
className="max-w-lg !mx-auto"
/>
### What happens when you create a queue?
Every new Queue creates two tables in the `pgmq` schema. These tables are `pgmq.q_<queue_name>` to store and process active messages and `pgmq.a_<queue_name>` to store any archived messages.
A "Basic Queue" will create `pgmq.q_<queue_name>` and `pgmq.a_<queue_name>` tables as logged tables.
However, an "Unlogged Queue" will create `pgmq.q_<queue_name>` as an unlogged table for better performance while sacrificing durability. The `pgmq.a_<queue_name>` table will still be created as a logged table so your archived messages remain safe and secure.
## Expose Queues to client-side consumers
Queues, by default, are not exposed over Supabase Data API and are only accessible via Postgres clients.
However, you may grant client-side consumers access to your Queues by enabling the Supabase Data API and granting permissions to the Queues API, which is a collection of database functions in the `pgmq_public` schema that wraps the database functions in the `pgmq` schema.
This is to prevent direct access to the `pgmq` schema and its tables (RLS is not enabled by default on any tables) and database functions.
To get started, navigate to the Queues [Settings page](/dashboard/project/_/integrations/queues/settings) and toggle on “Expose Queues via PostgREST”. Once enabled, Supabase creates and exposes a `pgmq_public` schema containing database function wrappers to a subset of `pgmq`'s database functions.
<Image
alt="Screenshot of Queues settings with toggle to expose to PostgREST"
src={{
dark: '/docs/img/queues-quickstart-settings.png',
light: '/docs/img/queues-quickstart-settings.png',
}}
/>
### Enable RLS on your tables in `pgmq` schema
For security purposes, you must enable Row Level Security (RLS) on all Queue tables (all tables in `pgmq` schema that begin with `q_`) if the Data API is enabled.
You’ll want to create RLS policies for any Queues you want your client-side consumers to interact with.
<Image
alt="Screenshot of creating an RLS policy from the Queues settings"
src={{
dark: '/docs/img/queues-quickstart-rls.png',
light: '/docs/img/queues-quickstart-rls.png',
}}
/>
### Grant permissions to `pgmq_public` database functions
On top of enabling RLS and writing RLS policies on the underlying Queue tables, you must grant the correct permissions to the `pgmq_public` database functions for each Data API role.
The permissions required for each Queue API database function:
| **Operations** | **Permissions Required** |
| ------------------- | ------------------------ |
| `send` `send_batch` | `Select` `Insert` |
| `read` `pop` | `Select` `Update` |
| `archive` `delete` | `Select` `Delete` |
<Image
alt="Screenshot of configuring API access for roles from the Queues settings"
src={{
dark: '/docs/img/queues-quickstart-roles.png',
light: '/docs/img/queues-quickstart-roles-light.png',
}}
/>
<Admonition type="note">
`postgres` and `service_role` roles should never be exposed client-side.
</Admonition>
### Enqueuing and Dequeuing Messages
Once your Queue has been created, you can begin enqueuing and dequeuing Messages.
Here's a TypeScript example using the official Supabase client library:
```tsx
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'supabaseURL'
const supabaseKey = 'supabaseKey'
const supabase = createClient(supabaseUrl, supabaseKey)
const QueuesTest: React.FC = () => {
//Add a Message
const sendToQueue = async () => {
const result = await supabase.rpc('send', {
queue_name: 'foo',
message: { hello: 'world' },
sleep_seconds: 30,
})
console.log(result)
}
//Dequeue Message
const popFromQueue = async () => {
const result = await supabase.rpc('pop', { queue_name: 'foo' })
console.log(result)
}
return (
<div className="p-6">
<h2 className="text-2xl font-bold mb-4">Queue Test Component</h2>
<button
onClick={sendToQueue}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 mr-4"
>
Add Message
</button>
<button
onClick={popFromQueue}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Pop Message
</button>
</div>
)
}
export default QueuesTest
```
@@ -84,6 +84,11 @@ The following third-party providers have shown consistent support for the self-h
description: 'Deploys using Kubernetes.',
href: 'https://stackgres.io/blog/running-supabase-on-top-of-stackgres/',
},
{
name: 'Pigsty',
description: 'Deploys using Ansible.',
href: 'https://pigsty.io/blog/db/supabase/',
},
].map((x) => (
<div className="md:col-span-6" key={x.href}>
<Link href={x.href} passHref>
@@ -80,7 +80,7 @@ Allow a user to access a file that was previously uploaded by the same user:
create policy "Individual user Access"
on storage.objects for select
to authenticated
using ( (select auth.uid()) = owner_id );
using ( (select auth.uid()) = owner_id::uuid );
```
---
@@ -0,0 +1,11 @@
---
title = "Why is my camelCase name not working in Postgres functions or RLS policies?"
github_url = "https://github.com/orgs/supabase/discussions/12893"
date_created = "2023-03-08T16:26:51+00:00"
topics = [ "database" ]
database_id = "9dbcf760-7b1f-4cc4-8653-4e884fd02dad"
---
Avoid camelCase (upper case letters) if possible when naming functions, tables, columns, and variables. You must enclose "camelCase" in double quotes when you define it or use it. YOU WILL FORGET. Use snake_case instead and make your life easier.
Note to Prisma users, you will likely have to deal with it.
@@ -5,3 +5,5 @@ hideTitle: true
---
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.
To convert SQL queries to `supabase-js` calls, use the [SQL to REST API translator](/docs/guides/api/sql-to-rest).
+2
View File
@@ -30,6 +30,8 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
Checkout the different READMEs for information about supported Kotlin targets.
*Note that the minimum Android SDK version is 26. For lower versions, you need to enable [core library desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring).*
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
+2 -1
View File
@@ -11,6 +11,7 @@ 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'
import { PromoToast } from 'ui-patterns'
/**
* Global providers that wrap the entire app
@@ -25,7 +26,7 @@ function GlobalProviders({ children }: PropsWithChildren) {
<CommandProvider>
<div className="flex flex-col">
<SiteLayout>
{/* <PromoToast /> */}
<PromoToast />
{children}
<DocsCommandMenu />
</SiteLayout>
@@ -4,6 +4,17 @@ import Link from 'next/link'
import { useState, type PropsWithChildren } from 'react'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
export function CodeSampleDummy() {
return (
<Admonition type="caution" title="Local development warning">
The <code>$CodeSample</code> directive with external repos is not supported in local
development because it relies on a GitHub API key. Please check the preview site to see the
final UI.
</Admonition>
)
}
export function CodeSampleWrapper({
children,
@@ -14,10 +14,17 @@ const transformWithMock = codeSampleRemark({
let env: NodeJS.Process['env']
vi.mock('~/lib/constants', () => ({
IS_PLATFORM: true,
}))
describe('$CodeSample', () => {
beforeAll(() => {
env = process.env
process.env = { NODE_ENV: 'test', NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA: '1234567890' }
process.env = {
NODE_ENV: 'test',
NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA: '1234567890',
}
})
afterAll(() => {
Loaded 100 of 899 files, more files were not shown because too many files have changed in this diff. Show more