mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 06:39:09 +03:00
Merge branch 'master' into jgoux/feat-gitlab-integration-step-1
This commit is contained in:
893 files changed
+91591
-58206
No files matched your search
@@ -36,7 +36,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -26,7 +26,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
@@ -29,7 +29,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Setup node
|
||||
|
||||
@@ -38,10 +38,10 @@ jobs:
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: 6435a4cd1eeea7c2bbd343731de7e8a5127cb2d1
|
||||
key: 0be447eba738c3fd56698eff8ad01fa56c2391d7
|
||||
- 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
|
||||
run: cargo install --locked --git https://github.com/supabase-community/supa-mdx-lint --rev 0be447eba738c3fd56698eff8ad01fa56c2391d7
|
||||
- name: install reviewdog
|
||||
uses: reviewdog/action-setup@3f401fe1d58fe77e10d665ab713057375e39b887 # v1.3.0
|
||||
with:
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
run: |
|
||||
set -o pipefail
|
||||
git diff --name-only origin/$BASE_REF HEAD \
|
||||
| { grep -E "^apps/docs/content/guides/" || test $? = 1; } \
|
||||
| { grep -E "^apps/docs/content/" || test $? = 1; } \
|
||||
| xargs -r supa-mdx-lint --format rdf \
|
||||
| reviewdog -f=rdjsonl -reporter=github-pr-review
|
||||
- name: run linter on push or workflow dispatch
|
||||
|
||||
@@ -20,7 +20,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
|
||||
@@ -25,7 +25,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
|
||||
@@ -26,7 +26,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
|
||||
@@ -28,7 +28,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -21,7 +21,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -47,7 +46,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
@@ -73,7 +71,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Setup node
|
||||
|
||||
@@ -34,7 +34,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -22,7 +22,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
|
||||
@@ -24,7 +24,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
|
||||
@@ -24,7 +24,6 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 9.10.0
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
|
||||
@@ -1919,6 +1919,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-combobox-creatable": {
|
||||
name: "multi-select-combobox-creatable",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-combobox-creatable")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-combobox-creatable.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-deletable-badge": {
|
||||
name: "multi-select-deletable-badge",
|
||||
type: "components:example",
|
||||
|
||||
@@ -349,6 +349,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/components/slider',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Sidebar',
|
||||
href: '/docs/components/sidebar',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Sonner',
|
||||
href: '/docs/components/sonner',
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -47,6 +47,13 @@ Use `MultiSelectorInput`to add a search input.
|
||||
|
||||
<ComponentPreview name="multi-select-combobox" />
|
||||
|
||||
### Badge Combo Box with creatable selector
|
||||
|
||||
creatable: `boolean`
|
||||
`creatable` prop on the `MultiSelectorList` component can be used to add a new option if no items match
|
||||
|
||||
<ComponentPreview name="multi-select-combobox-creatable" />
|
||||
|
||||
### Badge Limit
|
||||
|
||||
badgeLimit: `number` | `"wrap"`.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import * as React from 'react'
|
||||
import { Calendar, MoreHorizontal, Tags, Trash, User } from 'lucide-react'
|
||||
|
||||
import { Button, CommandList_Shadcn_ } from 'ui'
|
||||
import { Button, CommandList_Shadcn_, ScrollArea } from 'ui'
|
||||
import {
|
||||
Command_Shadcn_,
|
||||
CommandEmpty_Shadcn_,
|
||||
@@ -75,23 +75,25 @@ export default function ComboboxDropdownMenu() {
|
||||
<DropdownMenuSubContent className="p-0">
|
||||
<Command_Shadcn_>
|
||||
<CommandInput_Shadcn_ placeholder="Filter label..." autoFocus={true} />
|
||||
<CommandList_Shadcn_>
|
||||
<CommandEmpty_Shadcn_>No label found.</CommandEmpty_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
{labels.map((label) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={label}
|
||||
value={label}
|
||||
onSelect={(value) => {
|
||||
setLabel(value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
<ScrollArea className="h-20">
|
||||
<CommandList_Shadcn_>
|
||||
<CommandEmpty_Shadcn_>No label found.</CommandEmpty_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
{labels.map((label) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={label}
|
||||
value={label}
|
||||
onSelect={(value) => {
|
||||
setLabel(value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
</ScrollArea>
|
||||
</Command_Shadcn_>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger className="w-72" label="Select fruits" badgeLimit="wrap" />
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorInput placeholder="Search fruits" showResetIcon />
|
||||
<MultiSelectorList creatable>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -1091,6 +1091,11 @@ export const examples: Registry = [
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-combobox.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-combobox-creatable',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-combobox-creatable.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-deletable-badge',
|
||||
type: 'components:example',
|
||||
|
||||
@@ -8,7 +8,8 @@ module.exports = config({
|
||||
// purge styles from grid library
|
||||
//
|
||||
'./../../packages/ui/src/**/*.{tsx,ts,js}',
|
||||
'./../../packages/ui-patterns/!(node_modules)/**/*.{tsx,ts,js}',
|
||||
'./../../packages/ui-patterns/**/*.{tsx,ts,js}',
|
||||
'!./../../packages/ui-patterns/node_modules/**/*', // ignore node_modules rule
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import matter from 'gray-matter'
|
||||
import type { Code, Heading, Root } from 'mdast'
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
import { toMarkdown } from 'mdast-util-to-markdown'
|
||||
@@ -11,7 +12,8 @@ import { EXAMPLES_DIRECTORY } from '~/lib/docs'
|
||||
const PROMPTS_DIRECTORY = join(EXAMPLES_DIRECTORY, 'prompts')
|
||||
|
||||
function parseMarkdown(markdown: string) {
|
||||
const mdast = fromMarkdown(markdown)
|
||||
const { content: withoutFrontmatter } = matter(markdown)
|
||||
const mdast = fromMarkdown(withoutFrontmatter)
|
||||
|
||||
let heading = ''
|
||||
visit(mdast, 'heading', (node: Heading) => {
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
redirectNonexistentReferenceSection,
|
||||
} from '~/features/docs/Reference.utils'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
export default async function ReferencePage({
|
||||
params: { slug },
|
||||
}: {
|
||||
@@ -39,7 +41,6 @@ export default async function ReferencePage({
|
||||
|
||||
return <ClientSdkReferencePage sdkId={sdkId} libVersion={version} />
|
||||
} else if (isCliReference) {
|
||||
console.log('Returning CLI reference page: %o', parsedPath)
|
||||
return <CliReferencePage />
|
||||
} else if (isApiReference) {
|
||||
return <ApiReferencePage />
|
||||
|
||||
@@ -109,15 +109,15 @@ const BreadcrumbsInternal = ({
|
||||
</DrawerTrigger>
|
||||
<DrawerContent showHandle={false}>
|
||||
<div className="grid gap-1 px-4">
|
||||
{breadcrumbs
|
||||
.slice(1, -2)
|
||||
.map((crumb) =>
|
||||
crumb.url ? (
|
||||
<Link href={crumb.url}>{crumb.title || crumb.name}</Link>
|
||||
) : (
|
||||
crumb.title || crumb.name
|
||||
)
|
||||
)}
|
||||
{breadcrumbs.slice(1, -2).map((crumb, index) =>
|
||||
crumb.url ? (
|
||||
<Link key={index} href={crumb.url}>
|
||||
{crumb.title || crumb.name}
|
||||
</Link>
|
||||
) : (
|
||||
crumb.title || crumb.name
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<DrawerFooter className="pt-4">
|
||||
<DrawerClose asChild>
|
||||
@@ -131,12 +131,12 @@ const BreadcrumbsInternal = ({
|
||||
<BreadcrumbSeparator />
|
||||
</>
|
||||
)}
|
||||
{appendedBreadcrumbs?.map((crumb, i) => (
|
||||
<Fragment key={crumb.url}>
|
||||
{appendedBreadcrumbs?.map((crumb, index) => (
|
||||
<Fragment key={index}>
|
||||
<BreadcrumbItem
|
||||
className={cn(
|
||||
'flex items-center overflow-hidden',
|
||||
i === appendedBreadcrumbs.length - 1 && 'md:text-foreground-light'
|
||||
index === appendedBreadcrumbs.length - 1 && 'md:text-foreground-light'
|
||||
)}
|
||||
>
|
||||
{crumb.url ? (
|
||||
@@ -148,7 +148,7 @@ const BreadcrumbsInternal = ({
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator
|
||||
className={cn(i === appendedBreadcrumbs.length - 1 && 'md:hidden')}
|
||||
className={cn(index === appendedBreadcrumbs.length - 1 && 'md:hidden')}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
@@ -170,7 +170,8 @@ function useBreadcrumbs() {
|
||||
if (isAiPromptsPage) {
|
||||
const breadcrumbs = [
|
||||
{ name: 'Getting started', url: '/guides/getting-started' },
|
||||
{ name: 'AI Prompts', url: '/guides/getting-started/ai-prompts' },
|
||||
{ name: 'AI Tools' },
|
||||
{ name: 'Prompts', url: '/guides/getting-started/ai-prompts' },
|
||||
]
|
||||
return breadcrumbs
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ const HomePageCover = (props) => {
|
||||
href: '/guides/getting-started/quickstarts/vue',
|
||||
},
|
||||
{
|
||||
tooltip: 'NuxtJS',
|
||||
tooltip: 'Nuxt',
|
||||
icon: '/docs/img/icons/nuxt-icon',
|
||||
href: '/guides/getting-started/quickstarts/nuxtjs',
|
||||
},
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
There is no fixed fee for a Preview branch. You only pay for the usage it incurs. With Compute costs of $0.01344 per hour, a branch running on Micro Compute size starts at $0.32 per day.
|
||||
@@ -0,0 +1,8 @@
|
||||
$2 per 1 million invocations. You are only charged for usage exceeding your subscription plan's quota.
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | --------- | ---------------------------- |
|
||||
| Free | 500,000 | - |
|
||||
| Pro | 2 million | $2 per 1 million invocations |
|
||||
| Team | 2 million | $2 per 1 million invocations |
|
||||
| Enterprise | Custom | Custom |
|
||||
@@ -0,0 +1 @@
|
||||
$0.00325 per MAU. You are only charged for usage exceeding your subscription plan's quota.
|
||||
@@ -0,0 +1 @@
|
||||
$0.015 per SSO MAU. You are only charged for usage exceeding your subscription plan's quota.
|
||||
@@ -0,0 +1 @@
|
||||
$0.00325 per Third-Party MAU. You are only charged for usage exceeding your subscription plan's quota.
|
||||
@@ -0,0 +1,7 @@
|
||||
$0.1027 per hour ($75 per month) for the first project. $0.0137 per hour ($10 per month) for every additional project.
|
||||
|
||||
| Plan | Project 1 per month | Project 2 per month | Project 3 per month |
|
||||
| ---------- | ------------------- | ------------------- | ------------------- |
|
||||
| Pro | $75 | $10 | $10 |
|
||||
| Team | $75 | $10 | $10 |
|
||||
| Enterprise | Custom | Custom | Custom |
|
||||
@@ -0,0 +1,7 @@
|
||||
Pricing depends on the recovery retention period, which determines how many days back you can restore data to any chosen point of up to seconds in granularity.
|
||||
|
||||
| Recovery Retention Period in Days | Hourly Price USD | Monthly Price USD |
|
||||
| --------------------------------- | ---------------- | ----------------- |
|
||||
| 7 | $0.137 | $100 |
|
||||
| 14 | $0.274 | $200 |
|
||||
| 28 | $0.55 | $400 |
|
||||
@@ -0,0 +1,8 @@
|
||||
$10 per 1,000 peak connections. You are only charged for usage exceeding your subscription plan's quota.
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------ | ------------------------------ |
|
||||
| Free | 200 | - |
|
||||
| Pro | 500 | $10 per 1,000 peak connections |
|
||||
| Team | 500 | $10 per 1,000 peak connections |
|
||||
| Enterprise | Custom | Custom |
|
||||
@@ -0,0 +1,8 @@
|
||||
$2.50 per 1 million messages. You are only charged for usage exceeding your subscription plan's quota.
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | --------- | ---------------------------- |
|
||||
| Free | 2 million | - |
|
||||
| Pro | 5 million | $2.50 per 1 million messages |
|
||||
| Team | 5 million | $2.50 per 1 million messages |
|
||||
| Enterprise | Custom | Custom |
|
||||
@@ -0,0 +1,13 @@
|
||||
$5 per 1,000 origin images. You are only charged for usage exceeding your subscription plan's quota.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
The count resets at the start of each billing cycle.
|
||||
|
||||
</Admonition>
|
||||
|
||||
| Plan | Quota | Over-Usage |
|
||||
| ---------- | ------ | -------------------------- |
|
||||
| Pro | 100 | $5 per 1,000 origin images |
|
||||
| Team | 100 | $5 per 1,000 origin images |
|
||||
| Enterprise | Custom | Custom |
|
||||
@@ -0,0 +1,8 @@
|
||||
$0.00002919 per GB-Hr ($0.021 per GB per month). You are only charged for usage exceeding your subscription plan's quota.
|
||||
|
||||
| Plan | Quota in GB | Over-Usage per GB | Quota in GB-Hrs | Over-Usage per GB-Hrs |
|
||||
| ---------- | ----------- | ----------------- | --------------- | --------------------- |
|
||||
| Free | 1 | - | 744 | - |
|
||||
| Pro | 100 | $0.021 | 74,400 | $0.00002919 |
|
||||
| Team | 100 | $0.021 | 74,400 | $0.00002919 |
|
||||
| Enterprise | Custom | Custom | Custom | Custom |
|
||||
@@ -15,6 +15,17 @@ import QuickstartIntro from './quickstart_intro.mdx'
|
||||
import SocialProviderSettingsSupabase from './social_provider_settings_supabase.mdx'
|
||||
import SocialProviderSetup from './social_provider_setup.mdx'
|
||||
import PostgresInstallation from './postgres_installation.mdx'
|
||||
import PricingMfaPhone from './billing/pricing/pricing_mfa_phone.mdx'
|
||||
import PricingBranching from './billing/pricing/pricing_branching.mdx'
|
||||
import PricingEdgeFunctions from './billing/pricing/pricing_edge_functions.mdx'
|
||||
import PricingMau from './billing/pricing/pricing_mau.mdx'
|
||||
import PricingMauSso from './billing/pricing/pricing_mau_sso.mdx'
|
||||
import PricingMauThirdParty from './billing/pricing/pricing_mau_third_party.mdx'
|
||||
import PricingPitr from './billing/pricing/pricing_pitr.mdx'
|
||||
import PricingRealtimeMessages from './billing/pricing/pricing_realtime_messages.mdx'
|
||||
import PricingRealtimeConnections from './billing/pricing/pricing_realtime_connections.mdx'
|
||||
import PricingStorageImageTransformations from './billing/pricing/pricing_storage_image_transformations.mdx'
|
||||
import PricingStorageSize from './billing/pricing/pricing_storage_size.mdx'
|
||||
|
||||
export {
|
||||
AuthErrorCodesTable,
|
||||
@@ -32,4 +43,15 @@ export {
|
||||
SocialProviderSettingsSupabase,
|
||||
SocialProviderSetup,
|
||||
PostgresInstallation,
|
||||
PricingMfaPhone,
|
||||
PricingBranching,
|
||||
PricingEdgeFunctions,
|
||||
PricingMau,
|
||||
PricingMauSso,
|
||||
PricingMauThirdParty,
|
||||
PricingPitr,
|
||||
PricingRealtimeMessages,
|
||||
PricingRealtimeConnections,
|
||||
PricingStorageImageTransformations,
|
||||
PricingStorageSize,
|
||||
}
|
||||
@@ -258,7 +258,7 @@ export const gettingstarted: NavMenuConstant = {
|
||||
items: [
|
||||
{ name: 'Next.js', url: '/guides/getting-started/quickstarts/nextjs' },
|
||||
{ name: 'React', url: '/guides/getting-started/quickstarts/reactjs' },
|
||||
{ name: 'NuxtJS', url: '/guides/getting-started/quickstarts/nuxtjs' },
|
||||
{ name: 'Nuxt', url: '/guides/getting-started/quickstarts/nuxtjs' },
|
||||
{ name: 'Vue', url: '/guides/getting-started/quickstarts/vue' },
|
||||
{ name: 'Hono', url: '/guides/getting-started/quickstarts/hono' },
|
||||
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter' },
|
||||
@@ -366,13 +366,17 @@ export const gettingstarted: NavMenuConstant = {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'AI Prompts',
|
||||
name: 'AI Tools',
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'Overview',
|
||||
name: 'Prompts',
|
||||
url: '/guides/getting-started/ai-prompts',
|
||||
},
|
||||
{
|
||||
name: 'Model context protocol (MCP)',
|
||||
url: '/guides/getting-started/mcp',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -1009,10 +1013,6 @@ export const database: NavMenuConstant = {
|
||||
name: 'pg_plan_filter: Restrict Total Cost',
|
||||
url: '/guides/database/extensions/pg_plan_filter',
|
||||
},
|
||||
{
|
||||
name: 'pg_stat_monitor: Extended Query Performance Monitoring',
|
||||
url: '/guides/database/extensions/pg_stat_monitor',
|
||||
},
|
||||
{
|
||||
name: 'postgres_fdw: query data from an external Postgres server',
|
||||
url: '/guides/database/extensions/postgres_fdw',
|
||||
@@ -1415,6 +1415,10 @@ export const functions: NavMenuConstant = {
|
||||
name: 'Limits',
|
||||
url: '/guides/functions/limits',
|
||||
},
|
||||
{
|
||||
name: 'Pricing',
|
||||
url: '/guides/functions/pricing',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1583,6 +1587,7 @@ export const realtime: NavMenuConstant = {
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Quotas', url: '/guides/realtime/quotas' },
|
||||
{ name: 'Pricing', url: '/guides/realtime/pricing' },
|
||||
{ name: 'Architecture', url: '/guides/realtime/architecture' },
|
||||
{ name: 'Message Protocol', url: '/guides/realtime/protocol', items: [] },
|
||||
{ name: 'Benchmarks', url: '/guides/realtime/benchmarks' },
|
||||
@@ -1668,6 +1673,7 @@ export const storage: NavMenuConstant = {
|
||||
items: [
|
||||
{ name: 'Copy / Move Objects', url: '/guides/storage/management/copy-move-objects' },
|
||||
{ name: 'Delete Objects', url: '/guides/storage/management/delete-objects' },
|
||||
{ name: 'Pricing', url: '/guides/storage/management/pricing' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1747,6 +1753,10 @@ export const ai = {
|
||||
url: '/guides/ai/vector-indexes',
|
||||
items: vectorIndexItems,
|
||||
},
|
||||
{
|
||||
name: 'Automatic embeddings',
|
||||
url: '/guides/ai/automatic-embeddings',
|
||||
},
|
||||
{
|
||||
name: 'Engineering for scale',
|
||||
url: '/guides/ai/engineering-for-scale',
|
||||
@@ -1920,6 +1930,22 @@ export const local_development: NavMenuConstant = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Testing',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Getting started', url: '/guides/local-development/testing/overview' },
|
||||
{
|
||||
name: 'pgTAP advanced guide',
|
||||
url: '/guides/local-development/testing/pgtap-extended',
|
||||
},
|
||||
{ name: 'Database testing', url: '/guides/database/testing' },
|
||||
{
|
||||
name: 'RLS policies testing',
|
||||
url: '/guides/database/extensions/pgtap#testing-rls-policies',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -2083,12 +2109,114 @@ export const platform: NavMenuConstant = {
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'How billing works',
|
||||
url: '/guides/platform/org-based-billing',
|
||||
name: 'About billing on Supabase',
|
||||
url: '/guides/platform/billing-on-supabase',
|
||||
},
|
||||
{
|
||||
name: 'Spend caps and cost-controls',
|
||||
url: '/guides/platform/spend-cap',
|
||||
name: 'Get set up for billing',
|
||||
url: '/guides/platform/get-set-up-for-billing',
|
||||
},
|
||||
{
|
||||
name: 'Manage your subscription',
|
||||
url: '/guides/platform/manage-your-subscription',
|
||||
},
|
||||
{
|
||||
name: 'Manage your usage',
|
||||
url: '/guides/platform/manage-your-usage',
|
||||
items: [
|
||||
{
|
||||
name: 'Compute',
|
||||
url: '/guides/platform/manage-your-usage/compute',
|
||||
},
|
||||
{
|
||||
name: 'Egress',
|
||||
url: '/guides/platform/manage-your-usage/egress',
|
||||
},
|
||||
{
|
||||
name: 'Disk Size',
|
||||
url: '/guides/platform/manage-your-usage/disk-size',
|
||||
},
|
||||
{
|
||||
name: 'Disk Throughput',
|
||||
url: '/guides/platform/manage-your-usage/disk-throughput',
|
||||
},
|
||||
{
|
||||
name: 'Disk IOPS',
|
||||
url: '/guides/platform/manage-your-usage/disk-iops',
|
||||
},
|
||||
{
|
||||
name: 'Monthly Active Users',
|
||||
url: '/guides/platform/manage-your-usage/monthly-active-users',
|
||||
},
|
||||
{
|
||||
name: 'Monthly Active Third-Party Users',
|
||||
url: '/guides/platform/manage-your-usage/monthly-active-users-third-party',
|
||||
},
|
||||
{
|
||||
name: 'Monthly Active SSO Users',
|
||||
url: '/guides/platform/manage-your-usage/monthly-active-users-sso',
|
||||
},
|
||||
{
|
||||
name: 'Storage Size',
|
||||
url: '/guides/platform/manage-your-usage/storage-size',
|
||||
},
|
||||
{
|
||||
name: 'Storage Image Transformations',
|
||||
url: '/guides/platform/manage-your-usage/storage-image-transformations',
|
||||
},
|
||||
{
|
||||
name: 'Edge Function Invocations',
|
||||
url: '/guides/platform/manage-your-usage/edge-function-invocations',
|
||||
},
|
||||
{
|
||||
name: 'Realtime Messages',
|
||||
url: '/guides/platform/manage-your-usage/realtime-messages',
|
||||
},
|
||||
{
|
||||
name: 'Realtime Peak Connections',
|
||||
url: '/guides/platform/manage-your-usage/realtime-peak-connections',
|
||||
},
|
||||
{
|
||||
name: 'Custom Domains',
|
||||
url: '/guides/platform/manage-your-usage/custom-domains',
|
||||
},
|
||||
{
|
||||
name: 'Point-in-Time Recovery',
|
||||
url: '/guides/platform/manage-your-usage/point-in-time-recovery',
|
||||
},
|
||||
{
|
||||
name: 'IPv4',
|
||||
url: '/guides/platform/manage-your-usage/ipv4',
|
||||
},
|
||||
{
|
||||
name: 'MFA Phone',
|
||||
url: '/guides/platform/manage-your-usage/advanced-mfa-phone',
|
||||
},
|
||||
{
|
||||
name: 'Read Replicas',
|
||||
url: '/guides/platform/manage-your-usage/read-replicas',
|
||||
},
|
||||
{
|
||||
name: 'Branching',
|
||||
url: '/guides/platform/manage-your-usage/branching',
|
||||
},
|
||||
{
|
||||
name: 'Log Drains',
|
||||
url: '/guides/platform/manage-your-usage/log-drains',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Your monthly invoice',
|
||||
url: '/guides/platform/your-monthly-invoice',
|
||||
},
|
||||
{
|
||||
name: 'Control your costs',
|
||||
url: '/guides/platform/cost-control',
|
||||
},
|
||||
{
|
||||
name: 'Credits',
|
||||
url: '/guides/platform/credits',
|
||||
},
|
||||
{
|
||||
name: 'Billing FAQ',
|
||||
|
||||
@@ -39,19 +39,36 @@ const NavigationMenuGuideList = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Inject federated prompts into the 'AI Tools > Prompts' section
|
||||
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),
|
||||
],
|
||||
const aiToolsSectionIndex = menu.items.findIndex((item) => item.name === 'AI Tools')
|
||||
if (aiToolsSectionIndex !== -1) {
|
||||
const beforeAITools = menu.items.slice(0, aiToolsSectionIndex)
|
||||
const afterAITools = menu.items.slice(aiToolsSectionIndex + 1)
|
||||
|
||||
const aiToolsSection = menu.items[aiToolsSectionIndex]
|
||||
const promptsSectionIndex = aiToolsSection.items.findIndex((item) => item.name === 'Prompts')
|
||||
|
||||
if (promptsSectionIndex !== -1) {
|
||||
const beforePrompts = aiToolsSection.items.slice(0, promptsSectionIndex)
|
||||
const afterPrompts = aiToolsSection.items.slice(promptsSectionIndex + 1)
|
||||
|
||||
const promptsSection = aiToolsSection.items[promptsSectionIndex]
|
||||
|
||||
const modifiedPromptsSection = {
|
||||
...promptsSection,
|
||||
items: additionalNavItems.prompts,
|
||||
}
|
||||
|
||||
const modifiedAIToolsSection = {
|
||||
...aiToolsSection,
|
||||
items: [...beforePrompts, modifiedPromptsSection, ...afterPrompts],
|
||||
}
|
||||
|
||||
menu = {
|
||||
...menu,
|
||||
items: [...beforeAITools, modifiedAIToolsSection, ...afterAITools],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,817 @@
|
||||
---
|
||||
id: 'ai-automatic-embeddings'
|
||||
title: 'Automatic embeddings'
|
||||
description: 'Automate embedding generation and updates in Postgres'
|
||||
sidebar_label: 'Automatic embeddings'
|
||||
---
|
||||
|
||||
Vector embeddings enable powerful [semantic search](/docs/guides/ai/semantic-search) capabilities in Postgres, but managing them alongside your content has traditionally been complex. This guide demonstrates how to automate embedding generation and updates using Supabase [Edge Functions](/docs/guides/functions), [pgmq](/docs/guides/database/extensions/pgmq), [pg_net](/docs/guides/database/extensions/pg_net), and [pg_cron](/docs/guides/cron).
|
||||
|
||||
## Understanding the challenge
|
||||
|
||||
When implementing semantic search with pgvector, developers typically need to:
|
||||
|
||||
1. Generate embeddings via an external API (like OpenAI)
|
||||
2. Store these embeddings alongside the content
|
||||
3. Keep embeddings in sync when content changes
|
||||
4. Handle failures and retries in the embedding generation process
|
||||
|
||||
While Postgres [full-text search](/docs/guides/database/full-text-search) can handle this internally through synchronous calls to `to_tsvector` and [triggers](https://www.postgresql.org/docs/current/textsearch-features.html#TEXTSEARCH-UPDATE-TRIGGERS), semantic search requires asynchronous API calls to a provider like OpenAI to generate vector embeddings. This guide demonstrates how to use triggers, queues, and Supabase Edge Functions to bridge this gap.
|
||||
|
||||
## Understanding the architecture
|
||||
|
||||
We'll leverage the following Postgres and Supabase features to create the automated embedding system:
|
||||
|
||||
1. [pgvector](/docs/guides/database/extensions/pgvector): Stores and queries vector embeddings
|
||||
1. [pgmq](/docs/guides/queues): Queues embedding generation requests for processing and retries
|
||||
1. [pg_net](/docs/guides/database/extensions/pg_net): Handles asynchronous HTTP requests to Edge Functions directly from Postgres
|
||||
1. [pg_cron](/docs/guides/cron): Automatically processes and retries embedding generations
|
||||
1. [Triggers](/docs/guides/database/postgres/triggers): Detects content changes and enqueues embedding generation requests
|
||||
1. [Edge Functions](/docs/guides/functions): Generates embeddings via an API like OpenAI (customizable)
|
||||
|
||||
We'll design the system to:
|
||||
|
||||
1. Be generic, so that it can be used with any table and content. This allows you to configure embeddings in multiple places, each with the ability to customize the input used for embedding generation. These will all use the same queue infrastructure and Edge Function to generate the embeddings.
|
||||
|
||||
1. Handle failures gracefully, by retrying failed jobs and providing detailed information about the status of each job.
|
||||
|
||||
## Implementation
|
||||
|
||||
We'll start by setting up the infrastructure needed to queue and process embedding generation requests. Then we'll create an example table with triggers to enqueue these embedding requests whenever content is inserted or updated.
|
||||
|
||||
### Step 1: Enable extensions
|
||||
|
||||
First, let's enable the required extensions:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="sql"
|
||||
queryGroup="database-method"
|
||||
>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
-- For vector operations
|
||||
create extension if not exists vector
|
||||
with
|
||||
schema extensions;
|
||||
|
||||
-- For queueing and processing jobs
|
||||
-- (pgmq will create its own schema)
|
||||
create extension if not exists pgmq;
|
||||
|
||||
-- For async HTTP requests
|
||||
create extension if not exists pg_net
|
||||
with
|
||||
schema extensions;
|
||||
|
||||
-- For scheduled processing and retries
|
||||
-- (pg_cron will create its own schema)
|
||||
create extension if not exists pg_cron;
|
||||
|
||||
-- For clearing embeddings during updates
|
||||
create extension if not exists hstore
|
||||
with
|
||||
schema extensions;
|
||||
```
|
||||
|
||||
Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
|
||||
To disable an extension, call `drop extension`.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Extensions](/dashboard/project/_/database/extensions) page in the Dashboard.
|
||||
2. Search for and enable the following extensions:
|
||||
- `vector`
|
||||
- `pgmq`
|
||||
- `pg_net`
|
||||
- `pg_cron`
|
||||
- `hstore`
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Step 2: Create utility functions
|
||||
|
||||
Before we set up our embedding logic, we need to create some utility functions:
|
||||
|
||||
```sql
|
||||
-- Schema for utility functions
|
||||
create schema util;
|
||||
|
||||
-- Utility function to get the Supabase project URL (required for Edge Functions)
|
||||
create function util.project_url()
|
||||
returns text
|
||||
language plpgsql
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
secret_value text;
|
||||
begin
|
||||
-- Retrieve the project URL from Vault
|
||||
select decrypted_secret into secret_value from vault.decrypted_secrets where name = 'project_url';
|
||||
return secret_value;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Generic function to invoke any Edge Function
|
||||
create or replace function util.invoke_edge_function(
|
||||
name text,
|
||||
body jsonb,
|
||||
timeout_milliseconds int = 5 * 60 * 1000 -- default 5 minute timeout
|
||||
)
|
||||
returns void
|
||||
language plpgsql
|
||||
as $$
|
||||
declare
|
||||
headers_raw text;
|
||||
auth_header text;
|
||||
begin
|
||||
-- If we're in a PostgREST session, reuse the request headers for authorization
|
||||
headers_raw := current_setting('request.headers', true);
|
||||
|
||||
-- Only try to parse if headers are present
|
||||
auth_header := case
|
||||
when headers_raw is not null then
|
||||
(headers_raw::json->>'authorization')
|
||||
else
|
||||
null
|
||||
end;
|
||||
|
||||
-- Perform async HTTP request to the edge function
|
||||
perform net.http_post(
|
||||
url => util.project_url() || '/functions/v1/' || name,
|
||||
headers => jsonb_build_object(
|
||||
'Content-Type', 'application/json',
|
||||
'Authorization', auth_header
|
||||
),
|
||||
body => body,
|
||||
timeout_milliseconds => timeout_milliseconds
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Generic trigger function to clear a column on update
|
||||
create or replace function util.clear_column()
|
||||
returns trigger
|
||||
language plpgsql as $$
|
||||
declare
|
||||
clear_column text := TG_ARGV[0];
|
||||
begin
|
||||
NEW := NEW #= hstore(clear_column, NULL);
|
||||
return NEW;
|
||||
end;
|
||||
$$;
|
||||
```
|
||||
|
||||
Here we create:
|
||||
|
||||
- A schema `util` to store utility functions.
|
||||
- A function to retrieve the Supabase project URL from [Vault](/docs/guides/database/vault). We'll add this secret next.
|
||||
- A generic function to invoke any Edge Function with a given name and request body.
|
||||
- A generic trigger function to clear a column on update. This function accepts the column name as an argument and sets it to `NULL` in the `NEW` record. We'll explain how to use this function later.
|
||||
|
||||
Every project has a unique API URL that is required to invoke Edge Functions. Let's go ahead and add the project URL secret to Vault depending on your environment.
|
||||
|
||||
When working with a local Supabase stack, add the following to your `supabase/seed.sql` file:
|
||||
|
||||
```sql
|
||||
select
|
||||
vault.create_secret('http://api.supabase.internal:8000', 'project_url');
|
||||
```
|
||||
|
||||
When deploying to the cloud platform, open the [SQL editor](/dashboard/project/_/sql/new) and run the following, replacing `<project-url>` with your [project's API URL](/dashboard/project/_/settings/api):
|
||||
|
||||
```sql
|
||||
select
|
||||
vault.create_secret('<project-url>', 'project_url');
|
||||
```
|
||||
|
||||
### Step 3: Create queue and triggers
|
||||
|
||||
Our goal is to automatically generate embeddings whenever content is inserted or updated within a table. We can use triggers and queues to achieve this. Our approach is to automatically queue embedding jobs whenever records are inserted or updated in a table, then process them asynchronously using a cron job. If a job fails, it will remain in the queue and be retried in the next scheduled task.
|
||||
|
||||
First we create a `pgmq` queue for processing embedding requests:
|
||||
|
||||
```sql
|
||||
-- Queue for processing embedding jobs
|
||||
select pgmq.create('embedding_jobs');
|
||||
```
|
||||
|
||||
Next we create a trigger function to queue embedding jobs. We'll use this function to handle both insert and update events:
|
||||
|
||||
```sql
|
||||
-- Generic trigger function to queue embedding jobs
|
||||
create or replace function util.queue_embeddings()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
declare
|
||||
content_function text = TG_ARGV[0];
|
||||
embedding_column text = TG_ARGV[1];
|
||||
begin
|
||||
perform pgmq.send(
|
||||
queue_name => 'embedding_jobs',
|
||||
msg => jsonb_build_object(
|
||||
'id', NEW.id,
|
||||
'schema', TG_TABLE_SCHEMA,
|
||||
'table', TG_TABLE_NAME,
|
||||
'contentFunction', content_function,
|
||||
'embeddingColumn', embedding_column
|
||||
)
|
||||
);
|
||||
return NEW;
|
||||
end;
|
||||
$$;
|
||||
```
|
||||
|
||||
Our `util.queue_embeddings` trigger function is generic and can be used with any table and content function. It accepts two arguments:
|
||||
|
||||
1. `content_function`: The name of a function that returns the text content to be embedded. The function should accept a single row as input and return text (see the `embedding_input` example).
|
||||
|
||||
This allows you to customize the text input passed to the embedding model - for example, you could concatenate multiple columns together like `title` and `content` and use the result as input.
|
||||
|
||||
2. `embedding_column`: The name of the destination column where the embedding will be stored.
|
||||
|
||||
Note that the `util.queue_embeddings` trigger function requires a `for each row` clause to work correctly. See [Usage](#usage) for an example of how to use this trigger function with your table.
|
||||
|
||||
Next we'll create a function to process the embedding jobs. This function will read jobs from the queue, group them into batches, and invoke the Edge Function to generate embeddings. We'll use `pg_cron` to schedule this function to run every 10 seconds.
|
||||
|
||||
```sql
|
||||
-- Function to process embedding jobs from the queue
|
||||
create or replace function util.process_embeddings(
|
||||
batch_size int = 10,
|
||||
max_requests int = 10,
|
||||
timeout_milliseconds int = 5 * 60 * 1000 -- default 5 minute timeout
|
||||
)
|
||||
returns void
|
||||
language plpgsql
|
||||
as $$
|
||||
declare
|
||||
job_batches jsonb[];
|
||||
batch jsonb;
|
||||
begin
|
||||
with
|
||||
-- First get jobs and assign batch numbers
|
||||
numbered_jobs as (
|
||||
select
|
||||
message || jsonb_build_object('jobId', msg_id) as job_info,
|
||||
(row_number() over (order by 1) - 1) / batch_size as batch_num
|
||||
from pgmq.read(
|
||||
queue_name => 'embedding_jobs',
|
||||
vt => timeout_milliseconds / 1000,
|
||||
qty => max_requests * batch_size
|
||||
)
|
||||
),
|
||||
-- Then group jobs into batches
|
||||
batched_jobs as (
|
||||
select
|
||||
jsonb_agg(job_info) as batch_array,
|
||||
batch_num
|
||||
from numbered_jobs
|
||||
group by batch_num
|
||||
)
|
||||
-- Finally aggregate all batches into array
|
||||
select array_agg(batch_array)
|
||||
from batched_jobs
|
||||
into job_batches;
|
||||
|
||||
-- Invoke the embed edge function for each batch
|
||||
foreach batch in array job_batches loop
|
||||
perform util.invoke_edge_function(
|
||||
name => 'embed',
|
||||
body => batch,
|
||||
timeout_milliseconds => timeout_milliseconds
|
||||
);
|
||||
end loop;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Schedule the embedding processing
|
||||
select
|
||||
cron.schedule(
|
||||
'process-embeddings',
|
||||
'10 seconds',
|
||||
$$
|
||||
select util.process_embeddings();
|
||||
$$
|
||||
);
|
||||
```
|
||||
|
||||
Let's discuss some common questions about this approach:
|
||||
|
||||
#### Why not generate all embeddings in a single Edge Function request?
|
||||
|
||||
While this is possible, it can lead to long processing times and potential timeouts. Batching allows us to process multiple embeddings concurrently and handle failures more effectively.
|
||||
|
||||
#### Why not one request per row?
|
||||
|
||||
This approach can lead to API rate limiting and performance issues. Batching provides a balance between efficiency and reliability.
|
||||
|
||||
#### Why queue requests instead of processing them immediately?
|
||||
|
||||
Queuing allows us to handle failures gracefully, retry requests, and manage concurrency more effectively. Specifically we are using `pgmq`'s visibility timeouts to ensure that failed requests are retried.
|
||||
|
||||
#### How do visibility timeouts work?
|
||||
|
||||
Every time we read a message from the queue, we set a visibility timeout which tells `pgmq` to hide the message from other readers for a certain period. If the Edge Function fails to process the message within this period, the message becomes visible again and will be retried by the next scheduled task.
|
||||
|
||||
#### How do we handle retries?
|
||||
|
||||
We use `pg_cron` to schedule a task that reads messages from the queue and processes them. If the Edge Function fails to process a message, it becomes visible again after a timeout and can be retried by the next scheduled task.
|
||||
|
||||
#### Is 10 seconds a good interval for processing?
|
||||
|
||||
This interval is a good starting point, but you may need to adjust it based on your workload and the time it takes to generate embeddings. You can adjust the `batch_size`, `max_requests`, and `timeout_milliseconds` parameters to optimize performance.
|
||||
|
||||
### Step 4: Create the Edge Function
|
||||
|
||||
Finally we'll create the Edge Function to generate embeddings. We'll use OpenAI's API in this example, but you can replace it with any other embedding generation service.
|
||||
|
||||
Use the Supabase CLI to create a new Edge Function:
|
||||
|
||||
```bash
|
||||
supabase functions new embed
|
||||
```
|
||||
|
||||
This will create a new directory `supabase/functions/embed` with an `index.ts` file. Replace the contents of this file with the following:
|
||||
|
||||
_supabase/functions/embed/index.ts_:
|
||||
|
||||
```typescript
|
||||
// Setup type definitions for built-in Supabase Runtime APIs
|
||||
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
|
||||
|
||||
// We'll use the OpenAI API to generate embeddings
|
||||
import OpenAI from 'jsr:@openai/openai'
|
||||
|
||||
import { z } from 'npm:zod'
|
||||
|
||||
// We'll make a direct Postgres connection to update the document
|
||||
import postgres from 'https://deno.land/x/postgresjs@v3.4.5/mod.js'
|
||||
|
||||
// Initialize OpenAI client
|
||||
const openai = new OpenAI({
|
||||
// We'll need to manually set the `OPENAI_API_KEY` environment variable
|
||||
apiKey: Deno.env.get('OPENAI_API_KEY'),
|
||||
})
|
||||
|
||||
// Initialize Postgres client
|
||||
const sql = postgres(
|
||||
// `SUPABASE_DB_URL` is a built-in environment variable
|
||||
Deno.env.get('SUPABASE_DB_URL')!
|
||||
)
|
||||
|
||||
const jobSchema = z.object({
|
||||
jobId: z.number(),
|
||||
id: z.number(),
|
||||
schema: z.string(),
|
||||
table: z.string(),
|
||||
contentFunction: z.string(),
|
||||
embeddingColumn: z.string(),
|
||||
})
|
||||
|
||||
const failedJobSchema = jobSchema.extend({
|
||||
error: z.string(),
|
||||
})
|
||||
|
||||
type Job = z.infer<typeof jobSchema>
|
||||
type FailedJob = z.infer<typeof failedJobSchema>
|
||||
|
||||
type Row = {
|
||||
id: string
|
||||
content: unknown
|
||||
}
|
||||
|
||||
const QUEUE_NAME = 'embedding_jobs'
|
||||
|
||||
// Listen for HTTP requests
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method !== 'POST') {
|
||||
return new Response('expected POST request', { status: 405 })
|
||||
}
|
||||
|
||||
if (req.headers.get('content-type') !== 'application/json') {
|
||||
return new Response('expected json body', { status: 400 })
|
||||
}
|
||||
|
||||
// Use Zod to parse and validate the request body
|
||||
const parseResult = z.array(jobSchema).safeParse(await req.json())
|
||||
|
||||
if (parseResult.error) {
|
||||
return new Response(`invalid request body: ${parseResult.error.message}`, {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
|
||||
const pendingJobs = parseResult.data
|
||||
|
||||
// Track jobs that completed successfully
|
||||
const completedJobs: Job[] = []
|
||||
|
||||
// Track jobs that failed due to an error
|
||||
const failedJobs: FailedJob[] = []
|
||||
|
||||
async function processJobs() {
|
||||
let currentJob: Job | undefined
|
||||
|
||||
while ((currentJob = pendingJobs.shift()) !== undefined) {
|
||||
try {
|
||||
await processJob(currentJob)
|
||||
completedJobs.push(currentJob)
|
||||
} catch (error) {
|
||||
failedJobs.push({
|
||||
...currentJob,
|
||||
error: error instanceof Error ? error.message : JSON.stringify(error),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Process jobs while listening for worker termination
|
||||
await Promise.race([processJobs(), catchUnload()])
|
||||
} catch (error) {
|
||||
// If the worker is terminating (e.g. wall clock limit reached),
|
||||
// add pending jobs to fail list with termination reason
|
||||
failedJobs.push(
|
||||
...pendingJobs.map((job) => ({
|
||||
...job,
|
||||
error: error instanceof Error ? error.message : JSON.stringify(error),
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
// Log completed and failed jobs for traceability
|
||||
console.log('finished processing jobs:', {
|
||||
completedJobs: completedJobs.length,
|
||||
failedJobs: failedJobs.length,
|
||||
})
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
completedJobs,
|
||||
failedJobs,
|
||||
}),
|
||||
{
|
||||
// 200 OK response
|
||||
status: 200,
|
||||
|
||||
// Custom headers to report job status
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Completed-Jobs': completedJobs.length.toString(),
|
||||
'X-Failed-Jobs': failedJobs.length.toString(),
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Generates an embedding for the given text.
|
||||
*/
|
||||
async function generateEmbedding(text: string) {
|
||||
const response = await openai.embeddings.create({
|
||||
model: 'text-embedding-3-small',
|
||||
input: text,
|
||||
})
|
||||
const [data] = response.data
|
||||
|
||||
if (!data) {
|
||||
throw new Error('failed to generate embedding')
|
||||
}
|
||||
|
||||
return data.embedding
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an embedding job.
|
||||
*/
|
||||
async function processJob(job: Job) {
|
||||
const { jobId, id, schema, table, contentFunction, embeddingColumn } = job
|
||||
|
||||
// Fetch content for the schema/table/row combination
|
||||
const [row]: [Row] = await sql`
|
||||
select
|
||||
id,
|
||||
${sql(contentFunction)}(t) as content
|
||||
from
|
||||
${sql(schema)}.${sql(table)} t
|
||||
where
|
||||
id = ${id}
|
||||
`
|
||||
|
||||
if (!row) {
|
||||
throw new Error(`row not found: ${schema}.${table}/${id}`)
|
||||
}
|
||||
|
||||
if (typeof row.content !== 'string') {
|
||||
throw new Error(`invalid content - expected string: ${schema}.${table}/${id}`)
|
||||
}
|
||||
|
||||
const embedding = await generateEmbedding(row.content)
|
||||
|
||||
await sql`
|
||||
update
|
||||
${sql(schema)}.${sql(table)}
|
||||
set
|
||||
${sql(embeddingColumn)} = ${JSON.stringify(embedding)}
|
||||
where
|
||||
id = ${id}
|
||||
`
|
||||
|
||||
await sql`
|
||||
select pgmq.delete(${QUEUE_NAME}, ${jobId}::bigint)
|
||||
`
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that rejects if the worker is terminating.
|
||||
*/
|
||||
function catchUnload() {
|
||||
return new Promise((reject) => {
|
||||
addEventListener('beforeunload', (ev: any) => {
|
||||
reject(new Error(ev.detail?.reason))
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The Edge Function listens for incoming HTTP requests from `pg_net` and processes each embedding job. It is a generic worker that can handle embedding jobs for any table and column. It uses OpenAI's API to generate embeddings and updates the corresponding row in the database. It also deletes the job from the queue once it has been processed.
|
||||
|
||||
The function is designed to process multiple jobs independently. If one job fails, it will not affect the processing of other jobs. The function returns a `200 OK` response with a list of completed and failed jobs. We can use this information to diagnose failed jobs. See [Troubleshooting](#troubleshooting) for more details.
|
||||
|
||||
You will need to set the `OPENAI_API_KEY` environment variable to authenticate with OpenAI. When running the Edge Function locally, you can add it to a `.env` file:
|
||||
|
||||
_.env_:
|
||||
|
||||
```
|
||||
OPENAI_API_KEY=your-api-key
|
||||
```
|
||||
|
||||
When you're ready to deploy the Edge Function, set can set the environment variable using the Supabase CLI:
|
||||
|
||||
```shell
|
||||
supabase secrets set --env-file .env
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
supabase secrets set OPENAI_API_KEY=<your-api-key>
|
||||
```
|
||||
|
||||
Alternatively, you can replace the `generateEmbedding` function with your own embedding generation logic.
|
||||
|
||||
See [Deploy to Production](/docs/guides/functions/deploy) for more information on how to deploy the Edge Function.
|
||||
|
||||
## Usage
|
||||
|
||||
Now that the infrastructure is in place, let's go through an example of how to use this system to automatically generate embeddings for a table of documents. You can use this approach with multiple tables and customize the input for each embedding generation as needed.
|
||||
|
||||
### 1. Create table to store documents with embeddings
|
||||
|
||||
We'll set up a new `documents` table that will store our content and embeddings:
|
||||
|
||||
```sql
|
||||
-- Table to store documents with embeddings
|
||||
create table documents (
|
||||
id integer primary key generated always as identity,
|
||||
title text not null,
|
||||
content text not null,
|
||||
embedding halfvec,
|
||||
created_at timestamp with time zone default now()
|
||||
);
|
||||
|
||||
-- Index for vector search over document embeddings
|
||||
create index on documents using hnsw (embedding halfvec_cosine_ops);
|
||||
```
|
||||
|
||||
Our `documents` table stores the title and content of each document along with its vector embedding. We use a `halfvec` column to store the embeddings, which is a `pgvector` data type that stores float values in half precision (16 bits) to save space.
|
||||
|
||||
We use an [HNSW index](/docs/guides/ai/vector-indexes/hnsw-indexes) on the vector column. Note that we are choosing `halfvec_cosine_ops` as the index method, which means our future queries will need to use cosine distance (`<=>`) to find similar embeddings. Also note that HNSW indexes support a maximum of 4000 dimensions for `halfvec` vectors, so keep this in mind when choosing an embedding model. If your model generates embeddings with more than 4000 dimensions, you will need to reduce the dimensionality before indexing them. See [Matryoshka embeddings](/blog/matryoshka-embeddings) for a potential solution to shortening dimensions.
|
||||
|
||||
Also note that the table must have a primary key column named `id` for our triggers to work correctly with the `util.queue_embeddings` function and for our Edge Function to update the correct row.
|
||||
|
||||
### 2. Create triggers to enqueue embedding jobs
|
||||
|
||||
Now we'll set up the triggers to enqueue embedding jobs whenever content is inserted or updated:
|
||||
|
||||
```sql
|
||||
-- Customize the input for embedding generation
|
||||
-- e.g. Concatenate title and content with a markdown header
|
||||
create or replace function embedding_input(doc documents)
|
||||
returns text
|
||||
language plpgsql
|
||||
immutable
|
||||
as $$
|
||||
begin
|
||||
return '# ' || doc.title || E'\n\n' || doc.content;
|
||||
end;
|
||||
$$;
|
||||
|
||||
-- Trigger for insert events
|
||||
create trigger embed_documents_on_insert
|
||||
after insert
|
||||
on documents
|
||||
for each row
|
||||
execute function util.queue_embeddings('embedding_input', 'embedding');
|
||||
|
||||
-- Trigger for update events
|
||||
create trigger embed_documents_on_update
|
||||
after update of title, content -- must match the columns in embedding_input()
|
||||
on documents
|
||||
for each row
|
||||
execute function util.queue_embeddings('embedding_input', 'embedding');
|
||||
```
|
||||
|
||||
We create 2 triggers:
|
||||
|
||||
1. `embed_documents_on_insert`: Enqueues embedding jobs whenever new rows are inserted into the `documents` table.
|
||||
|
||||
2. `embed_documents_on_update`: Enqueues embedding jobs whenever the `title` or `content` columns are updated in the `documents` table.
|
||||
|
||||
Both of these triggers use the same `util.queue_embeddings` function that will queue the embedding jobs for processing. They accept 2 arguments:
|
||||
|
||||
1. `embedding_input`: The name of the function that generates the input for embedding generation. This function allows you to customize the text input passed to the embedding model (e.g. concatenating the title and content). The function should accept a single row as input and return text.
|
||||
|
||||
2. `embedding`: The name of the destination column where the embedding will be stored.
|
||||
|
||||
Note that the update trigger only fires when the `title` or `content` columns are updated. This is to avoid unnecessary updates to the embedding column when other columns are updated. Make sure that these columns match the columns used in the `embedding_input` function.
|
||||
|
||||
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
|
||||
|
||||
#### (Optional) Clearing embeddings on update
|
||||
|
||||
Note that our trigger will enqueue new embedding jobs when content is updated, but it will not clear any existing embeddings. This means that an embedding can be temporarily out of sync with the content until the new embedding is generated and updated.
|
||||
|
||||
If it is more important to have _accurate_ embeddings than _any_ embedding, you can add another trigger to clear the existing embedding until the new one is generated:
|
||||
|
||||
```sql
|
||||
-- Trigger to clear the embedding column on update
|
||||
create trigger clear_document_embedding_on_update
|
||||
before update of title, content -- must match the columns in embedding_input()
|
||||
on documents
|
||||
for each row
|
||||
execute function util.clear_column('embedding');
|
||||
```
|
||||
|
||||
`util.clear_column` is a generic trigger function we created earlier that can be used to clear any column in a table.
|
||||
|
||||
- It accepts the column name as an argument. This column must be nullable.
|
||||
- It requires a `before` trigger with a `for each row` clause.
|
||||
- It requires the `hstore` extension we created earlier.
|
||||
|
||||
This example will clear the `embedding` column whenever the `title` or `content` columns are updated (note the `of title, content` clause). This ensures that the embedding is always in sync with the title and content, but it will result in temporary gaps in search results until the new embedding is generated.
|
||||
|
||||
We intentionally use a `before` trigger because it allows us to modify the record before it's written to disk, avoiding an extra `update` statement that would be needed with an `after` trigger.
|
||||
|
||||
### 3. Insert and update documents
|
||||
|
||||
Let's insert a new document and update its content to see the embedding generation in action:
|
||||
|
||||
```sql
|
||||
-- Insert a new document
|
||||
insert into documents (title, content)
|
||||
values
|
||||
('Understanding Vector Databases', 'Vector databases are specialized...');
|
||||
|
||||
-- Immediately check the embedding column
|
||||
select id, embedding
|
||||
from documents
|
||||
where title = 'Understanding Vector Databases';
|
||||
```
|
||||
|
||||
You should observe that the `embedding` column is initially `null` after inserting the document. This is because the embedding generation is asynchronous and will be processed by the Edge Function in the next scheduled task.
|
||||
|
||||
Wait up to 10 seconds for the next task to run, then check the `embedding` column again:
|
||||
|
||||
```sql
|
||||
select id, embedding
|
||||
from documents
|
||||
where title = 'Understanding Vector Databases';
|
||||
```
|
||||
|
||||
You should see the generated embedding for the document.
|
||||
|
||||
Next let's update the content of the document:
|
||||
|
||||
```sql
|
||||
-- Update the content of the document
|
||||
update documents
|
||||
set content = 'Vector databases allow you to query...'
|
||||
where title = 'Understanding Vector Databases';
|
||||
|
||||
-- Immediately check the embedding column
|
||||
select id, embedding
|
||||
from documents
|
||||
where title = 'Understanding Vector Databases';
|
||||
```
|
||||
|
||||
You should observe that the `embedding` column is reset to `null` after updating the content. This is because of the trigger we added to clear existing embeddings whenever the content is updated. The embedding will be regenerated by the Edge Function in the next scheduled task.
|
||||
|
||||
Wait up to 10 seconds for the next task to run, then check the `embedding` column again:
|
||||
|
||||
```sql
|
||||
select id, embedding
|
||||
from documents
|
||||
where title = 'Understanding Vector Databases';
|
||||
```
|
||||
|
||||
You should see the updated embedding for the document.
|
||||
|
||||
Finally we'll update the title of the document:
|
||||
|
||||
```sql
|
||||
-- Update the title of the document
|
||||
update documents
|
||||
set title = 'Understanding Vector Databases with Supabase'
|
||||
where title = 'Understanding Vector Databases';
|
||||
```
|
||||
|
||||
You should observe that the `embedding` column is once again reset to `null` after updating the title. This is because the trigger we added to clear existing embeddings fires when either the `content` or `title` columns are updated. The embedding will be regenerated by the Edge Function in the next scheduled task.
|
||||
|
||||
Wait up to 10 seconds for the next task to run, then check the `embedding` column again:
|
||||
|
||||
```sql
|
||||
select id, embedding
|
||||
from documents
|
||||
where title = 'Understanding Vector Databases with Supabase';
|
||||
```
|
||||
|
||||
You should see the updated embedding for the document.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The `embed` Edge Function processes a batch of embedding jobs and returns a `200 OK` response with a list of completed and failed jobs in the body. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"completedJobs": [
|
||||
{
|
||||
"jobId": "1",
|
||||
"id": "1",
|
||||
"schema": "public",
|
||||
"table": "documents",
|
||||
"contentFunction": "embedding_input",
|
||||
"embeddingColumn": "embedding"
|
||||
}
|
||||
],
|
||||
"failedJobs": [
|
||||
{
|
||||
"jobId": "2",
|
||||
"id": "2",
|
||||
"schema": "public",
|
||||
"table": "documents",
|
||||
"contentFunction": "embedding_input",
|
||||
"embeddingColumn": "embedding",
|
||||
"error": "error connecting to openai api"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
It also returns the number of completed and failed jobs in the response headers. For example:
|
||||
|
||||
```
|
||||
X-Completed-Jobs: 1
|
||||
X-Failed-Jobs: 1
|
||||
```
|
||||
|
||||
You can also use the `X-Deno-Execution-Id` header to trace the execution of the Edge Function within the [dashboard](/dashboard/project/_/functions) logs.
|
||||
|
||||
Each failed job includes an `error` field with a description of the failure. Reasons for a job failing could include:
|
||||
|
||||
- An error generating the embedding via external API
|
||||
- An error connecting to the database
|
||||
- The edge function being terminated (e.g. due to a wall clock limit)
|
||||
- Any other error thrown during processing
|
||||
|
||||
`pg_net` stores HTTP responses in the `net._http_response` table, which can be queried to diagnose issues with the embedding generation process.
|
||||
|
||||
```sql
|
||||
select
|
||||
*
|
||||
from
|
||||
net._http_response
|
||||
where
|
||||
(headers->>'X-Failed-Jobs')::int > 0;
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Automating embedding generation and updates in Postgres allow you to build powerful semantic search capabilities without the complexity of managing embeddings manually.
|
||||
|
||||
By combining Postgres features like triggers, queues, and other extensions with Supabase Edge Functions, we can create a robust system that handles embedding generation asynchronously and retries failed jobs automatically.
|
||||
|
||||
This system can be customized to work with any content and embedding generation service, providing a flexible and scalable solution for semantic search in Postgres.
|
||||
|
||||
## See also
|
||||
|
||||
- [What are embeddings?](/docs/guides/ai/concepts)
|
||||
- [Semantic search](/docs/guides/ai/semantic-search)
|
||||
- [Vector indexes](/docs/guides/ai/vector-indexes)
|
||||
- [Supabase Edge Functions](/docs/guides/functions)
|
||||
@@ -277,7 +277,7 @@ There are various ways to improve your pgvector performance. Here are some tips:
|
||||
|
||||
It's useful to execute a few thousand “warm-up” queries before going into production. This helps help with RAM utilization. This can also help to determine that you've selected the right compute size for your workload.
|
||||
|
||||
### Finetune index parameters
|
||||
### Fine-tune index parameters
|
||||
|
||||
You can increase the Requests per Second by increasing `m` and `ef_construction` or `lists`. This also has an important caveat: building the index takes longer with higher values for these parameters.
|
||||
|
||||
@@ -301,7 +301,7 @@ You can increase the Requests per Second by increasing `m` and `ef_construction`
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ivfflat" label="IVFFlat">
|
||||
|
||||
|
||||
<Image
|
||||
alt="multi database"
|
||||
src={{
|
||||
|
||||
@@ -80,9 +80,9 @@ To start quicker you may use Supabase CLI to spin everything up locally as it al
|
||||
supabase start
|
||||
```
|
||||
|
||||
This will pull all docker images and run supabase stack in docker on your local machine. It will also apply all the necessary migrations to set the whole thing up. You can then use your local setup the same way, just export the environment variables and follow to the next steps.
|
||||
This will pull all docker images and run Supabase stack in docker on your local machine. It will also apply all the necessary migrations to set the whole thing up. You can then use your local setup the same way, just export the environment variables and follow to the next steps.
|
||||
|
||||
Using `supabase-cli` is not required and you can use any other docker image or hosted version of PostgresDB that includes `pgvector`. Just make sure you run migrations from `examples/providers/supabase/migrations/20230414142107_init_pg_vector.sql`.
|
||||
Using `supabase-cli` is not required and you can use any other docker image or hosted version of Postgres that includes `pgvector`. Just make sure you run migrations from `examples/providers/supabase/migrations/20230414142107_init_pg_vector.sql`.
|
||||
|
||||
### Step 5: Obtain OpenAI API key
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ breadcrumb: 'AI Examples'
|
||||
|
||||
Supabase provides a [Headless Search Toolkit](https://github.com/supabase/headless-vector-search) for adding "Generative Q&A" to your documentation. The toolkit is "headless", so that you can integrate it into your existing website and style it to match your website theme.
|
||||
|
||||
You can see how this works with the Supabase docs. Just hit `cmd+k` and "ask" for something like "what are the features of supabase?". You will see that the response is streamed back, using the information provided in the docs:
|
||||
You can see how this works with the Supabase docs. Just hit `cmd+k` and "ask" for something like "what are the features of Supabase?". You will see that the response is streamed back, using the information provided in the docs:
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ subtitle: 'Implement image search with the OpenAI CLIP Model and Supabase Vector
|
||||
The [OpenAI CLIP Model](https://github.com/openai/CLIP) was trained on a variety of (image, text)-pairs. You can use the CLIP model for:
|
||||
|
||||
- Text-to-Image / Image-To-Text / Image-to-Image / Text-to-Text Search
|
||||
- You can fine-tune it on your own image and text data with the regular SentenceTransformers training code.
|
||||
- You can fine-tune it on your own image and text data with the regular `SentenceTransformers` training code.
|
||||
|
||||
[SentenceTransformers](https://www.sbert.net/examples/applications/image-search/README.html) provides models that allow you to embed images and text into the same vector space. You can use this to find similar images as well as to implement image search.
|
||||
[`SentenceTransformers`](https://www.sbert.net/examples/applications/image-search/README.html) provides models that allow you to embed images and text into the same vector space. You can use this to find similar images as well as to implement image search.
|
||||
|
||||
You can find the full application code as a Python Poetry project on [GitHub](https://github.com/supabase/supabase/tree/master/examples/ai/image_search#image-search-with-supabase-vector).
|
||||
|
||||
@@ -72,7 +72,7 @@ DB_CONNECTION = "postgresql://postgres:postgres@localhost:54322/postgres"
|
||||
|
||||
## Create embeddings for your images
|
||||
|
||||
In the root of your project, create a new folder called `images` and add some images. You can use the images from the example project on [GitHub](https://github.com/supabase/supabase/tree/master/examples/ai/image_search/images) or you can find license free images on [unsplash](https://unsplash.com).
|
||||
In the root of your project, create a new folder called `images` and add some images. You can use the images from the example project on [GitHub](https://github.com/supabase/supabase/tree/master/examples/ai/image_search/images) or you can find license free images on [Unsplash](https://unsplash.com).
|
||||
|
||||
Next, create a `seed` method, which will create a new Supabase Vector Collection, generate embeddings for your images, and upsert the embeddings into your database:
|
||||
|
||||
@@ -130,7 +130,7 @@ seed = "image_search.main:seed"
|
||||
search = "image_search.main:search"
|
||||
```
|
||||
|
||||
After activating the virtual environtment with `poetry shell` you can now run your seed script via `poetry run seed`. You can inspect the generated embeddings in your local database by visiting the local Supabase dashboard at [localhost:54323](http://localhost:54323/project/default/editor), selecting the `vecs` schema, and the `image_vectors` database.
|
||||
After activating the virtual environment with `poetry shell` you can now run your seed script via `poetry run seed`. You can inspect the generated embeddings in your local database by visiting the local Supabase dashboard at [localhost:54323](http://localhost:54323/project/default/editor), selecting the `vecs` schema, and the `image_vectors` database.
|
||||
|
||||
## Perform an image search from a text query
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ With our database set up, we need to process and store all `.mdx` files in the `
|
||||
<StepHikeCompact.Step step={3}>
|
||||
<StepHikeCompact.Details title="Run script at build time">
|
||||
|
||||
Include the script in your `package.json` script commands to enable Vercel to automaticall run it at build time.
|
||||
Include the script in your `package.json` script commands to enable Vercel to automatically run it at build time.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ You should see a GPT response come back from OpenAI!
|
||||
|
||||
## Deploy
|
||||
|
||||
Deploy your function to the cloud by runnning:
|
||||
Deploy your function to the cloud by running:
|
||||
|
||||
```bash
|
||||
supabase functions deploy --no-verify-jwt openai
|
||||
|
||||
@@ -84,7 +84,7 @@ bedrock_client = boto3.client(
|
||||
|
||||
## Create embeddings for your images
|
||||
|
||||
In the root of your project, create a new folder called `images` and add some images. You can use the images from the example project on [GitHub](https://github.com/supabase/supabase/tree/master/examples/ai/aws_bedrock_image_search/images) or you can find license free images on [unsplash](https://unsplash.com).
|
||||
In the root of your project, create a new folder called `images` and add some images. You can use the images from the example project on [GitHub](https://github.com/supabase/supabase/tree/master/examples/ai/aws_bedrock_image_search/images) or you can find license free images on [Unsplash](https://unsplash.com).
|
||||
|
||||
To send images to the Amazon Bedrock API we need to need to encode them as `base64` strings. Create the following helper methods:
|
||||
|
||||
@@ -188,7 +188,7 @@ seed = "image_search.main:seed"
|
||||
search = "image_search.main:search"
|
||||
```
|
||||
|
||||
After activating the virtual environtment with `poetry shell` you can now run your seed script via `poetry run seed`. You can inspect the generated embeddings in your Supabase Dashboard by visiting the [Table Editor](https://supabase.com/dashboard/project/_/editor), selecting the `vecs` schema, and the `image_vectors` table.
|
||||
After activating the virtual environment with `poetry shell` you can now run your seed script via `poetry run seed`. You can inspect the generated embeddings in your Supabase Dashboard by visiting the [Table Editor](https://supabase.com/dashboard/project/_/editor), selecting the `vecs` schema, and the `image_vectors` table.
|
||||
|
||||
## Perform an image search from a text query
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ On the other hand, if you need to scale your application, you will need to [crea
|
||||
`pgvector` supports two types of indexes: HNSW and IVFFlat. We recommend using [HNSW](/docs/guides/ai/vector-indexes/hnsw-indexes) because of its [performance](https://supabase.com/blog/increase-performance-pgvector-hnsw#hnsw-performance-1536-dimensions) and [robustness against changing data](/docs/guides/ai/vector-indexes/hnsw-indexes#when-should-you-create-hnsw-indexes).
|
||||
|
||||
<Image
|
||||
alt="dbpedia embeddings comparing ivfflat and hnsw queries-per-second using the 4XL compute addon"
|
||||
alt="dbpedia embeddings comparing ivfflat and hnsw queries-per-second using the 4XL compute add-on"
|
||||
src={{
|
||||
light: '/docs/img/ai/going-prod/dbpedia-ivfflat-vs-hnsw-4xl--light.png',
|
||||
dark: '/docs/img/ai/going-prod/dbpedia-ivfflat-vs-hnsw-4xl--dark.png',
|
||||
@@ -83,12 +83,12 @@ You can find more examples of how `lists` and `probes` constants affect accuracy
|
||||
|
||||
First, a few generic tips which you can pick and choose from:
|
||||
|
||||
1. The Supabase managed platform will automatically optimize Postgres configs for you based on your compute addon. But if you self-host, consider **adjusting your Postgres config** based on RAM & CPU cores. See [example optimizations](https://gist.github.com/egor-romanov/323e2847851bbd758081511785573c08) for more details.
|
||||
1. The Supabase managed platform will automatically optimize Postgres configs for you based on your compute add-on. But if you self-host, consider **adjusting your Postgres config** based on RAM & CPU cores. See [example optimizations](https://gist.github.com/egor-romanov/323e2847851bbd758081511785573c08) for more details.
|
||||
2. Prefer `inner-product` to `L2` or `Cosine` distances if your vectors are normalized (like `text-embedding-ada-002`). If embeddings are not normalized, `Cosine` distance should give the best results with an index.
|
||||
3. **Pre-warm your database.** Implement the warm-up technique before transitioning to production or running benchmarks.
|
||||
- Use [pg_prewarm](https://www.postgresql.org/docs/current/pgprewarm.html) to load the index into RAM `select pg_prewarm('vecs.docs_vec_idx');`. This will help to avoid cold cache issues.
|
||||
- Execute 10,000 to 50,000 "warm-up" queries before each benchmark/prod. This will help to utilize cache and buffers more efficiently.
|
||||
4. **Establish your workload.** Finetune `m` and `ef_construction` or `lists` constants for the pgvector index to accelerate your queries (at the expense of a slower build times). For instance, for benchmarks with 1,000,000 OpenAI embeddings, we set `m` and `ef_construction` to 32 and 80, and it resulted in 35% higher QPS than 24 and 56 values respectively.
|
||||
4. **Establish your workload.** Fine-tune `m` and `ef_construction` or `lists` constants for the pgvector index to accelerate your queries (at the expense of a slower build times). For instance, for benchmarks with 1,000,000 OpenAI embeddings, we set `m` and `ef_construction` to 32 and 80, and it resulted in 35% higher QPS than 24 and 56 values respectively.
|
||||
5. **Benchmark your own specific workloads.** Doing this during cache warm-up helps gauge the best value for the index build parameters, balancing accuracy with queries per second (QPS).
|
||||
|
||||
## Going into production
|
||||
|
||||
@@ -5,7 +5,7 @@ subtitle: 'Learn how to integrate Supabase with LlamaIndex, a data framework for
|
||||
breadcrumb: 'AI Integrations'
|
||||
---
|
||||
|
||||
This guide will walk you through a basic example using the LlamaIndex [SupabaseVectorStore](https://github.com/supabase/supabase/blob/master/examples/ai/llamaindex/llamaindex.ipynb).
|
||||
This guide will walk you through a basic example using the LlamaIndex [`SupabaseVectorStore`](https://github.com/supabase/supabase/blob/master/examples/ai/llamaindex/llamaindex.ipynb).
|
||||
|
||||
<DatabaseSetup />
|
||||
|
||||
@@ -66,5 +66,5 @@ You can view the inserted items in the [Table Editor](https://supabase.com/dashb
|
||||
|
||||
## Resources
|
||||
|
||||
- Visit the LlamaIndex + SupabaseVectorStore [docs](https://gpt-index.readthedocs.io/en/latest/examples/vector_stores/SupabaseVectorIndexDemo.html)
|
||||
- Visit the LlamaIndex + `SupabaseVectorStore` [docs](https://gpt-index.readthedocs.io/en/latest/examples/vector_stores/SupabaseVectorIndexDemo.html)
|
||||
- Visit the official LlamaIndex [repo](https://github.com/jerryjliu/llama_index/)
|
||||
@@ -147,7 +147,7 @@ export const run = async () => {
|
||||
|
||||
### Advanced metadata filtering
|
||||
|
||||
You can also use query builder-style filtering ([similar to how the Supabase JavaScript library works](https://supabase.com/docs/reference/javascript/using-filters)) instead of passing an object. Note that since the filter properties will be in the metadata column, you need to use arrow operators (`->` for integer or `->>` for text) as defined in [Postgrest API documentation](https://postgrest.org/en/stable/references/api/tables_views.html?highlight=operators#json-columns) and specify the data type of the property (e.g. the column should look something like `metadata->some_int_value::int`).
|
||||
You can also use query builder-style filtering ([similar to how the Supabase JavaScript library works](https://supabase.com/docs/reference/javascript/using-filters)) instead of passing an object. Note that since the filter properties will be in the metadata column, you need to use arrow operators (`->` for integer or `->>` for text) as defined in [PostgREST API documentation](https://postgrest.org/en/stable/references/api/tables_views.html?highlight=operators#json-columns) and specify the data type of the property (e.g. the column should look something like `metadata->some_int_value::int`).
|
||||
|
||||
```js
|
||||
import { SupabaseFilterRPCCall, SupabaseVectorStore } from 'langchain/vectorstores/supabase'
|
||||
|
||||
@@ -109,7 +109,7 @@ Let's build an Edge Function that will accept an input string and generate an em
|
||||
|
||||
Note the two options we pass to `session.run()`:
|
||||
|
||||
- `mean_pool`: The first option sets `pooling` to `mean`. Pooling referes to how token-level embedding representations are compressed into a single sentence embedding that reflects the meaning of the entire sentence. Average pooling is the most common type of pooling for sentence embeddings.
|
||||
- `mean_pool`: The first option sets `pooling` to `mean`. Pooling refers to how token-level embedding representations are compressed into a single sentence embedding that reflects the meaning of the entire sentence. Average pooling is the most common type of pooling for sentence embeddings.
|
||||
- `normalize`: The second option tells to normalize the embedding vector so that it can be used with distance measures like dot product. A normalized vector means its length (magnitude) is 1 - also referred to as a unit vector. A vector is normalized by dividing each element by the vector's length (magnitude), which maintains its direction but changes its length to 1.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
@@ -50,7 +50,7 @@ on document_sections for select to authenticated using (
|
||||
);
|
||||
```
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="tip">
|
||||
|
||||
In this example, the current user is determined using the built-in `auth.uid()` function when the query is executed through your project's auto-generated [REST API](/docs/guides/api). If you are connecting to your Supabase database through a direct Postgres connection, see [Direct Postgres Connection](#direct-postgres-connection) below for directions on how to achieve the same access control.
|
||||
|
||||
@@ -117,7 +117,7 @@ RLS is latency-sensitive, so extra caution should be taken before implementing t
|
||||
|
||||
</Admonition>
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="tip">
|
||||
|
||||
For data sources other than Postgres, see [Foreign Data Wrappers](/docs/guides/database/extensions/wrappers/overview) for a list of external sources supported today. If your data lives in a source not provided in the list, please contact [support](https://supabase.com/dashboard/support/new) and we'll be happy to discuss your use case.
|
||||
|
||||
@@ -161,7 +161,7 @@ import foreign schema public limit to (users, documents)
|
||||
from server foreign_server into external;
|
||||
```
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="tip">
|
||||
|
||||
This example maps the `authenticated` role in Supabase to the `postgres` user in the external DB. In production, it's best to create a custom user on the external DB that has the minimum permissions necessary to access the information you need.
|
||||
|
||||
@@ -224,7 +224,7 @@ where document_sections.embedding <#> embedding < -match_threshold
|
||||
order by document_sections.embedding <#> embedding;
|
||||
```
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="caution">
|
||||
|
||||
You might be tempted to discard RLS completely and simply filter by user within the `where` clause. Though this will work, we recommend RLS as a general best practice since RLS is always applied even as new queries and application logic is introduced in the future.
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ create table documents (
|
||||
|
||||
In the above SQL snippet, we create a `documents` table with a column called `embedding` (note this is just a regular Postgres column - you can name it whatever you like). We give the `embedding` column a `vector` data type with 384 dimensions. Change this to the number of dimensions produced by your embedding model. For example, if you are [generating embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings) using the open source [`gte-small`](https://huggingface.co/Supabase/gte-small) model, you would set this number to 384 since that model produces 384 dimensions.
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="tip">
|
||||
|
||||
In general, embeddings with fewer dimensions perform best. See our [analysis on fewer dimensions in pgvector](https://supabase.com/blog/fewer-dimensions-are-better-pgvector).
|
||||
|
||||
@@ -155,7 +155,7 @@ const { data: documents } = await supabaseClient.rpc('match_documents', {
|
||||
|
||||
In this example `embedding` would be another embedding you wish to compare against your table of pre-generated embedding documents. For example if you were building a search engine, every time the user submits their query you would first generate an embedding on the search query itself, then pass it into the above `rpc()` function to match.
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="tip">
|
||||
|
||||
Be sure to use embeddings produced from the same embedding model when calculating distance. Comparing embeddings from two different models will produce no meaningful result.
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
id: automatic-retries-in-supabase-js
|
||||
title: 'How to do automatic retries with `supabase-js`'
|
||||
subtitle: 'Learn how to add automatic retries to your Supabase API requests using `fetch-retry`.'
|
||||
---
|
||||
|
||||
<Admonition type="danger" title="Important">
|
||||
|
||||
You should only enable retries if your requests fail with network errors (e.g. 520 status from Cloudflare). A high number of retries have the potential to exhaust the Data API connection pool, which could result in lower throughput and failed requests.
|
||||
|
||||
</Admonition>
|
||||
|
||||
The `fetch-retry` package allows you to add retry logic to `fetch` requests, making it a useful tool for enhancing the resilience of API calls in your Supabase applications. Here's a step-by-step guide on how to integrate `fetch-retry` with the `supabase-js` library.
|
||||
|
||||
## 1. Install dependencies
|
||||
|
||||
To get started, ensure you have both `supabase-js` and `fetch-retry` installed in your project:
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js fetch-retry
|
||||
```
|
||||
|
||||
## 2. Wrap the fetch function
|
||||
|
||||
The `fetch-retry` package works by wrapping the native `fetch` function. You can create a custom fetch instance with retry logic and pass it to the `supabase-js` client.
|
||||
|
||||
```javascript
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import fetchRetry from 'fetch-retry'
|
||||
|
||||
// Wrap the global fetch with fetch-retry
|
||||
const fetchWithRetry = fetchRetry(fetch)
|
||||
|
||||
// Create a Supabase client instance with the custom fetch
|
||||
const supabase = createClient('https://your-supabase-url.supabase.co', 'your-anon-key', {
|
||||
global: {
|
||||
fetch: fetchWithRetry,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## 3. Configure retry options
|
||||
|
||||
You can configure `fetch-retry` options to control retry behavior, such as the number of retries, retry delay, and which errors should trigger a retry.
|
||||
|
||||
Here is an example with custom retry options:
|
||||
|
||||
```javascript
|
||||
const fetchWithRetry = fetchRetry(fetch, {
|
||||
retries: 3, // Number of retry attempts
|
||||
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000), // Exponential backoff
|
||||
retryOn: [520], // Retry only on Cloudflare errors
|
||||
})
|
||||
```
|
||||
|
||||
In this example, the `retryDelay` function implements an exponential backoff strategy, and retries are triggered only for specific HTTP status codes.
|
||||
|
||||
## 4. Using the Supabase client
|
||||
|
||||
With `fetch-retry` integrated, you can use the Supabase client as usual. The retry logic will automatically apply to all network requests made by `supabase-js`.
|
||||
|
||||
```javascript
|
||||
async function fetchData() {
|
||||
const { data, error } = await supabase.from('your_table').select('*')
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching data:', error)
|
||||
} else {
|
||||
console.log('Fetched data:', data)
|
||||
}
|
||||
}
|
||||
|
||||
fetchData()
|
||||
```
|
||||
|
||||
## 5. Fine-Tuning retries for specific requests
|
||||
|
||||
If you need different retry logic for certain requests, you can use the `retryOn` with a custom function to inspect the URL or response and decide whether to retry the request.
|
||||
|
||||
```javascript
|
||||
const fetchWithRetry = fetchRetry(fetch, {
|
||||
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
|
||||
retryOn: (attempt, error, response) => {
|
||||
const shouldRetry
|
||||
= (attempt: number, error: Error | null, response: Response | null) =>
|
||||
attempt < 3
|
||||
&& response
|
||||
&& response.status == 520 // Cloudflare errors
|
||||
&& response.url.includes('rpc/your_stored_procedure')
|
||||
|
||||
if (shouldRetry(attempt, error, response)) {
|
||||
console.log(`Retrying request... Attempt #${attempt}`, response)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
async function yourStoredProcedure() {
|
||||
const { data, error } = await supabase
|
||||
.rpc('your_stored_procedure', { param1: 'value1' });
|
||||
|
||||
if (error) {
|
||||
console.log('Error executing RPC:', error);
|
||||
} else {
|
||||
console.log('Response:', data);
|
||||
}
|
||||
}
|
||||
|
||||
yourStoredProcedure();
|
||||
```
|
||||
|
||||
By using `retryOn` with a custom function, you can define specific conditions for retrying requests. In this example, the retry logic is applied only to requests targeting a specific stored procedure.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Integrating `fetch-retry` with `supabase-js` is a straightforward way to add robustness to your Supabase API requests. By handling transient errors and implementing retry strategies, you can improve the reliability of your application while maintaining a seamless user experience.
|
||||
@@ -105,9 +105,9 @@ curl '<SUPABASE_URL>/rest/v1/todos' \
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
JS Reference: [select()](/docs/reference/javascript/select),
|
||||
[insert()](/docs/reference/javascript/insert),
|
||||
[update()](/docs/reference/javascript/update),
|
||||
[upsert()](/docs/reference/javascript/upsert),
|
||||
[delete()](/docs/reference/javascript/delete),
|
||||
[rpc()](/docs/reference/javascript/rpc) (call Postgres functions).
|
||||
JS Reference: [`select()`](/docs/reference/javascript/select),
|
||||
[`insert()`](/docs/reference/javascript/insert),
|
||||
[`update()`](/docs/reference/javascript/update),
|
||||
[`upsert()`](/docs/reference/javascript/upsert),
|
||||
[`delete()`](/docs/reference/javascript/delete),
|
||||
[`rpc()`](/docs/reference/javascript/rpc) (call Postgres functions).
|
||||
@@ -258,7 +258,7 @@ jobs:
|
||||
branch: ${{ github.ref }}
|
||||
```
|
||||
|
||||
Alternatively, you can use a community-supported GitHub action: [generate-supabase-db-types-github-action](https://github.com/lyqht/generate-supabase-db-types-github-action).
|
||||
Alternatively, you can use a community-supported GitHub action: [`generate-supabase-db-types-github-action`](https://github.com/lyqht/generate-supabase-db-types-github-action).
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ const { data, error } = await supabase
|
||||
## Resources
|
||||
|
||||
- [Supabase - Get started for free](https://supabase.com)
|
||||
- [Postgrest Operators](https://postgrest.org/en/stable/api.html#operators)
|
||||
- [PostgREST Operators](https://postgrest.org/en/stable/api.html#operators)
|
||||
- [Supabase API: JavaScript select](/docs/reference/javascript/select)
|
||||
- [Supabase API: JavaScript modifiers](/docs/reference/javascript/using-modifiers)
|
||||
- [Supabase API: JavaScript filters](/docs/reference/javascript/using-filters)
|
||||
@@ -7,7 +7,7 @@ description: 'Translate SQL queries to HTTP requests and Supabase client code'
|
||||
|
||||
Sometimes it's challenging to translate SQL queries to the equivalent [PostgREST](https://postgrest.org/) request or Supabase client code. Use this tool to help with this translation.
|
||||
|
||||
<Admonition type="info">
|
||||
<Admonition type="note">
|
||||
|
||||
PostgREST supports a subset of SQL, so not all SQL queries will translate.
|
||||
|
||||
|
||||
@@ -38,3 +38,12 @@ Supabase Auth works with many popular Auth methods, including Social and Phone A
|
||||
### Phone Auth
|
||||
|
||||
<AuthProviders type="phone" />
|
||||
|
||||
## Pricing
|
||||
|
||||
Charges apply to Monthly Active Users (MAU), Monthly Active Third-Party Users (Third-Party MAU), and Monthly Active SSO Users (SSO MAU) and Advanced MFA Add-ons. For a detailed breakdown of how these charges are calculated, refer to the following pages:
|
||||
|
||||
- [Pricing MAU](/docs/guides/platform/manage-your-usage/monthly-active-users)
|
||||
- [Pricing Third-Party MAU](/docs/guides/platform/manage-your-usage/monthly-active-users-third-party)
|
||||
- [Pricing SSO MAU](/docs/guides/platform/manage-your-usage/monthly-active-users-sso)
|
||||
- [Advanced MFA - Phone](/docs/guides/platform/manage-your-usage/advanced-mfa-phone)
|
||||
@@ -189,7 +189,7 @@ response = supabase.auth.update_user({
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
You can use the [`linkIdentity()`](/docs/reference/javascript/auth-linkidentity) method to link an oauth identity to the anonymous user.
|
||||
You can use the [`linkIdentity()`](/docs/reference/javascript/auth-linkidentity) method to link an OAuth identity to the anonymous user.
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.auth.linkIdentity({ provider: 'google' })
|
||||
@@ -216,7 +216,7 @@ try await supabase.auth.linkIdentity(provider: .google)
|
||||
</TabPanel>
|
||||
<TabPanel id="kotlin" label="Kotlin">
|
||||
|
||||
You can use the [`linkIdentity()`](/docs/reference/kotlin/auth-linkidentity) method to link an oauth identity to the anonymous user.
|
||||
You can use the [`linkIdentity()`](/docs/reference/kotlin/auth-linkidentity) method to link an OAuth identity to the anonymous user.
|
||||
|
||||
```kotlin
|
||||
supabase.auth.linkIdentity(Google)
|
||||
@@ -225,7 +225,7 @@ supabase.auth.linkIdentity(Google)
|
||||
</TabPanel>
|
||||
<TabPanel id="python" label="Python">
|
||||
|
||||
You can use the [`link_identity()`](/docs/reference/python/auth-linkidentity) method to link an oauth identity to the anonymous user.
|
||||
You can use the [`link_identity()`](/docs/reference/python/auth-linkidentity) method to link an OAuth identity to the anonymous user.
|
||||
|
||||
```python
|
||||
response = supabase.auth.link_identity({'provider': 'google'})
|
||||
@@ -250,7 +250,7 @@ to authenticated
|
||||
using ( true );
|
||||
```
|
||||
|
||||
<Admonition variant="note" label="Use restrictive policies">
|
||||
<Admonition type="note" label="Use restrictive policies">
|
||||
|
||||
RLS policies are permissive by default, which means that they are combined using an "OR" operator when multiple policies are applied. It is important to construct restrictive policies to ensure that the checks for an anonymous user are always enforced when combined with other policies.
|
||||
|
||||
@@ -313,7 +313,7 @@ async function resolveDataConflicts(anonymousUserId, existingUserId) {
|
||||
|
||||
## Abuse prevention and rate limits
|
||||
|
||||
Since anonymous users are stored in your database, bad actors can abuse the endpoint to increase your database size drastically. It is strongly recommended to [enable invisible Captcha or Cloudflare Turnstile](/docs/guides/auth/auth-captcha) to prevent abuse for anonymous sign-ins. An IP-based rate limit is enforced at 30 requests per hour which can be modified in your [dashboard](/dashboard/project/_/auth/rate-limits). You can refer to the full list of rate limits [here](/docs/guides/platform/going-into-prod#rate-limiting-resource-allocation--abuse-prevention).
|
||||
Since anonymous users are stored in your database, bad actors can abuse the endpoint to increase your database size drastically. It is strongly recommended to [enable invisible CAPTCHA or Cloudflare Turnstile](/docs/guides/auth/auth-captcha) to prevent abuse for anonymous sign-ins. An IP-based rate limit is enforced at 30 requests per hour which can be modified in your [dashboard](/dashboard/project/_/auth/rate-limits). You can refer to the full list of rate limits [here](/docs/guides/platform/going-into-prod#rate-limiting-resource-allocation--abuse-prevention).
|
||||
|
||||
## Automatic cleanup
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@ In the Settings page, look for the **Sitekey** section and copy the key.
|
||||
|
||||
## 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.
|
||||
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**.
|
||||
Select your CAPTCHA provider from the dropdown, enter your CAPTCHA **Secret key**, and click **Save**.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -77,13 +77,13 @@ Let's create a empty state to store our `captchaToken`
|
||||
const [captchaToken, setCaptchaToken] = useState()
|
||||
```
|
||||
|
||||
Now lets add the HCaptcha component to the JSX section of our code
|
||||
Now lets add the `HCaptcha` component to the JSX section of our code
|
||||
|
||||
```jsx
|
||||
<HCaptcha />
|
||||
```
|
||||
|
||||
We will pass it the sitekey we copied from the hCaptcha website as a property along with a onVerify property which takes a callback function. This callback function will have a token as one of its properties. Let's set the token in the state using `setCaptchaToken`
|
||||
We will pass it the sitekey we copied from the hCaptcha website as a property along with a `onVerify` property which takes a callback function. This callback function will have a token as one of its properties. Let's set the token in the state using `setCaptchaToken`
|
||||
|
||||
```jsx
|
||||
<HCaptcha
|
||||
@@ -94,7 +94,7 @@ We will pass it the sitekey we copied from the hCaptcha website as a property al
|
||||
/>
|
||||
```
|
||||
|
||||
Now lets use the captcha token we receive in our Supabase signUp function.
|
||||
Now lets use the CAPTCHA token we receive in our Supabase signUp function.
|
||||
|
||||
```jsx
|
||||
await supabase.auth.signUp({
|
||||
@@ -104,9 +104,9 @@ await supabase.auth.signUp({
|
||||
})
|
||||
```
|
||||
|
||||
We will also need to reset the captcha challenge after we have made a call to the function above.
|
||||
We will also need to reset the CAPTCHA challenge after we have made a call to the function above.
|
||||
|
||||
Create a ref to use on our HCaptcha component.
|
||||
Create a ref to use on our `HCaptcha` component.
|
||||
|
||||
```jsx
|
||||
const captcha = useRef()
|
||||
@@ -136,7 +136,7 @@ In order to test that this works locally we will need to use something like [ngr
|
||||
|
||||
<TabPanel id="turnstile-2" label="Turnstile">
|
||||
|
||||
The frontend requires some changes to provide the captcha on-screen for the user. Turnstile can be used with any JavaScript framework but we'll use React and the Turnstile React component for this example.
|
||||
The frontend requires some changes to provide the CAPTCHA on-screen for the user. Turnstile can be used with any JavaScript framework but we'll use React and the Turnstile React component for this example.
|
||||
|
||||
Install @marsidev/react-turnstile in your project as a dependency.
|
||||
|
||||
@@ -183,9 +183,9 @@ await supabase.auth.signUp({
|
||||
})
|
||||
```
|
||||
|
||||
To test locally, you will need to add localhost to the domain whitelist as per the [Cloudflare docs](https://developers.cloudflare.com/turnstile/reference/testing/)
|
||||
To test locally, you will need to add localhost to the domain allowlist as per the [Cloudflare docs](https://developers.cloudflare.com/turnstile/reference/testing/)
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
Run the application and you should now be provided with a captcha challenge.
|
||||
Run the application and you should now be provided with a CAPTCHA challenge.
|
||||
@@ -5,7 +5,7 @@ description: 'A prebuilt, customizable React component for authenticating users.
|
||||
sitemapPriority: 0.5
|
||||
---
|
||||
|
||||
<Admonition type="warning">
|
||||
<Admonition type="caution">
|
||||
|
||||
As of 7th Feb 2024, [this repository](https://github.com/supabase-community/auth-ui) is no longer maintained by the Supabase Team. At the moment, the team does not have capacity to give the expected level of care to this repository. We may revisit Auth UI in the future but regrettably have to leave it on hold for now as we focus on other priorities such as improving the Server-Side Rendering (SSR) package and advanced Auth primitives.
|
||||
|
||||
@@ -444,7 +444,7 @@ A full list of the available variables is below:
|
||||
| `phone_input_label` | Phone number |
|
||||
| `phone_input_placeholder` | Your phone number |
|
||||
| `token_input_label` | Token |
|
||||
| `token_input_placeholder` | Your Otp token |
|
||||
| `token_input_placeholder` | Your OTP token |
|
||||
| `button_label` | Verify token |
|
||||
| `loading_button_label` | Signing in ... |
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ void main() async {
|
||||
|
||||
### Email Auth
|
||||
|
||||
Use a SupaEmailAuth widget to create an email and password signin and signup form. It also contains a button to toggle to display a forgot password form.
|
||||
Use a `SupaEmailAuth` widget to create an email and password signin and signup form. It also contains a button to toggle to display a forgot password form.
|
||||
|
||||
You can pass metadataFields to add additional fields to the form to pass as metadata to Supabase.
|
||||
You can pass `metadataFields` to add additional fields to the form to pass as metadata to Supabase.
|
||||
|
||||
```dart
|
||||
SupaEmailAuth(
|
||||
@@ -63,7 +63,7 @@ SupaEmailAuth(
|
||||
|
||||
### Magic link Auth
|
||||
|
||||
Use SupaMagicAuth widget to create a magic link signIn form.
|
||||
Use `SupaMagicAuth` widget to create a magic link signIn form.
|
||||
|
||||
```dart
|
||||
SupaMagicAuth(
|
||||
@@ -75,7 +75,7 @@ SupaMagicAuth(
|
||||
|
||||
### Reset password
|
||||
|
||||
Use SupaResetPassword to create a password reset form.
|
||||
Use `SupaResetPassword` to create a password reset form.
|
||||
|
||||
```dart
|
||||
SupaResetPassword(
|
||||
@@ -87,7 +87,7 @@ SupaResetPassword(
|
||||
|
||||
### Phone Auth
|
||||
|
||||
Use SupaPhoneAuth to create a phone authentication form.
|
||||
Use `SupaPhoneAuth` to create a phone authentication form.
|
||||
|
||||
```dart
|
||||
SupaPhoneAuth(
|
||||
@@ -100,7 +100,7 @@ SupaPhoneAuth(
|
||||
|
||||
The package supports login with [official social providers](../../auth#providers).
|
||||
|
||||
Use SupaSocialsAuth to create list of social login buttons.
|
||||
Use `SupaSocialsAuth` to create list of social login buttons.
|
||||
|
||||
```dart
|
||||
SupaSocialsAuth(
|
||||
|
||||
@@ -20,7 +20,7 @@ The `auth-helpers` package has been replaced with the `@supabase/ssr` package. W
|
||||
size="medium"
|
||||
className="text-foreground-light border-b mt-8 pb-2"
|
||||
>
|
||||
|
||||
|
||||
<AccordionItem
|
||||
header="See legacy docs"
|
||||
id="legacy-docs"
|
||||
@@ -196,7 +196,7 @@ You can pass types that were [generated with the Supabase CLI](/docs/reference/j
|
||||
|
||||
### Browser client
|
||||
|
||||
Creating a new supabase client object:
|
||||
Creating a new `supabase` client object:
|
||||
|
||||
```tsx
|
||||
import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
|
||||
@@ -205,7 +205,7 @@ import { Database } from '../database.types'
|
||||
const supabaseClient = createPagesBrowserClient<Database>()
|
||||
```
|
||||
|
||||
Retrieving a supabase client object from the SessionContext:
|
||||
Retrieving a `supabase` client object from the `SessionContext`:
|
||||
|
||||
```tsx
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
@@ -286,7 +286,7 @@ export default LoginPage
|
||||
|
||||
## Server-side rendering (SSR)
|
||||
|
||||
Create a server supabase client to retrieve the logged in user's session:
|
||||
Create a server Supabase client to retrieve the logged in user's session:
|
||||
|
||||
```jsx pages/profile.js
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
@@ -321,7 +321,7 @@ export const getServerSideProps = async (ctx) => {
|
||||
|
||||
## Server-side data fetching with RLS
|
||||
|
||||
You can use the server supabase client to run [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) authenticated queries server-side:
|
||||
You can use the server Supabase client to run [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) authenticated queries server-side:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -543,7 +543,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
|
||||
## Protecting API routes
|
||||
|
||||
Create a server supabase client to retrieve the logged in user's session:
|
||||
Create a server Supabase client to retrieve the logged in user's session:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -674,7 +674,7 @@ supabase.auth.signUp({
|
||||
|
||||
#### Deprecated functions
|
||||
|
||||
With v0.7.x of the Next.js Auth Helpers a new naming convention has been implemented for createClient functions. The `createBrowserSupabaseClient` and `createServerSupabaseClient` functions have been marked as deprecated, and will be removed in a future version of the Auth Helpers.
|
||||
With v0.7.x of the Next.js Auth Helpers a new naming convention has been implemented for `createClient` functions. The `createBrowserSupabaseClient` and `createServerSupabaseClient` functions have been marked as deprecated, and will be removed in a future version of the Auth Helpers.
|
||||
|
||||
- `createBrowserSupabaseClient` has been replaced with `createPagesBrowserClient`
|
||||
- `createServerSupabaseClient` has been replaced with `createPagesServerClient`
|
||||
@@ -917,7 +917,7 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
- The `useUser` hook now returns the `user` object or `null`.
|
||||
- Usage with TypeScript: You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion:
|
||||
|
||||
Creating a new supabase client object:
|
||||
Creating a new `supabase` client object:
|
||||
|
||||
```tsx
|
||||
import { Database } from '../database.types'
|
||||
@@ -925,7 +925,7 @@ import { Database } from '../database.types'
|
||||
const [supabaseClient] = useState(() => createPagesBrowserClient<Database>())
|
||||
```
|
||||
|
||||
Retrieving a supabase client object from the SessionContext:
|
||||
Retrieving a `supabase` client object from the `SessionContext`:
|
||||
|
||||
```tsx
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
@@ -1304,7 +1304,7 @@ supabase.auth.signUp({
|
||||
|
||||
#### Deprecated functions
|
||||
|
||||
With v0.7.x of the Next.js Auth Helpers a new naming convention has been implemented for createClient functions. The `createMiddlewareSupabaseClient`, `createBrowserSupabaseClient`, `createServerComponentSupabaseClient` and `createRouteHandlerSupabaseClient` functions have been marked as deprecated, and will be removed in a future version of the Auth Helpers.
|
||||
With v0.7.x of the Next.js Auth Helpers a new naming convention has been implemented for `createClient` functions. The `createMiddlewareSupabaseClient`, `createBrowserSupabaseClient`, `createServerComponentSupabaseClient` and `createRouteHandlerSupabaseClient` functions have been marked as deprecated, and will be removed in a future version of the Auth Helpers.
|
||||
|
||||
- `createMiddlewareSupabaseClient` has been replaced with `createMiddlewareClient`
|
||||
- `createBrowserSupabaseClient` has been replaced with `createClientComponentClient`
|
||||
|
||||
@@ -20,7 +20,7 @@ We generally recommend using the new `@supabase/ssr` package instead of `auth-he
|
||||
size="medium"
|
||||
className="text-foreground-light border-b mt-8 pb-2"
|
||||
>
|
||||
|
||||
|
||||
<AccordionItem
|
||||
header="See legacy docs"
|
||||
id="legacy-docs"
|
||||
@@ -155,7 +155,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Note that we are specifying filterSerializedResponseHeaders here. We need to tell SvelteKit that supabase needs the `content-range` and `x-supabase-api-version` headers.
|
||||
Note that we are specifying `filterSerializedResponseHeaders` here. We need to tell SvelteKit that Supabase needs the `content-range` and `x-supabase-api-version` headers.
|
||||
|
||||
</Admonition>
|
||||
|
||||
@@ -213,7 +213,7 @@ export const GET = async ({ url, locals: { supabase } }) => {
|
||||
|
||||
### Generate types from your database
|
||||
|
||||
In order to get the most out of TypeScript and it's intellisense, you should import the generated Database types into the `app.d.ts` type definition file that comes with your SvelteKit project, where `import('./DatabaseDefinitions')` points to the generated types file outlined in [v2 docs here](https://supabase.com/docs/reference/javascript/release-notes#typescript-support) after you have logged in, linked, and generated types through the Supabase CLI.
|
||||
In order to get the most out of TypeScript and its IntelliSense, you should import the generated Database types into the `app.d.ts` type definition file that comes with your SvelteKit project, where `import('./DatabaseDefinitions')` points to the generated types file outlined in [v2 docs here](https://supabase.com/docs/reference/javascript/release-notes#typescript-support) after you have logged in, linked, and generated types through the Supabase CLI.
|
||||
|
||||
```ts src/app.d.ts
|
||||
// src/app.d.ts
|
||||
@@ -377,13 +377,13 @@ TypeScript types can be [generated with the Supabase CLI](https://supabase.com/d
|
||||
|
||||
Access the client inside pages by `$page.data.supabase` or `data.supabase` when using `export let data`.
|
||||
|
||||
The usage of `depends` tells sveltekit that this load function should be executed whenever `invalidate` is called to keep the page store in sync.
|
||||
The usage of `depends` tells SvelteKit that this load function should be executed whenever `invalidate` is called to keep the page store in sync.
|
||||
|
||||
`createSupabaseLoadClient` caches the client when running in a browser environment and therefore does not create a new client for every time the load function runs.
|
||||
|
||||
#### Setting up the event listener on the client side
|
||||
|
||||
We need to create an event listener in the root `+layout.svelte` file in order to catch supabase events being triggered.
|
||||
We need to create an event listener in the root `+layout.svelte` file in order to catch Supabase events being triggered.
|
||||
|
||||
```svelte src/routes/+layout.svelte
|
||||
<!-- src/routes/+layout.svelte -->
|
||||
@@ -416,7 +416,7 @@ The usage of `invalidate` tells SvelteKit that the root `+layout.ts` load functi
|
||||
|
||||
#### Sign in / sign up / sign out
|
||||
|
||||
We can access the supabase instance in our `+page.svelte` file through the data object.
|
||||
We can access the Supabase instance in our `+page.svelte` file through the data object.
|
||||
|
||||
```svelte src/routes/auth/+page.svelte
|
||||
<!-- // src/routes/auth/+page.svelte -->
|
||||
@@ -634,7 +634,7 @@ If you try to submit a form with the action `?/createPost` without a valid sessi
|
||||
|
||||
To avoid writing the same auth logic in every single route you can also use the handle hook to
|
||||
protect multiple routes at once. For this to work with your Supabase session, you need to use
|
||||
Sveltekit's [sequence helper](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks) function.
|
||||
SvelteKit's [sequence helper](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks) function.
|
||||
Edit your `/src/hooks.server.js` with the below:
|
||||
|
||||
<Tabs
|
||||
@@ -1081,7 +1081,7 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
||||
|
||||
Since version 0.9 relies on `hooks.server.ts` to setup our client, we no longer need the `hooks.client.ts` in our project for Supabase related code.
|
||||
|
||||
#### Typings [#migration-typings]
|
||||
#### Types [#migration-typings]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1431,7 +1431,7 @@ import '$lib/db'
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Typings [#migration-typings-0-8]
|
||||
#### Types [#migration-typings-0-8]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1784,7 +1784,7 @@ export const handle = sequence(auth(), yourHandler)
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Typings [#migration-typings-0-7]
|
||||
#### Types [#migration-typings-0-7]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
|
||||
@@ -36,7 +36,7 @@ Supabase supports 2 ways to [configure a hook](/dashboard/project/_/auth/hooks)
|
||||
>
|
||||
<TabPanel id="postgres-function" label="Postgres Function">
|
||||
|
||||
A [postgres function](/docs/guides/database/functions) can be configured as a hook. The function should take in a single argument -- the event of type JSONB -- and return a JSONB object. Since the postgres function runs on your database, the request does not leave your project's instance.
|
||||
A [Postgres function](/docs/guides/database/functions) can be configured as a hook. The function should take in a single argument -- the event of type JSONB -- and return a JSONB object. Since the Postgres function runs on your database, the request does not leave your project's instance.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="http" label="HTTP Endpoint">
|
||||
@@ -59,9 +59,9 @@ Sign the payload and grant permissions selectively in order to guard the integri
|
||||
>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
When you configure a postgres function as a hook, Supabase will automatically apply the following grants to the function for these reasons:
|
||||
When you configure a Postgres function as a hook, Supabase will automatically apply the following grants to the function for these reasons:
|
||||
|
||||
- Allow the `supabase_auth_admin` role to execute the function. The `supabase_auth_admin` role is the postgres role that is used by Supabase Auth to make requests to your database.
|
||||
- Allow the `supabase_auth_admin` role to execute the function. The `supabase_auth_admin` role is the Postgres role that is used by Supabase Auth to make requests to your database.
|
||||
- Revoke permissions from other roles (e.g. `anon`, `authenticated`, `public`) to ensure the function is not accessible by Supabase Data APIs.
|
||||
|
||||
```sql
|
||||
@@ -81,7 +81,7 @@ revoke execute
|
||||
|
||||
You will need to alter your row-level security (RLS) policies to allow the `supabase_auth_admin` role to access tables that you have RLS policies on. You can read more about RLS policies [here](/docs/guides/database/postgres/row-level-security).
|
||||
|
||||
Alternatively, you can create your postgres function via the dashboard with the `security definer` tag. The `security definer` tag specifies that the function is to be executed with the privileges of the user that owns it.
|
||||
Alternatively, you can create your Postgres function via the dashboard with the `security definer` tag. The `security definer` tag specifies that the function is to be executed with the privileges of the user that owns it.
|
||||
|
||||
Currently, functions created via the dashboard take on the `postgres` role. Read more about the `security definer` tag [in our database guide](/docs/guides/database/functions#security-definer-vs-invoker)
|
||||
|
||||
@@ -96,16 +96,16 @@ HTTP Hooks in Supabase follow the [Standard Webhooks Specification](https://www.
|
||||
|
||||
When the request is made to the HTTP hook, you should use the [Standard Webhooks libraries](https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries) to verify these headers.
|
||||
|
||||
When a HTTP hook is created, the secret generated should be of the following format: `v1,whsec_<standard-base64-secret>`:
|
||||
When a HTTP hook is created, the secret generated should be of the `v1,whsec_<base64-secret>` format:
|
||||
|
||||
- `v1` denotes the version of the hook
|
||||
- `whsec_` signifies that the secret is symmetric
|
||||
- `<standard-base64-secret>` implies a Standard Base64 encoded secret which can contain the characters `+`, `/` and `=`
|
||||
- `<base64-secret>` implies a Standard Base64 encoded secret which can contain the characters `+`, `/` and `=`
|
||||
|
||||
The secret is used to verify the payload received in your hook. Create an entry in your `.env.local` file to store the secret for each hook that you have. For example:
|
||||
The secret is used to verify the payload received in your hook. Create an entry in your `.env.local` file to store the `<standard-base64-secret>` portion of the secret for each hook that you have. For example:
|
||||
|
||||
```bash
|
||||
HOOK_SECRETS=<standard-base64-secret>
|
||||
```ini
|
||||
SEND_SMS_HOOK_SECRETS=v1,whsec_<base64-secret>
|
||||
```
|
||||
|
||||
There field is expressed in plural rather than singular as there are plans to allow for asymmetric signing and multiple hook secrets for ease of secret rotation. For instance: `<standard-base-64-secret>|<another-standard-base-64-secret>`.
|
||||
@@ -117,7 +117,7 @@ import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const payload = await req.text()
|
||||
const hookSecret = Deno.env.get('HOOK_SECRETS')
|
||||
const hookSecret = Deno.env.get('SEND_SMS_HOOK_SECRETS').replace('v1,whsec_', '')
|
||||
// Extract headers and security specific fields
|
||||
const headers = Object.fromEntries(req.headers)
|
||||
const wh = new Webhook(hookSecret)
|
||||
@@ -229,19 +229,25 @@ You can visit `SQL Editor > Templates` for hook templates.
|
||||
</TabPanel>
|
||||
<TabPanel id="http" label="HTTP">
|
||||
Modify the `auth.hook.<hook_name>` field and set `uri` to a valid HTTP URI. For example, the `send_sms` hook would take the following fields:
|
||||
```
|
||||
|
||||
```toml
|
||||
[auth.hook.send_sms]
|
||||
enabled = true
|
||||
uri = "http://host.docker.internal:54321/functions/v1/send_sms"
|
||||
secrets = "env(SEND_SMS_HOOK_SECRET)"
|
||||
# Comma separated list of secrets
|
||||
secrets = "env(SEND_SMS_HOOK_SECRETS)"
|
||||
```
|
||||
|
||||
`host.docker.internal` is a special DNS name used in Docker to allow a container to access the host machine's network. This allows the Auth container to reach the Functions container
|
||||
<Admonition type="info">
|
||||
|
||||
`host.docker.internal` is a special DNS name used in Docker to allow a container to access the host machine's network. This allows the Auth container to reach your HTTP function, no matter if it's a Supabase Edge Function or a custom endpoint.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Fill in the Hook Secret in `supabase/functions/.env`
|
||||
|
||||
```bash
|
||||
SEND_SMS_HOOK_SECRET='v1,whsec_<base64_standard_secret>'
|
||||
```ini
|
||||
SEND_SMS_HOOK_SECRETS='v1,whsec_<base64-secret>'
|
||||
```
|
||||
|
||||
Start the function locally:
|
||||
@@ -344,7 +350,7 @@ Outside of runtime errors, both HTTP Hooks and Postgres Hooks return timeout err
|
||||
|
||||
## Available Hooks
|
||||
|
||||
Each Hook description contains an example JSONSchema which you can use in conjunction with [JSONSchema Faker](https://json-schema-faker.js.org/) in order to generate a mock payload. For HTTP Hooks, you can also use [the Standard Webhooks Testing Tool](https://www.standardwebhooks.com/simulate) to simulate a request.
|
||||
Each Hook description contains an example JSON Schema which you can use in conjunction with [JSON Schema Faker](https://json-schema-faker.js.org/) in order to generate a mock payload. For HTTP Hooks, you can also use [the Standard Webhooks Testing Tool](https://www.standardwebhooks.com/simulate) to simulate a request.
|
||||
|
||||
<div className="grid md:grid-cols-12 gap-4 not-prose">
|
||||
{[
|
||||
|
||||
@@ -431,7 +431,7 @@ import * as base64 from 'https://denopkg.com/chiefbiiko/base64/mod.ts'
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const payload = await req.text()
|
||||
const base64_secret = Deno.env.get('AUTH_CUSTOM_ACCESS_TOKEN_SECRET')
|
||||
const base64_secret = Deno.env.get('CUSTOM_ACCESS_TOKEN_SECRET').replace('v1,whsec_', '')
|
||||
const headers = Object.fromEntries(req.headers)
|
||||
const wh = new Webhook(base64_secret)
|
||||
try {
|
||||
@@ -477,7 +477,7 @@ import * as base64 from 'https://denopkg.com/chiefbiiko/base64/mod.ts'
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const payload = await req.text()
|
||||
const base64_secret = Deno.env.get('AUTH_CUSTOM_ACCESS_TOKEN_SECRET')
|
||||
const base64_secret = Deno.env.get('CUSTOM_ACCESS_TOKEN_SECRET').replace('v1,whsec_', '')
|
||||
const headers = Object.fromEntries(req.headers)
|
||||
const wh = new Webhook(base64_secret)
|
||||
try {
|
||||
|
||||
@@ -282,7 +282,7 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
|
||||
},
|
||||
"email_action_type": {
|
||||
"type": "string",
|
||||
"enum": ["signup", "login", "reset_password"]
|
||||
"enum": ["signup", "invite", "magiclink", "recovery", "email_change", "email"]
|
||||
},
|
||||
"site_url": {
|
||||
"type": "string",
|
||||
@@ -472,14 +472,14 @@ If you want to send emails through the Supabase Resend integration, which uses R
|
||||
|
||||
Create a `.env` file with the following environment variables:
|
||||
|
||||
```bash
|
||||
RESEND_API_KEY=your_resend_api_key
|
||||
SEND_EMAIL_HOOK_SECRET=<base64_secret>
|
||||
```ini
|
||||
RESEND_API_KEY="your_resend_api_key"
|
||||
SEND_EMAIL_HOOK_SECRET="v1,whsec_<base64_secret>"
|
||||
```
|
||||
|
||||
<Admonition type="info">
|
||||
<Admonition type="note">
|
||||
|
||||
You can generate the secret in the [Auth Hooks](/dashboard/project/_/auth/hooks) section of the Supabase dashboard. Make sure to remove the `v1,whsec_` prefix!
|
||||
You can generate the secret in the [Auth Hooks](/dashboard/project/_/auth/hooks) section of the Supabase dashboard.
|
||||
|
||||
</Admonition>
|
||||
|
||||
@@ -502,7 +502,7 @@ import { Webhook } from "https://esm.sh/standardwebhooks@1.0.0";
|
||||
import { Resend } from "npm:resend";
|
||||
|
||||
const resend = new Resend(Deno.env.get("RESEND_API_KEY") as string);
|
||||
const hookSecret = Deno.env.get("SEND_EMAIL_HOOK_SECRET") as string;
|
||||
const hookSecret = (Deno.env.get("SEND_EMAIL_HOOK_SECRET") as string).replace("v1,whsec_", "");
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method !== "POST") {
|
||||
@@ -569,7 +569,7 @@ supabase functions deploy send-email --no-verify-jwt
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="http-internationalization-for-emails" label="Add Internationalization for Email Templates">
|
||||
Your company is expanding to France and Spain. As part of expansion efforts, the company would like to deliver internationalized email templates to best support local users in their native language. Ensure that you have configured `POSTMARK_SERVER_TOKEN` and `AUTH_SEND_EMAIL_HOOK_SECRET` in your `.env` file.
|
||||
Your company is expanding to France and Spain. As part of expansion efforts, the company would like to deliver internationalized email templates to best support local users in their native language. Ensure that you have configured `POSTMARK_SERVER_TOKEN` and `SEND_EMAIL_HOOK_SECRET` in your `.env` file.
|
||||
|
||||
```javascript
|
||||
import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
|
||||
@@ -650,7 +650,7 @@ Deno.serve(async (req) => {
|
||||
const payload = await req.text()
|
||||
const serverToken = Deno.env.get('POSTMARK_SERVER_TOKEN')
|
||||
const headers = Object.fromEntries(req.headers)
|
||||
const base64_secret = Deno.env.get('AUTH_SEND_EMAIL_HOOK_SECRET')
|
||||
const base64_secret = Deno.env.get('SEND_EMAIL_HOOK_SECRET').replace('v1,whsec_', '')
|
||||
const wh = new Webhook(base64_secret)
|
||||
const { user, email_data } = wh.verify(payload, headers)
|
||||
|
||||
@@ -717,7 +717,7 @@ Deno.serve(async (req) => {
|
||||
</TabPanel>
|
||||
{/* <TabPanel id="http-backup-email-provider" label="Add Backup Email Provider">
|
||||
Your company is rapidly growing and depends heavily on email signups. You'd like to configure a backup email provider in case the email provider runs out of credits during your new product launch. Postmark and Sengrid are used as examples but in practice you can use any email provider.
|
||||
Ensure that you have configured `POSTMARK_SERVER_TOKEN`, `SENDGRID_API_KEY` and `AUTH_SEND_EMAIL_HOOK_SECRET` in your `.env` file.
|
||||
Ensure that you have configured `POSTMARK_SERVER_TOKEN`, `SENDGRID_API_KEY` and `SEND_EMAIL_HOOK_SECRET` in your `.env` file.
|
||||
|
||||
```javascript
|
||||
import {
|
||||
@@ -829,7 +829,7 @@ Deno.serve(async (req) => {
|
||||
const postmarkServerToken = Deno.env.get("POSTMARK_SERVER_TOKEN");
|
||||
const sendGridApiKey = Deno.env.get("SENDGRID_API_KEY");
|
||||
const headers = Object.fromEntries(req.headers);
|
||||
const base64_secret = Deno.env.get('AUTH_SEND_EMAIL_HOOK_SECRET');
|
||||
const base64_secret = Deno.env.get('SEND_EMAIL_HOOK_SECRET').replace('v1,whsec_', '');
|
||||
const wh = new Webhook(base64_secret);
|
||||
const {
|
||||
user,
|
||||
|
||||
@@ -8,7 +8,7 @@ Runs before a message is sent. Use the hook to:
|
||||
|
||||
- Use a regional SMS Provider
|
||||
- Use alternate messaging channels such as WhatsApp
|
||||
- Adjust the message body to include platform specific fields such as the [AppHash](https://developers.google.com/identity/sms-retriever/overview)
|
||||
- Adjust the message body to include platform specific fields such as the [`AppHash`](https://developers.google.com/identity/sms-retriever/overview)
|
||||
|
||||
**Inputs**
|
||||
|
||||
@@ -464,7 +464,7 @@ const sendTextMessage = async (
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const payload = await req.text()
|
||||
const base64_secret = Deno.env.get('SEND_SMS_HOOK_SECRET')
|
||||
const base64_secret = Deno.env.get('SEND_SMS_HOOK_SECRET').replace('v1,whsec_', '')
|
||||
const headers = Object.fromEntries(req.headers)
|
||||
const wh = new Webhook(base64_secret)
|
||||
try {
|
||||
@@ -527,12 +527,8 @@ Deno.serve(async (req) => {
|
||||
Your company is expanding into Latin America and would like to use WhatsApp for higher deliverability. Write a hook to send WhatsApp messages to requests from the continent and SMS messages to all other numbers.
|
||||
|
||||
```javascript
|
||||
import {
|
||||
Webhook
|
||||
} from "https://esm.sh/standardwebhooks@1.0.0";
|
||||
import {
|
||||
readAll
|
||||
} from "https://deno.land/std/io/read_all.ts";
|
||||
import { Webhook } from "https://esm.sh/standardwebhooks@1.0.0";
|
||||
import { readAll } from "https://deno.land/std/io/read_all.ts";
|
||||
import * as base64 from "https://denopkg.com/chiefbiiko/base64/mod.ts";
|
||||
|
||||
const accountSid: string | undefined = Deno.env.get("TWILIO_ACCOUNT_SID");
|
||||
@@ -580,7 +576,7 @@ const sendMessage = async (
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const payload = await req.text();
|
||||
const base64_secret = Deno.env.get("SEND_SMS_HOOK_SECRET");
|
||||
const base64_secret = Deno.env.get("SEND_SMS_HOOK_SECRET").replace('v1,whsec_', '');
|
||||
const headers = Object.fromEntries(req.headers);
|
||||
const wh = new Webhook(base64_secret);
|
||||
try {
|
||||
|
||||
@@ -111,7 +111,7 @@ Below is an example that creates a new `UnenrollMFA` component that illustrates
|
||||
- When the component appears on screen, the `supabase.auth.mfa.listFactors()` endpoint
|
||||
fetches all existing factors together with their details.
|
||||
- The existing factors for a user are displayed in a table.
|
||||
- Once the user has selected a factor to unenroll, they can type in the factorId and click **Unenroll**
|
||||
- Once the user has selected a factor to unenroll, they can type in the `factorId` and click **Unenroll**
|
||||
which creates a confirmation modal.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
@@ -21,8 +21,6 @@ Below is a flow chart illustrating how the Enrollment and Verify APIs work in th
|
||||
containerClassName="max-w-[700px]"
|
||||
/>
|
||||
|
||||
Phone MFA is part of the Auth Advanced MFA Add-on and costs an additional $75 per month for the first project in the organization and an additional $10 per month for additional projects.
|
||||
|
||||
### Add enrollment flow
|
||||
|
||||
An enrollment flow provides a UI for users to set up additional authentication factors. Most applications add the enrollment flow in two places within their app:
|
||||
@@ -311,3 +309,9 @@ function AuthMFA() {
|
||||
Each code is valid for up to 5 minutes, after which a new one can be sent. Successive codes remain valid until expiry. When possible choose the longest code length acceptable to your use case, at a minimum of 6. This can be configured in the [Authentication Settings](/dashboard/project/_/settings/auth).
|
||||
|
||||
Please be aware that Phone MFA is vulnerable to SIM swap attacks where an attacker will call a mobile provider and ask to port the target's phone number to a new SIM card and then use the said SIM card to intercept an MFA code. Please evaluate the your application's tolerance for such an attack. You can read more about SIM swapping attacks [here](https://en.wikipedia.org/wiki/SIM_swap_scam)
|
||||
|
||||
## Pricing
|
||||
|
||||
<PricingMfaPhone />
|
||||
|
||||
For a detailed breakdown of how charges are calculated, refer to [Manage Advanced MFA Phone usage](/docs/guides/platform/manage-your-usage/advanced-mfa-phone).
|
||||
@@ -63,7 +63,7 @@ They use lists of known email addresses to sign up users to your project with pr
|
||||
Usually the goal for this behavior is:
|
||||
|
||||
- To negatively affect your email sending reputation, after which they might ask for a ransom promising to stop the behavior.
|
||||
- To cause a short-term or even long-term Denial of Service attack on your service, by preventing new account creation, sign ins with magic links or one-time passwords, or to severely impact important security flows in your application (such as reset password or forgot password).
|
||||
- To cause a short-term or even long-term Denial of Service attack on your service, by preventing new account creation, signins with magic links or one-time passwords, or to severely impact important security flows in your application (such as reset password or forgot password).
|
||||
- To force you to reduce the security posture of your project, such as by disabling email confirmations. At that point, they may target specific or a broad number of users by creating an account in their name. Then they can use social engineering techniques to trick them to use your application in such a way that both attacker and victim have access to the same account.
|
||||
|
||||
Mitigation strategies:
|
||||
|
||||
@@ -5,7 +5,7 @@ description: 'Use Single Sign-On (SSO) authentication on your project with SAML
|
||||
video: 'https://www.youtube.com/v/em1cpOAXknM'
|
||||
---
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="tip">
|
||||
|
||||
Looking for guides on how to use Single Sign-On with the Supabase dashboard? Head on over to [Enable SSO for Your Organization](/docs/guides/platform/sso).
|
||||
|
||||
@@ -13,7 +13,7 @@ Looking for guides on how to use Single Sign-On with the Supabase dashboard? Hea
|
||||
|
||||
Supabase Auth supports enterprise-level Single Sign-On (SSO) for any identity providers compatible with the SAML 2.0 protocol. This is a non-exclusive list of supported identity providers:
|
||||
|
||||
- Google Workspaces (formerly known as GSuite)
|
||||
- Google Workspaces (formerly known as G Suite)
|
||||
- Okta, Auth0
|
||||
- Microsoft Active Directory, Azure Active Directory, Microsoft Entra
|
||||
- PingIdentity
|
||||
@@ -36,14 +36,14 @@ Please note that SAML 2.0 support is offered on plans Pro and above. Check the [
|
||||
The number of SAML and SSO acronyms can often be overwhelming. Here's a glossary which you can refer back to at any time:
|
||||
|
||||
- **Identity Provider**, **IdP**, or **IDP**
|
||||
An identity provider is a service that manages user accounts at a company or organization. It can verify the identity of a user and exchange that information with your Supabase project and other applications. It acts as a single source of truth for user identities and access rights. Commonly used identity providers are: Microsoft Active Directory (Azure AD, Microsoft Entra), Okta, Google Workspaces (GSuite), PingIdentity, OneLogin, and many others. There are also self-hosted and on-prem versions of identity providers, and sometimes they are accessible only by having access to a company VPN or being in a specific building.
|
||||
An identity provider is a service that manages user accounts at a company or organization. It can verify the identity of a user and exchange that information with your Supabase project and other applications. It acts as a single source of truth for user identities and access rights. Commonly used identity providers are: Microsoft Active Directory (Azure AD, Microsoft Entra), Okta, Google Workspaces (G Suite), PingIdentity, OneLogin, and many others. There are also self-hosted and on-prem versions of identity providers, and sometimes they are accessible only by having access to a company VPN or being in a specific building.
|
||||
- **Service Provider**, **SP**
|
||||
This is the software that is asking for user information from an identity provider. In Supabase, this is your project's Auth server.
|
||||
- **Assertion**
|
||||
An assertion is a statement issued by an identity provider that contains information about a user.
|
||||
- **EntityID**
|
||||
- **`EntityID`**
|
||||
A globally unique ID (usually a URL) that identifies an Identity Provider or Service Provider across the world.
|
||||
- **NameID**
|
||||
- **`NameID`**
|
||||
A unique ID (usually an email address) that identifies a user at an Identity Provider.
|
||||
- **Metadata**
|
||||
An XML document that describes the features and configuration of an Identity Provider or Service Provider. It can be as a standalone document or as a URL. Usually (but not always) the `EntityID` is the URL at which you can access the Metadata.
|
||||
@@ -53,7 +53,7 @@ The number of SAML and SSO acronyms can often be overwhelming. Here's a glossary
|
||||
This is one of the most important SAML URLs. It is the URL where Supabase Auth will accept assertions from an identity provider. Basically, once the identity provider verifies the user's identity it will redirect to this URL and the redirect request will contain the assertion.
|
||||
- **Binding (Redirect, POST, or Artifact)**
|
||||
This is a description of the way an identity provider communicates with Supabase Auth. When using the Redirect binding, the communication occurs using HTTP 301 redirects. When it's `POST`, it's using `POST` requests sent with `<form>` elements on a page. When using Artifact, it's using a more secure exchange over a Redirect or `POST`.
|
||||
- **RelayState**
|
||||
- **`RelayState`**
|
||||
State used by Supabase Auth to hold information about a request to verify the identity of a user.
|
||||
|
||||
## Important SAML 2.0 information
|
||||
@@ -62,12 +62,12 @@ Below is information about your project's SAML 2.0 configuration which you can s
|
||||
|
||||
| Name | Value |
|
||||
| --------------------------- | ----------------------------------------------------------------------- |
|
||||
| EntityID | `https://<project>.supabase.co/auth/v1/sso/saml/metadata` |
|
||||
| `EntityID` | `https://<project>.supabase.co/auth/v1/sso/saml/metadata` |
|
||||
| Metadata URL | `https://<project>.supabase.co/auth/v1/sso/saml/metadata` |
|
||||
| Metadata URL<br/>(download) | `https://<project>.supabase.co/auth/v1/sso/saml/metadata?download=true` |
|
||||
| ACS URL | `https://<project>.supabase.co/auth/v1/sso/saml/acs` |
|
||||
| SLO URL | `https://<project>.supabase.co/auth/v1/sso/slo` |
|
||||
| NameID | Required `emailAddress` or `persistent` |
|
||||
| `NameID` | Required `emailAddress` or `persistent` |
|
||||
|
||||
Note that SLO (Single Logout) is not supported at this time with Supabase Auth as it is a rarely supported feature by identity providers. However, the URL is registered and advertised for when this does become available.
|
||||
|
||||
@@ -155,7 +155,7 @@ Commonly used SAML 2.0 Identity Providers that support Metadata URLs:
|
||||
|
||||
Commonly used SAML 2.0 Identity Providers that only support Metadata XML files:
|
||||
|
||||
- Google Workspaces (GSuite)
|
||||
- Google Workspaces (G Suite)
|
||||
- Any self-hosted or on-prem identity provider behind a VPN
|
||||
|
||||
Once you've obtained the SAML 2.0 Metadata XML file or URL you can [establish a connection](/docs/reference/cli/supabase-sso-add) with your project's Supabase Auth server by running:
|
||||
@@ -323,7 +323,7 @@ Most SAML 2.0 identity providers use Lightweight Directory Access Protocol (LDAP
|
||||
|
||||
**Accessing the stored attributes**
|
||||
|
||||
The stored attributes, once mapped, show up in the access token (a JWT) of the user. If you need to look these values up in the database, you can find them in the `auth.identities` table under the `identity_data` JSON column. Identities created for SSO providers have `sso:<uuid-of-provider>` in the `provider` column, while `id` contains the unique NameID of the user account.
|
||||
The stored attributes, once mapped, show up in the access token (a JWT) of the user. If you need to look these values up in the database, you can find them in the `auth.identities` table under the `identity_data` JSON column. Identities created for SSO providers have `sso:<uuid-of-provider>` in the `provider` column, while `id` contains the unique `NameID` of the user account.
|
||||
|
||||
Furthermore, you can find the same identity data under `raw_app_meta_data` inside `auth.users`.
|
||||
|
||||
@@ -382,6 +382,12 @@ supabase sso show <provider-id> --project-ref <your-project>
|
||||
|
||||
You can use the `-o json` flag to output the information as JSON, should you need to. Other formats may be supported, please use `--help` to see all available options.
|
||||
|
||||
## Pricing
|
||||
|
||||
<PricingMauSso />
|
||||
|
||||
For a detailed breakdown of how charges are calculated, refer to [Manage Monthly Active SSO Users usage](/docs/guides/platform/manage-your-usage/monthly-active-users-sso).
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
### How do I publish my application to an identity provider's marketplace?
|
||||
|
||||
@@ -41,7 +41,7 @@ The JSON object starts out looking something like this:
|
||||
|
||||
Just note that the more data you store in your token, the longer the encoded string will be.
|
||||
|
||||
When we want to send the JWT to the user, we first encode the data using an algorithm such as `HS256`. There are many libraries (and several different algorithms) that can be used to do this encoding/decoding, such as [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken). The signing is as simple as:
|
||||
When we want to send the JWT to the user, we first encode the data using an algorithm such as `HS256`. There are many libraries (and several different algorithms) that can be used to do this encoding/decoding, such as [`jsonwebtoken`](https://www.npmjs.com/package/jsonwebtoken). The signing is as simple as:
|
||||
|
||||
```js
|
||||
// from https://replit.com/@awalias/jsonwebtokens#index.js
|
||||
|
||||
@@ -113,14 +113,14 @@ With Deep Linking, you can configure this redirect to open a specific page. This
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
// Currently supabase_flutter supports deep links on Android, iOS, Web, MacOS and Windows.
|
||||
// Currently supabase_flutter supports deep links on Android, iOS, Web, macOS and Windows.
|
||||
|
||||
### Deep link config
|
||||
|
||||
- Go to your [auth settings](https://supabase.com/dashboard/project/_/auth/url-configuration) page.
|
||||
- You need to enter your app redirect callback on `Additional Redirect URLs` field.
|
||||
|
||||
The redirect callback url should have this format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.flutterquickstart://login-callback` is just an example, you can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
|
||||
The redirect callback URL should have this format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.flutterquickstart://login-callback` is just an example, you can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
|
||||
|
||||

|
||||
|
||||
@@ -269,11 +269,10 @@ With Deep Linking, you can configure this redirect to open a specific page. This
|
||||
|
||||
You can achieve it with [url_protocol](https://pub.dev/packages/url_protocol) inside you app.
|
||||
|
||||
The most relevant solution is to include those registry modifications into your installer to allow
|
||||
for deregistration.
|
||||
The most relevant solution is to include those registry modifications into your installer to allow for deregistration.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="macos" label="MacOS">
|
||||
<TabPanel id="macos" label="macOS">
|
||||
|
||||
Add this XML chapter in your `macos/Runner/Info.plist` inside `<plist version="1.0"><dict>` chapter:
|
||||
|
||||
@@ -311,7 +310,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This
|
||||
1. Go to your [auth settings](https://supabase.com/dashboard/project/_/auth/url-configuration) page.
|
||||
2. Enter your app redirect URL in the `Additional Redirect URLs` field. This is the URL that the user gets redirected to after clicking a magic link.
|
||||
|
||||
The redirect callback url should have the format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.user-management://login-callback` is just an example. You can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
|
||||
The redirect callback URL should have the format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.user-management://login-callback` is just an example. You can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
|
||||
|
||||

|
||||
|
||||
@@ -349,7 +348,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This
|
||||
1. Go to your [auth settings](https://supabase.com/dashboard/project/_/auth/url-configuration) page.
|
||||
2. Enter your app redirect URL in the `Additional Redirect URLs` field. This is the URL that the user gets redirected to after clicking a magic link.
|
||||
|
||||
The redirect callback url should have the format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.user-management://login-callback` is just an example. You can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
|
||||
The redirect callback URL should have the format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.user-management://login-callback` is just an example. You can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
|
||||
|
||||
Now, edit the Android manifest to make sure the app opens when the user clicks on the magic link.
|
||||
|
||||
@@ -394,7 +393,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This
|
||||
}
|
||||
```
|
||||
|
||||
The user will now be authenticated when your app receives a valid deeplink!
|
||||
The user will now be authenticated when your app receives a valid deep link!
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
@@ -465,7 +465,7 @@ data = supabase.auth.sign_up({
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
When your user signs in, call [signInWithPassword()](/docs/reference/javascript/auth-signinwithpassword) with their email address and password:
|
||||
When your user signs in, call [`signInWithPassword()`](/docs/reference/javascript/auth-signinwithpassword) with their email address and password:
|
||||
|
||||
```js
|
||||
async function signInWithEmail() {
|
||||
@@ -479,7 +479,7 @@ async function signInWithEmail() {
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
When your user signs in, call [signInWithPassword()](/docs/reference/dart/auth-signinwithpassword) with their email address and password:
|
||||
When your user signs in, call [`signInWithPassword()`](/docs/reference/dart/auth-signinwithpassword) with their email address and password:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithEmail() async {
|
||||
@@ -576,7 +576,7 @@ try await supabase.auth.resetPasswordForEmail(
|
||||
<TabPanel id="kotlin" label="Kotlin">
|
||||
|
||||
```kotlin
|
||||
supabase.gotrue.sendRecoveryEmail(
|
||||
supabase.auth.resetPasswordForEmail(
|
||||
email = "valid.email@supabase.io",
|
||||
redirectUrl = "http://example.com/account/update-password"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ description: 'Learn about logging in to your platform using SMS one-time passwor
|
||||
|
||||
Phone Login is a method of authentication that allows users to log in to a website or application without using a password. The user authenticates through a one-time password (OTP) sent via a channel (SMS or WhatsApp).
|
||||
|
||||
<Admonition type="info">
|
||||
<Admonition type="note">
|
||||
|
||||
At this time, `WhatsApp` is only supported as a channel for the Twilio and Twilio Verify Providers.
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ hideToc: true
|
||||
|
||||
Start the development server, go to http://localhost:3000 in a browser, and you should see the contents of `app/page.tsx`.
|
||||
|
||||
To sign up a new user, navigate to http://localhost:3000/login, and click `Sign Up Now`.
|
||||
To sign up a new user, navigate to http://localhost:3000/sign-up, and click `Sign up`. *NOTE: .env.example must be renamed to .env.local before this route becomes available*
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
|
||||
@@ -376,7 +376,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
|
||||
<TabPanel id="layout" label="Root Layout Load">
|
||||
|
||||
Page components can get access to the supabase client from the `data` object due to this load function.
|
||||
Page components can get access to the Supabase client from the `data` object due to this load function.
|
||||
|
||||
```ts +layout.ts
|
||||
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
||||
|
||||
@@ -163,12 +163,13 @@ To prevent TypeScript errors, add type definitions for the new `event.locals` pr
|
||||
|
||||
```ts src/app.d.ts
|
||||
import type { Session, SupabaseClient, User } from '@supabase/supabase-js'
|
||||
import type { Database } from './database.types.ts' // import generated types
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
supabase: SupabaseClient
|
||||
supabase: SupabaseClient<Database>
|
||||
safeGetSession: () => Promise<{ session: Session | null; user: User | null }>
|
||||
session: Session | null
|
||||
user: User | null
|
||||
|
||||
@@ -63,7 +63,7 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app,
|
||||
6. Create a signing **Key** in the [Keys](https://developer.apple.com/account/resources/authkeys/list) section of the Apple Developer Console. You can use this key to generate a secret key using the tool below, which is added to your Supabase project's Auth configuration. Make sure you safely store the `AuthKey_XXXXXXXXXX.p8` file. If you ever lose access to it, or make it public accidentally please revoke it from the Apple Developer Console and create a new one immediately. You will have to generate a new secret key using this file every 6 months, so make sure you schedule a recurring meeting in your calendar!
|
||||
7. Finally, add the information you configured above to the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers).
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="tip">
|
||||
|
||||
Use this tool to generate a new Apple client secret. No keys leave your browser! Be aware that this tool does not currently work in Safari, so please use Firefox or a Chrome-based browser instead.
|
||||
|
||||
@@ -280,7 +280,7 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app,
|
||||
6. Create a signing **Key** in the [Keys](https://developer.apple.com/account/resources/authkeys/list) section of the Apple Developer Console. You can use this key to generate a secret key using the tool below, which is added to your Supabase project's Auth configuration. Make sure you safely store the `AuthKey_XXXXXXXXXX.p8` file. If you ever lose access to it, or make it public accidentally please revoke it from the Apple Developer Console and create a new one immediately. You will have to generate a new secret key using this file every 6 months, so make sure you schedule a recurring reminder in your calendar!
|
||||
7. Finally, add the information you configured above to the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers).
|
||||
|
||||
<Admonition>
|
||||
<Admonition type="tip">
|
||||
|
||||
Use this tool to generate a new Apple client secret. No keys leave your browser! Be aware that this tool does not currently work in Safari, so please use Firefox or a Chrome-based browser instead.
|
||||
|
||||
@@ -348,12 +348,12 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app,
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
|
||||
|
||||
<TabPanel id="kotlin" label="Kotlin">
|
||||
|
||||
|
||||
## Using native sign in with Apple in Kotlin
|
||||
|
||||
When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On iOS it uses Native Apple Login automatically and on other platforms it uses `gotrue.signInWith(Apple)`.
|
||||
|
||||
When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On iOS it uses Native Apple Login automatically and on other platforms it uses `gotrue.signInWith(Apple)`.
|
||||
|
||||
**Initialize the Supabase Client**
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ Supabase Auth requires that Azure returns a valid email address. Therefore you m
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `azure` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `azure` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithAzure() {
|
||||
@@ -142,7 +142,7 @@ async function signInWithAzure() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `azure` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `azure` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithAzure() async {
|
||||
|
||||
@@ -4,7 +4,7 @@ title: 'Login with Bitbucket'
|
||||
description: 'Add Bitbucket OAuth to your Supabase project'
|
||||
---
|
||||
|
||||
To enable Bitbucket Auth for your project, you need to set up a BitBucket OAuth application and add the application credentials to your Supabase Dashboard.
|
||||
To enable Bitbucket Auth for your project, you need to set up a Bitbucket OAuth application and add the application credentials to your Supabase Dashboard.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -57,7 +57,7 @@ Setting up Bitbucket logins for your application consists of 3 parts:
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `bitbucket` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `bitbucket` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithBitbucket() {
|
||||
@@ -70,7 +70,7 @@ async function signInWithBitbucket() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `bitbucket` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `bitbucket` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithBitbucket() async {
|
||||
|
||||
@@ -56,7 +56,7 @@ Setting up Discord logins for your application consists of 3 parts:
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `discord` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `discord` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithDiscord() {
|
||||
@@ -69,7 +69,7 @@ async function signInWithDiscord() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `discord` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `discord` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithDiscord() async {
|
||||
|
||||
@@ -73,7 +73,7 @@ Under `Build Your App`, click on `Use Cases` screen. From there, do the followin
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `facebook` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `facebook` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithFacebook() {
|
||||
@@ -86,7 +86,7 @@ async function signInWithFacebook() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `facebook` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `facebook` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithFacebook() async {
|
||||
@@ -102,7 +102,7 @@ Future<void> signInWithFacebook() async {
|
||||
</TabPanel>
|
||||
<TabPanel id="swift" label="Swift">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/swift/auth-signinwithoauth) with `facebook` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/swift/auth-signinwithoauth) with `facebook` as the `provider`:
|
||||
|
||||
```swift
|
||||
func signInWithFacebook() async throws {
|
||||
|
||||
@@ -57,7 +57,7 @@ Setting up Figma logins for your application consists of 3 parts:
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `figma` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `figma` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithFigma() {
|
||||
@@ -70,7 +70,7 @@ async function signInWithFigma() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/flutter/auth-signinwithoauth) with `figma` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/flutter/auth-signinwithoauth) with `figma` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithFigma() async {
|
||||
|
||||
@@ -51,7 +51,7 @@ Copy your new OAuth credentials
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `github` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `github` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithGithub() {
|
||||
@@ -64,7 +64,7 @@ async function signInWithGithub() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `github` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `github` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithGithub() async {
|
||||
@@ -80,7 +80,7 @@ Future<void> signInWithGithub() async {
|
||||
</TabPanel>
|
||||
<TabPanel id="swift" label="Swift">
|
||||
|
||||
When your user signs in, call [signInWithOAuth](/docs/reference/swift/auth-signinwithoauth) with `.github` as the `Provider`:
|
||||
When your user signs in, call [`signInWithOAuth`](/docs/reference/swift/auth-signinwithoauth) with `.github` as the `Provider`:
|
||||
|
||||
```swift
|
||||
func signInWithGithub() async throws {
|
||||
|
||||
@@ -54,7 +54,7 @@ Setting up GitLab logins for your application consists of 3 parts:
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `gitlab` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `gitlab` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithGitLab() {
|
||||
@@ -67,7 +67,7 @@ async function signInWithGitLab() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `gitlab` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `gitlab` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithGitLab() async {
|
||||
|
||||
@@ -27,7 +27,7 @@ To support Sign In with Google, you need to configure the Google provider for yo
|
||||
For web applications, you can set up your signin button two different ways:
|
||||
|
||||
- Use your own [application code](#application-code) for the button.
|
||||
- Use [Google's pre-built sign-in or OneTap flows](#google-pre-built).
|
||||
- Use [Google's pre-built sign-in or One Tap flows](#google-pre-built).
|
||||
|
||||
### Application code configuration
|
||||
|
||||
@@ -78,7 +78,7 @@ To use Google's pre-built signin buttons:
|
||||
1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for.
|
||||
- For Android, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app.
|
||||
- You will have a different set of SHA-1 certificate fingerprint for testing locally and going to production. Make sure to add both to the Google Cloud Console. and add all of the Client IDs to Supabase dashboard.
|
||||
- For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple AppStore.
|
||||
- For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple App Store.
|
||||
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. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_.
|
||||
|
||||
@@ -94,7 +94,7 @@ To use Google's pre-built signin buttons:
|
||||
- For both Android and iOS, you will need to create a Web client ID in the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).
|
||||
- For Android, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app.
|
||||
- You will have a different set of SHA-1 certificate fingerprint for testing locally and going to production. Make sure to add both to the Google Cloud Console. and add all of the Client IDs to Supabase dashboard.
|
||||
- For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple AppStore.
|
||||
- For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple App Store.
|
||||
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_. If you need iOS support, enable the `Skip nonce check` option.
|
||||
4. For iOS, add the `CFBundleURLTypes` attributes below into the `/ios/Runner/Info.plist` file.
|
||||
@@ -129,7 +129,7 @@ To use Google's pre-built signin buttons:
|
||||
|
||||
<TabPanel id="swift" label="Swift">
|
||||
|
||||
Google sign-in with Supabase is done through the [GoogleSignIn-iOS](https://github.com/google/GoogleSignIn-iOS) package.
|
||||
Google sign-in with Supabase is done through the [`GoogleSignIn-iOS`](https://github.com/google/GoogleSignIn-iOS) package.
|
||||
|
||||
When the user provides consent, Google issues an identity token (commonly abbreviated as ID token) that is then sent to your project's Supabase Auth server. When valid, a new user session is started by issuing an access and refresh token from Supabase Auth.
|
||||
|
||||
@@ -187,7 +187,7 @@ To use Google's pre-built signin buttons:
|
||||
1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for.
|
||||
|
||||
- For Android, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app.
|
||||
- For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple AppStore.
|
||||
- For iOS, use the instructions on screen to provide the app Bundle ID, and App Store ID and Team ID if the app is already published on the Apple App Store.
|
||||
|
||||
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. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Client IDs_.
|
||||
@@ -548,7 +548,7 @@ Future<void> _nativeGoogleSignIn() async {
|
||||
|
||||
### Web, macOS, Windows, and Linux
|
||||
|
||||
Google sign-in with Supabase on Web, macOS, Windows, and Linux is done through the [signInWithOAuth](docs/reference/dart/auth-signinwithoauth) method.
|
||||
Google sign-in with Supabase on Web, macOS, Windows, and Linux is done through the [`signInWithOAuth`](docs/reference/dart/auth-signinwithoauth) method.
|
||||
|
||||
This method of signing in is web based, and will open a browser window to perform the sign in. For non-web platforms, the user is brought back to the app via [deep linking](/docs/guides/auth/native-mobile-deep-linking?platform=flutter).
|
||||
|
||||
@@ -595,7 +595,7 @@ implementation ("com.google.android.libraries.identity.googleid:googleid:<latest
|
||||
implementation("androidx.credentials:credentials-play-services-auth:<latest version>")
|
||||
```
|
||||
|
||||
Add the following proguard rules to your `proguard-rules.pro` file:
|
||||
Add the following ProGuard rules to your `proguard-rules.pro` file:
|
||||
|
||||
```proguard
|
||||
-if class androidx.credentials.CredentialManager
|
||||
|
||||
@@ -52,7 +52,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
|
||||
|
||||
- Go to `Product settings` > `Kakao Login` > `Security`.
|
||||
- Click on the `Kakao Login` switch to enable Kakao Login.
|
||||
- Click on `generate code` at the bottom to generate the `Client secret code` -- this will serve as a `client_secret` for your supabase project.
|
||||
- Click on `generate code` at the bottom to generate the `Client secret code` -- this will serve as a `client_secret` for your Supabase project.
|
||||
- Make sure you enabled `Client secret code` by selecting `enable` from the `Activation state` section.
|
||||
|
||||
## Additional configurations on Kakao Developers portal
|
||||
@@ -79,7 +79,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `kakao` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `kakao` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithKakao() {
|
||||
@@ -92,7 +92,7 @@ async function signInWithKakao() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `kakao` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `kakao` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithKakao() async {
|
||||
|
||||
@@ -10,16 +10,16 @@ To enable Keycloak Auth for your project, you need to set up an Keycloak OAuth a
|
||||
|
||||
To get started with Keycloak, you can run it in a docker container with: `docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:latest start-dev`
|
||||
|
||||
This guide will be assuming that you are running keycloak in a docker container as described in the command above.
|
||||
This guide will be assuming that you are running Keycloak in a docker container as described in the command above.
|
||||
|
||||
Keycloak OAuth consists of five broad steps:
|
||||
|
||||
- Create a new client in your specified keycloak realm.
|
||||
- Create a new client in your specified Keycloak realm.
|
||||
- Obtain the `issuer` from the "OpenID Endpoint Configuration". This will be used as the `Keycloak URL`.
|
||||
- Ensure that the new client has the "Client Protocol" set to "openid-connect" and the "Access Type" is set to "confidential".
|
||||
- Ensure that the new client has the "Client Protocol" set to `openid-connect` and the "Access Type" is set to "confidential".
|
||||
- The `Client ID` of the client created will be used as the `client id`.
|
||||
- Obtain the `Secret` from the credentials tab which will be used as the `client secret`.
|
||||
- Add the callback url of your application to your allowlist.
|
||||
- Add the callback URL of your application to your allowlist.
|
||||
|
||||
## Access your Keycloak admin console
|
||||
|
||||
@@ -43,7 +43,7 @@ The "Client ID" of the created client will serve as the `client_id` when you mak
|
||||
|
||||
After you've created the client successfully, ensure that you set the following settings:
|
||||
|
||||
1. The "Client Protocol" should be set to "openid-connect".
|
||||
1. The "Client Protocol" should be set to `openid-connect`.
|
||||
2. The "Access Type" should be set to "confidential".
|
||||
3. The "Valid Redirect URIs" should be set to: `https://<project-ref>.supabase.co/auth/v1/callback`.
|
||||
|
||||
@@ -72,7 +72,7 @@ Since Keycloak version 22, the `openid` scope must be passed. Add this to the [`
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `keycloak` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `keycloak` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithKeycloak() {
|
||||
@@ -88,7 +88,7 @@ async function signInWithKeycloak() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `keycloak` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `keycloak` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithKeycloak() async {
|
||||
@@ -163,5 +163,5 @@ suspend fun signOut() {
|
||||
|
||||
## Resources
|
||||
|
||||
- You can find the keycloak openid endpoint configuration under the realm settings.
|
||||
- You can find the Keycloak OpenID endpoint configuration under the realm settings.
|
||||

|
||||
@@ -59,7 +59,7 @@ Ensure that the appropriate scopes have been added under OAuth 2.0 Scopes at the
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `linkedin_oidc` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `linkedin_oidc` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithLinkedIn() {
|
||||
@@ -72,7 +72,7 @@ async function signInWithLinkedIn() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `linkedin_oidc` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `linkedin_oidc` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithLinkedIn() async {
|
||||
|
||||
@@ -23,7 +23,7 @@ Setting up Notion logins for your application consists of 3 parts:
|
||||
|
||||
- Once logged in, go to [notion.so/my-integrations](https://notion.so/my-integrations) and create a new integration.
|
||||
- When creating your integration, ensure that you select "Public integration" under "Integration type" and "Read user information including email addresses" under "Capabilities".
|
||||
- You will need to add a redirect uri, see [Add the redirect uri](#add-the-redirect-uri)
|
||||
- You will need to add a redirect URI, see [Add the redirect URI](#add-the-redirect-uri)
|
||||
- Once you've filled in the necessary fields, click "Submit" to finish creating the integration.
|
||||
|
||||

|
||||
@@ -57,7 +57,7 @@ Setting up Notion logins for your application consists of 3 parts:
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `notion` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `notion` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithNotion() {
|
||||
@@ -70,7 +70,7 @@ async function signInWithNotion() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `notion` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `notion` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithNotion() async {
|
||||
|
||||
@@ -75,7 +75,7 @@ Under `Scopes`:
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `slack_oidc` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `slack_oidc` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithSlack() {
|
||||
@@ -88,7 +88,7 @@ async function signInWithSlack() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `slack` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `slack` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithSlack() async {
|
||||
|
||||
@@ -69,7 +69,7 @@ The following outlines the steps to sign in using Spotify with Supabase Auth.
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `spotify` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `spotify` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithSpotify() {
|
||||
@@ -82,7 +82,7 @@ async function signInWithSpotify() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `spotify` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `spotify` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithSpotify() async {
|
||||
|
||||
@@ -41,7 +41,7 @@ Setting up Twitch logins for your application consists of 3 parts:
|
||||
- Enter the name of your application.
|
||||
- Type or paste your `OAuth Redirect URL` (the callback URL from the previous step.)
|
||||
- Select a category for your app.
|
||||
- Check the Captcha box and click `Create`.
|
||||
- Check the CAPTCHA box and click `Create`.
|
||||
|
||||
## Retrieve your Twitch OAuth client ID and client secret
|
||||
|
||||
@@ -72,7 +72,7 @@ Setting up Twitch logins for your application consists of 3 parts:
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitch` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `twitch` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithTwitch() {
|
||||
@@ -85,7 +85,7 @@ async function signInWithTwitch() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `twitch` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `twitch` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithTwitch() async {
|
||||
|
||||
@@ -63,7 +63,7 @@ Setting up Twitter logins for your application consists of 3 parts:
|
||||
|
||||
<CreateClientSnippet />
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitter` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/javascript/auth-signinwithoauth) with `twitter` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithTwitter() {
|
||||
@@ -76,7 +76,7 @@ async function signInWithTwitter() {
|
||||
</TabPanel>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/dart/auth-signinwithoauth) with `twitter` as the `provider`:
|
||||
When your user signs in, call [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) with `twitter` as the `provider`:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithTwitter() async {
|
||||
|
||||
Loaded 100 of 893 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user