mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 22:59:11 +03:00
Merge branch 'master' into feat/tabs
This commit is contained in:
commit
5375fa78a2
937 files changed
+32464
-15311
No files matched your search
@@ -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
|
||||
@@ -27,4 +27,5 @@ jobs:
|
||||
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: ${{ steps.changed-files.outputs.CHANGED_FILES }}
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
+96
@@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
+26
@@ -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
|
||||
@@ -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
@@ -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">
|
||||
|
||||
@@ -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, [])
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
@@ -1944,6 +2021,7 @@ export const platform: NavMenuConstant = {
|
||||
{ name: 'Compute and Disk', url: '/guides/platform/compute-and-disk' },
|
||||
{ name: 'Database Size', url: '/guides/platform/database-size' },
|
||||
{ name: 'Fly Postgres', url: '/guides/platform/fly-postgres' },
|
||||
{ name: 'HIPAA Projects', url: '/guides/platform/hipaa-projects' },
|
||||
{
|
||||
name: 'Network Restrictions',
|
||||
url: '/guides/platform/network-restrictions',
|
||||
@@ -2124,6 +2202,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 +2619,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:
|
||||
|
||||
@@ -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
|
||||
@@ -116,7 +116,7 @@ Converting an anonymous user to a permanent user requires [linking an identity](
|
||||
You can use the [`updateUser()`](/docs/reference/javascript/auth-updateuser) method to link an email or phone identity to the anonymous user. To add a password for the anonymous user, the user's email or phone number needs to be verified first.
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.auth.updateUser({ email: 'example@email.com' })
|
||||
const { data, error } = await supabase.auth.updateUser({ email: 'valid.email@supabase.io' })
|
||||
|
||||
// verify the user's email by clicking on the email change link
|
||||
// or entering the 6-digit OTP sent to the email address
|
||||
@@ -131,7 +131,7 @@ const { data, error } = await supabase.auth.updateUser({ password: 'password' })
|
||||
You can use the [`updateUser()`](/docs/reference/dart/auth-updateuser) method to link an email or phone identity to the anonymous user.
|
||||
|
||||
```dart
|
||||
await supabase.auth.updateUser(UserAttributes(email: 'example@email.com'));
|
||||
await supabase.auth.updateUser(UserAttributes(email: 'valid.email@supabase.io'));
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
@@ -141,18 +141,18 @@ You can use the [`updateUser()`](/docs/reference/swift/auth-updateuser) method t
|
||||
|
||||
```swift
|
||||
try await supabase.auth.updateUser(
|
||||
user: UserAttributes(email: "example@email.com")
|
||||
user: UserAttributes(email: "valid.email@supabase.io")
|
||||
)
|
||||
```
|
||||
|
||||
</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 {
|
||||
email = "example@email.com"
|
||||
supabase.auth.updateUser {
|
||||
email = "valid.email@supabase.io"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -163,7 +163,7 @@ You can use the [`update_user()`](/docs/reference/python/auth-updateuser) method
|
||||
|
||||
```python
|
||||
response = supabase.auth.update_user({
|
||||
'email': 'example@email.com',
|
||||
'email': 'valid.email@supabase.io',
|
||||
})
|
||||
|
||||
# verify the user's email by clicking on the email change link
|
||||
@@ -274,7 +274,7 @@ const { data: anonData, error: anonError } = await supabase.auth.getSession()
|
||||
|
||||
// 2. Attempt to update the user with the existing email
|
||||
const { data: updateData, error: updateError } = await supabase.auth.updateUser({
|
||||
email: 'existing_user@example.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
})
|
||||
|
||||
// 3. Handle the error (since the email belongs to an existing user)
|
||||
@@ -286,7 +286,7 @@ if (updateError) {
|
||||
data: { user: existingUser },
|
||||
error: signInError,
|
||||
} = await supabase.auth.signInWithPassword({
|
||||
email: 'existing_user@example.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'user_password',
|
||||
})
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ If the user hasn't signed up yet, they are automatically signed up by default. T
|
||||
```js
|
||||
async function signInWithEmail() {
|
||||
const { data, error } = await supabase.auth.signInWithOtp({
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
options: {
|
||||
// set this to false if you do not want the user to be automatically signed up
|
||||
shouldCreateUser: false,
|
||||
@@ -67,7 +67,7 @@ import { makeRedirectUri } from 'expo-auth-session'
|
||||
const redirectTo = makeRedirectUri()
|
||||
|
||||
const { error } = await supabase.auth.signInWithOtp({
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
options: {
|
||||
emailRedirectTo: redirectTo,
|
||||
},
|
||||
@@ -81,7 +81,7 @@ Read the [Deep Linking Documentation](/docs/guides/auth/native-mobile-deep-linki
|
||||
|
||||
```dart
|
||||
Future<void> signInWithEmail() async {
|
||||
final AuthResponse res = await supabase.auth.signinwithotp(email: 'example@email.com');
|
||||
final AuthResponse res = await supabase.auth.signinwithotp(email: 'valid.email@supabase.io');
|
||||
}
|
||||
```
|
||||
|
||||
@@ -90,7 +90,7 @@ Future<void> signInWithEmail() async {
|
||||
|
||||
```swift
|
||||
try await supabase.auth.signInWithOTP(
|
||||
email: "example@email.com",
|
||||
email: "valid.email@supabase.io",
|
||||
redirectTo: URL(string: "https://example.com/welcome"),
|
||||
// set this to false if you do not want the user to be automatically signed up
|
||||
shouldCreateUser: false
|
||||
@@ -103,7 +103,7 @@ try await supabase.auth.signInWithOTP(
|
||||
```kotlin
|
||||
suspend fun signInWithEmail() {
|
||||
supabase.auth.signInWith(OTP) {
|
||||
email = "example@email.com"
|
||||
email = "valid.email@supabase.io"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -113,7 +113,7 @@ suspend fun signInWithEmail() {
|
||||
|
||||
```python
|
||||
response = supabase.auth.sign_in_with_otp({
|
||||
'email': 'example@email.com',
|
||||
'email': 'valid.email@supabase.io',
|
||||
'options': {
|
||||
# set this to false if you do not want the user to be automatically signed up
|
||||
'should_create_user': False,
|
||||
@@ -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
|
||||
|
||||
@@ -181,7 +181,7 @@ If the user hasn't signed up yet, they are automatically signed up by default. T
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.auth.signInWithOtp({
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
options: {
|
||||
// set this to false if you do not want the user to be automatically signed up
|
||||
shouldCreateUser: false,
|
||||
@@ -194,7 +194,7 @@ const { data, error } = await supabase.auth.signInWithOtp({
|
||||
|
||||
```dart
|
||||
Future<void> signInWithEmailOtp() async {
|
||||
final AuthResponse res = await supabase.auth.signInWithOtp(email: 'example@email.com');
|
||||
final AuthResponse res = await supabase.auth.signInWithOtp(email: 'valid.email@supabase.io');
|
||||
}
|
||||
```
|
||||
|
||||
@@ -203,7 +203,7 @@ Future<void> signInWithEmailOtp() async {
|
||||
|
||||
```swift
|
||||
try await supabase.auth.signInWithOTP(
|
||||
email: "example@email.com",
|
||||
email: "valid.email@supabase.io",
|
||||
// set this to false if you do not want the user to be automatically signed up
|
||||
shouldCreateUser: false
|
||||
)
|
||||
@@ -215,7 +215,7 @@ try await supabase.auth.signInWithOTP(
|
||||
```kotlin
|
||||
suspend fun signInWithEmailOtp() {
|
||||
supabase.auth.signInWith(OTP) {
|
||||
email = "example@email.com"
|
||||
email = "valid.email@supabase.io"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -225,7 +225,7 @@ suspend fun signInWithEmailOtp() {
|
||||
|
||||
```python
|
||||
response = supabase.auth.sign_in_with_otp({
|
||||
'email': 'example@email.com',
|
||||
'email': 'valid.email@supabase.io',
|
||||
'options': {
|
||||
# set this to false if you do not want the user to be automatically signed up
|
||||
'should_create_user': False,
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -664,7 +664,7 @@ For authentication methods that have a `redirectTo` or `emailRedirectTo`, this m
|
||||
|
||||
```jsx
|
||||
supabase.auth.signUp({
|
||||
email: 'jon@example.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'sup3rs3cur3',
|
||||
options: {
|
||||
emailRedirectTo: 'http://localhost:3000/auth/callback',
|
||||
|
||||
@@ -1294,7 +1294,7 @@ For authentication methods that have a `redirectTo` or `emailRedirectTo`, this m
|
||||
|
||||
```jsx
|
||||
supabase.auth.signUp({
|
||||
email: 'jon@example.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'sup3rs3cur3',
|
||||
options: {
|
||||
emailRedirectTo: 'http://localhost:3000/auth/callback',
|
||||
|
||||
@@ -613,7 +613,7 @@ export default function Login() {
|
||||
|
||||
const handleEmailLogin = async () => {
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: 'jon@supabase.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'password',
|
||||
})
|
||||
}
|
||||
@@ -651,7 +651,7 @@ export default function Login() {
|
||||
|
||||
const handleEmailLogin = async () => {
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: 'jon@supabase.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'password',
|
||||
})
|
||||
}
|
||||
@@ -826,7 +826,7 @@ For authentication methods that have a `redirectTo` or `emailRedirectTo`, this m
|
||||
|
||||
```jsx
|
||||
supabaseClient.auth.signUp({
|
||||
email: 'jon@example.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'sup3rs3cur3',
|
||||
options: {
|
||||
emailRedirectTo: 'http://localhost:3000/auth/callback',
|
||||
|
||||
@@ -903,7 +903,7 @@ For authentication methods that have a `redirectTo` or `emailRedirectTo`, this m
|
||||
|
||||
```ts
|
||||
await supabase.auth.signUp({
|
||||
email: 'jon@example.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'sup3rs3cur3',
|
||||
options: {
|
||||
emailRedirectTo: 'http://localhost:3000/auth/callback',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -37,14 +37,14 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
|
||||
"id": "8484b834-f29e-4af2-bf42-80644d154f76",
|
||||
"aud": "authenticated",
|
||||
"role": "authenticated",
|
||||
"email": "john@soupbase.io",
|
||||
"email": "valid.email@supabase.io",
|
||||
"phone": "",
|
||||
"app_metadata": {
|
||||
"provider": "email",
|
||||
"providers": ["email"]
|
||||
},
|
||||
"user_metadata": {
|
||||
"email": "john@soupbase.io",
|
||||
"email": "valid.email@supabase.io",
|
||||
"email_verified": false,
|
||||
"phone_verified": false,
|
||||
"sub": "8484b834-f29e-4af2-bf42-80644d154f76"
|
||||
@@ -55,7 +55,7 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
|
||||
"id": "8484b834-f29e-4af2-bf42-80644d154f76",
|
||||
"user_id": "8484b834-f29e-4af2-bf42-80644d154f76",
|
||||
"identity_data": {
|
||||
"email": "john@soupbase.io",
|
||||
"email": "valid.email@supabase.io",
|
||||
"email_verified": false,
|
||||
"phone_verified": false,
|
||||
"sub": "8484b834-f29e-4af2-bf42-80644d154f76"
|
||||
@@ -64,7 +64,7 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
|
||||
"last_sign_in_at": "2024-05-14T12:56:33.824231484Z",
|
||||
"created_at": "2024-05-14T12:56:33.824261Z",
|
||||
"updated_at": "2024-05-14T12:56:33.824261Z",
|
||||
"email": "john@soupbase.io"
|
||||
"email": "valid.email@supabase.io"
|
||||
}
|
||||
],
|
||||
"created_at": "2024-05-14T12:56:33.821567Z",
|
||||
|
||||
@@ -172,3 +172,13 @@ if google_identity:
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
### How to add email/password login to an OAuth account?
|
||||
|
||||
Call the `updateUser({ password: 'validpassword'})` to add email with password authentication to an account created with an OAuth provider (Google, GitHub, etc.).
|
||||
|
||||
### Can you sign up with email if already using OAuth?
|
||||
|
||||
If you try to create an email account after previously signing up with OAuth using the same email, you'll receive an obfuscated user response with no verification email sent. This prevents user enumeration attacks.
|
||||
@@ -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.
|
||||
@@ -80,7 +80,7 @@ Alternatively, you can use the `supabase sso info --project-ref <your-project>`
|
||||
User accounts and identities created via SSO differ from regular (email, phone, password, social login...) accounts in these ways:
|
||||
|
||||
- **No automatic linking.**
|
||||
Each user account verified using a SSO identity provider will not be automatically linked to existing user accounts in the system. That is, if a user `jane.doe@company.com` had signed up with a password, and then uses their company SSO login with your project, there will be two `jane.doe@company.com` user accounts in the system.
|
||||
Each user account verified using a SSO identity provider will not be automatically linked to existing user accounts in the system. That is, if a user `valid.email@supabase.io` had signed up with a password, and then uses their company SSO login with your project, there will be two `valid.email@supabase.io` user accounts in the system.
|
||||
- **Emails are not necessarily unique.**
|
||||
Given the behavior with no automatic linking, email addresses are no longer a unique identifier for a user account. Please always use the user's UUID to correctly reference user accounts.
|
||||
- **Sessions may have a maximum duration.**
|
||||
@@ -288,7 +288,7 @@ Given a SAML 2.0 assertion that includes these attributes:
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
|
||||
>
|
||||
<saml:AttributeValue xsi:type="xs:string">
|
||||
jane.doe@company.com
|
||||
valid.email@supabase.io
|
||||
</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
|
||||
@@ -308,7 +308,7 @@ Will result in the following claims in the user's identity in the database and J
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "jane.doe@company.com",
|
||||
"email": "valid.email@supabase.io",
|
||||
"custom_claims": {
|
||||
"first_name": "Jane Doe"
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ The `user access token` is the JWT issued when you call for example:
|
||||
|
||||
```js
|
||||
supabase.auth.signIn({
|
||||
email: 'lao.gimmie@gov.sg',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'They_Live_1988!',
|
||||
})
|
||||
```
|
||||
@@ -153,7 +153,7 @@ You'll notice that this token is quite a bit longer, since it contains informati
|
||||
"aud": "authenticated",
|
||||
"exp": 1615824388,
|
||||
"sub": "0334744a-f2a2-4aba-8c8a-6e748f62a172",
|
||||
"email": "d.l.solove@gmail.com",
|
||||
"email": "valid.email@supabase.io",
|
||||
"app_metadata": {
|
||||
"provider": "email"
|
||||
},
|
||||
|
||||
@@ -69,7 +69,7 @@ You can assign metadata to users on sign up:
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'example-password',
|
||||
options: {
|
||||
data: {
|
||||
@@ -85,7 +85,7 @@ const { data, error } = await supabase.auth.signUp({
|
||||
|
||||
```dart
|
||||
final res = await supabase.auth.signUp(
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'example-password',
|
||||
data: {
|
||||
'first_name': 'John',
|
||||
@@ -99,7 +99,7 @@ final res = await supabase.auth.signUp(
|
||||
|
||||
```swift
|
||||
try await supabase.auth.signUp(
|
||||
email: "example@email.com",
|
||||
email: "valid.email@supabase.io",
|
||||
password: "example-password",
|
||||
data: [
|
||||
"first_name": .string("John"),
|
||||
@@ -113,7 +113,7 @@ try await supabase.auth.signUp(
|
||||
|
||||
```kotlin
|
||||
val data = supabase.auth.signUpWith(Email) {
|
||||
email = "example@email.com"
|
||||
email = "valid.email@supabase.io"
|
||||
password = "example-password"
|
||||
data = buildJsonObject {
|
||||
put("first_name", "John")
|
||||
|
||||
@@ -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
|
||||
@@ -85,7 +85,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This
|
||||
|
||||
const sendMagicLink = async () => {
|
||||
const { error } = await supabase.auth.signInWithOtp({
|
||||
email: "example@email.com",
|
||||
email: "valid.email@supabase.io",
|
||||
options: {
|
||||
emailRedirectTo: redirectTo,
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ If you don't specify a redirect URL, the user is automatically redirected to you
|
||||
```js
|
||||
async function signUpNewUser() {
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'example-password',
|
||||
options: {
|
||||
emailRedirectTo: 'https://example.com/welcome',
|
||||
@@ -75,7 +75,7 @@ To sign up the user, call [signUp()](/docs/reference/dart/auth-signup) with thei
|
||||
```dart
|
||||
Future<void> signUpNewUser() async {
|
||||
final AuthResponse res = await supabase.auth.signUp(
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'example-password'
|
||||
);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ If you don't specify a redirect URL, the user is automatically redirected to you
|
||||
|
||||
```swift
|
||||
let response = try await supabase.auth.signUp(
|
||||
email: "example@email.com",
|
||||
email: "valid.email@supabase.io",
|
||||
password: "example-password",
|
||||
redirectTo: URL(string: "https://example.com/welcome")
|
||||
)
|
||||
@@ -106,7 +106,7 @@ To sign up the user, call [signUpWith(Email)](/docs/reference/kotlin/auth-signup
|
||||
```kotlin
|
||||
suspend fun signUpNewUser() {
|
||||
supabase.auth.signUpWith(Email) {
|
||||
email = "example@email.com"
|
||||
email = "valid.email@supabase.io"
|
||||
password = "example-password"
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ If you don't specify a redirect URL, the user is automatically redirected to you
|
||||
|
||||
```python
|
||||
data = await supabase.auth.sign_up({
|
||||
'email': 'example@email.com',
|
||||
'email': 'valid.email@supabase.io',
|
||||
'password': 'example-password',
|
||||
'options': {
|
||||
'email_redirect_to': 'https://example.com/welcome',
|
||||
@@ -387,7 +387,7 @@ If you don't specify a redirect URL, the user is automatically redirected to you
|
||||
```js
|
||||
async function signUpNewUser() {
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'example-password',
|
||||
options: {
|
||||
emailRedirectTo: 'https://example.com/welcome',
|
||||
@@ -404,7 +404,7 @@ To sign up the user, call [signUp()](/docs/reference/dart/auth-signup) with thei
|
||||
```dart
|
||||
Future<void> signUpNewUser() async {
|
||||
final AuthResponse res = await supabase.auth.signUp(
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'example-password'
|
||||
);
|
||||
}
|
||||
@@ -417,7 +417,7 @@ To sign up the user, call [signUp()](/docs/reference/swift/auth-signup) with the
|
||||
|
||||
```swift
|
||||
let response = try await supabase.auth.signUp(
|
||||
email: "example@email.com",
|
||||
email: "valid.email@supabase.io",
|
||||
password: "example-password",
|
||||
)
|
||||
```
|
||||
@@ -430,7 +430,7 @@ To sign up the user, call [signUpWith(Email)](/docs/reference/kotlin/auth-signup
|
||||
```kotlin
|
||||
suspend fun signUpNewUser() {
|
||||
supabase.auth.signUpWith(Email) {
|
||||
email = "example@email.com"
|
||||
email = "valid.email@supabase.io"
|
||||
password = "example-password"
|
||||
}
|
||||
}
|
||||
@@ -443,7 +443,7 @@ To sign up the user, call [signUp()](/docs/reference/python/auth-signup) with th
|
||||
|
||||
```python
|
||||
data = supabase.auth.sign_up({
|
||||
'email': 'example@email.com',
|
||||
'email': 'valid.email@supabase.io',
|
||||
'password': 'example-password',
|
||||
})
|
||||
```
|
||||
@@ -470,7 +470,7 @@ When your user signs in, call [signInWithPassword()](/docs/reference/javascript/
|
||||
```js
|
||||
async function signInWithEmail() {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'example-password',
|
||||
})
|
||||
}
|
||||
@@ -484,7 +484,7 @@ When your user signs in, call [signInWithPassword()](/docs/reference/dart/auth-s
|
||||
```dart
|
||||
Future<void> signInWithEmail() async {
|
||||
final AuthResponse res = await supabase.auth.signInWithPassword(
|
||||
email: 'example@email.com',
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'example-password'
|
||||
);
|
||||
}
|
||||
@@ -497,7 +497,7 @@ When your user signs in, call [signIn(email:password:)](/docs/reference/swift/au
|
||||
|
||||
```swift
|
||||
try await supabase.auth.signIn(
|
||||
email: "example@email.com",
|
||||
email: "valid.email@supabase.io",
|
||||
password: "example-password"
|
||||
)
|
||||
```
|
||||
@@ -510,7 +510,7 @@ When your user signs in, call [signInWith(Email)](/docs/reference/kotlin/auth-si
|
||||
```kotlin
|
||||
suspend fun signInWithEmail() {
|
||||
supabase.auth.signInWith(Email) {
|
||||
email = "example@email.com"
|
||||
email = "valid.email@supabase.io"
|
||||
password = "example-password"
|
||||
}
|
||||
}
|
||||
@@ -523,7 +523,7 @@ When your user signs in, call [sign_in_with_password()](/docs/reference/python/a
|
||||
|
||||
```python
|
||||
data = client.auth.sign_in_with_password({
|
||||
'email': 'example@email.com',
|
||||
'email': 'valid.email@supabase.io',
|
||||
'password': 'example-password',
|
||||
})
|
||||
```
|
||||
@@ -557,7 +557,7 @@ Collect the user's email address and request a password reset email. Specify the
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
await supabase.auth.resetPasswordForEmail('hello@example.com', {
|
||||
await supabase.auth.resetPasswordForEmail('valid.email@supabase.io', {
|
||||
redirectTo: 'http://example.com/account/update-password',
|
||||
})
|
||||
```
|
||||
@@ -567,7 +567,7 @@ await supabase.auth.resetPasswordForEmail('hello@example.com', {
|
||||
|
||||
```swift
|
||||
try await supabase.auth.resetPasswordForEmail(
|
||||
"hello@example.com",
|
||||
"valid.email@supabase.io",
|
||||
redirectTo: URL(string: "http://example.com/account/update-password")
|
||||
)
|
||||
```
|
||||
@@ -577,7 +577,7 @@ try await supabase.auth.resetPasswordForEmail(
|
||||
|
||||
```kotlin
|
||||
supabase.gotrue.sendRecoveryEmail(
|
||||
email = "hello@example.com",
|
||||
email = "valid.email@supabase.io",
|
||||
redirectUrl = "http://example.com/account/update-password"
|
||||
)
|
||||
```
|
||||
@@ -589,7 +589,7 @@ If you are on one of the Kotlin targets that have built-in support for redirect
|
||||
|
||||
```python
|
||||
client.auth.reset_password_email(
|
||||
'hello@example.com',
|
||||
'valid.email@supabase.io',
|
||||
{'redirect_to':'http://example.com/account/update-password'}
|
||||
)
|
||||
```
|
||||
@@ -859,7 +859,7 @@ async function resetPassword() {
|
||||
<TabPanel id="swift" label="Swift">
|
||||
|
||||
```swift
|
||||
try await supabase.auth.resetPasswordForEmail("hello@example.com")
|
||||
try await supabase.auth.resetPasswordForEmail("valid.email@supabase.io")
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
@@ -867,7 +867,7 @@ try await supabase.auth.resetPasswordForEmail("hello@example.com")
|
||||
|
||||
```kotlin
|
||||
supabase.gotrue.sendRecoveryEmail(
|
||||
email = "hello@example.com",
|
||||
email = "valid.email@supabase.io",
|
||||
)
|
||||
```
|
||||
|
||||
@@ -875,7 +875,7 @@ supabase.gotrue.sendRecoveryEmail(
|
||||
<TabPanel id="python" label="Python">
|
||||
|
||||
```python
|
||||
supabase.auth.reset_password_email('hello@example.com')
|
||||
supabase.auth.reset_password_email('valid.email@supabase.io')
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -247,10 +247,12 @@ export async function updateSession(request: NextRequest) {
|
||||
}
|
||||
)
|
||||
|
||||
// IMPORTANT: Avoid writing any logic between createServerClient and
|
||||
// Do not run code between createServerClient and
|
||||
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
|
||||
// issues with users being randomly logged out.
|
||||
|
||||
// IMPORTANT: DO NOT REMOVE auth.getUser()
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
@@ -266,8 +268,8 @@ export async function updateSession(request: NextRequest) {
|
||||
return NextResponse.redirect(url)
|
||||
}
|
||||
|
||||
// IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
|
||||
// creating a new response object with NextResponse.next() make sure to:
|
||||
// IMPORTANT: You *must* return the supabaseResponse object as it is.
|
||||
// If you're creating a new response object with NextResponse.next() make sure to:
|
||||
// 1. Pass the request in it, like so:
|
||||
// const myNewResponse = NextResponse.next({ request })
|
||||
// 2. Copy over the cookies, like so:
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -166,9 +166,9 @@ To use Google's pre-built signin buttons:
|
||||
|
||||
### Configuration [#ios-configuration]
|
||||
|
||||
1. Follow the integration instructions on the [get started with Google Sign-In](https://developers.google.com/identity/sign-in/ios/start-integrating) for iOS guide.
|
||||
1. Follow the integration instructions on the [get started with Google Sign-In](https://developers.google.com/identity/sign-in/ios/start-integrating) for the iOS guide.
|
||||
2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service.
|
||||
3. Add only the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_. Enable the `Skip nonce check` option.
|
||||
3. Add web client ID and iOS client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_, separated by a comma. Enable the `Skip nonce check` option.
|
||||
|
||||
</TabPanel>
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
@@ -70,11 +70,10 @@ The usename can be used to identify the source:
|
||||
|
||||
| Role | API/Tool |
|
||||
| ---------------------------- | ------------------------------------------------------------------------- |
|
||||
| supabase_admin | Used by Supabase to configure projects and for monitoring |
|
||||
| supabase_admin | Used by Supabase for monitoring and by Realtime |
|
||||
| authenticator | Data API (PostgREST) |
|
||||
| supabase_auth_admin | Auth |
|
||||
| supabase_storage_admin | Storage |
|
||||
| supabase_realtime_admin | Realtime |
|
||||
| supabase_replication_admin | Synchronizes Read Replicas |
|
||||
| postgres | Supabase Dashboard and External Tools (e.g., Prisma, SQLAlchemy, PSQL...) |
|
||||
| Custom roles defined by user | External Tools (e.g., Prisma, SQLAlchemy, PSQL...) |
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||
@@ -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.
|
||||
+3
-3
@@ -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:
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
|
||||
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
|
||||
|
||||
```js src/supabaseClient.jsx
|
||||
```tsx src/supabaseClient.tsx
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
||||
|
||||
@@ -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
|
||||
|
||||
Loaded 100 of 937 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user