Merge branch 'master' into docs/database-api

This commit is contained in:
dannykng committed 2023-01-27 17:00:11 -08:00
commit eda821e21b
322 files changed
+13928 -4808

No files matched your search

+19 -6
View File
@@ -1,7 +1,20 @@
node_modules/
# Root folder of this turbo monorepo
# Doesn't send useless context to Docker daemon
.git
.github
about
apps
docker
examples
i18n
**/node_modules
spec
supabase
tests
**/.next
.vercel
.next
.env.local
.env.production
.env.dev
.env.*
.env.*
.dockerignore
studio/Dockerfile
+20
View File
@@ -0,0 +1,20 @@
name: reviewdog
on:
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
misspell:
name: runner / misspell
runs-on: ubuntu-latest
steps:
- name: Check out code.
uses: actions/checkout@v1
- name: misspell
uses: reviewdog/action-misspell@v1
with:
github_token: ${{ secrets.github_token }}
locale: "US"
reporter: github-pr-review
level: error
exclude: "*.css"
+6 -2
View File
@@ -19,12 +19,16 @@ jobs:
permissions:
contents: read
packages: write
id-token: write
steps:
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.PROD_AWS_ROLE }}
aws-region: us-east-1
- uses: docker/login-action@v2
with:
registry: public.ecr.aws
username: ${{ secrets.PROD_ACCESS_KEY_ID }}
password: ${{ secrets.PROD_SECRET_ACCESS_KEY }}
- uses: docker/login-action@v2
with:
registry: ghcr.io
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Supabase
Copyright 2023 Supabase
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,12 +14,22 @@ import { useInView } from 'react-intersection-observer'
*/
interface Props {
tag: string
tag?: string
parseAnchors?: boolean
customAnchor?: string
}
const Heading: React.FC<Props> = ({ tag, children }) => {
/**
* This TOC is used in .mdx files and in .tsx files.
* In mdx files, we need to parse the content and format them to match the
* expected tocList format (text, link,level).
*
* In tsx files, we can generate this tocList directly. For these files, we don't
* need to parse the <a> and generate anchors
*/
const Heading: React.FC<Props> = ({ tag, parseAnchors = false, customAnchor, children }) => {
const HeadingTag = `${tag}` as any
const anchor = getAnchor(children)
const anchor = customAnchor ? customAnchor : getAnchor(children)
const link = `#${anchor}`
const { ref } = useInView({
@@ -33,7 +43,7 @@ const Heading: React.FC<Props> = ({ tag, children }) => {
return (
<HeadingTag id={anchor} ref={ref} className="group scroll-mt-24">
{removeAnchor(children)}
{parseAnchors ? removeAnchor(children) : children}
{anchor && (
<a href={link} className="ml-2 opacity-0 group-hover:opacity-100 transition">
#
+7 -12
View File
@@ -1,7 +1,7 @@
import React, { useState } from 'react'
import KJUR from 'jsrsasign'
import CodeBlock from './CodeBlock/CodeBlock'
import { Button } from 'ui'
import { Button, Select, Input } from 'ui'
const JWT_HEADER = { alg: 'HS256', typ: 'JWT' }
const now = new Date()
@@ -46,9 +46,8 @@ export default function JwtGenerator({}) {
<div>
<div className="grid mb-8">
<label for="secret">JWT Secret:</label>
<input
<Input
id="secret"
className="border rounded bg-gray-200 w-full"
type="text"
placeholder="JWT Secret (at least 32 characters)"
value={jwtSecret}
@@ -57,19 +56,15 @@ export default function JwtGenerator({}) {
</div>
<div className="grid mb-8">
<label for="service">Preconfigured Payload:</label>
<select
id="service"
onChange={handleKeySelection}
className="border rounded bg-gray-200 w-full"
>
<option value="anon">ANON_KEY</option>
<option value="service">SERVICE_KEY</option>
</select>
<Select id="service" onChange={handleKeySelection}>
<Select.Option value="anon">ANON_KEY</Select.Option>
<Select.Option value="service">SERVICE_KEY</Select.Option>
</Select>
</div>
<div className="grid mb-8">
<label for="token">Payload:</label>
<textarea
<Input.TextArea
id="token"
type="text"
rows="5"
@@ -0,0 +1,108 @@
If you upload additional profile photos, they'll accumulate
in the `avatars` bucket because of their random names with only the latest being referenced
from `public.profiles` and the older versions getting orphaned.
To automatically remove obsolete storage objects, extend the database
triggers. Note that it is not sufficient to delete the objects from the
`storage.objects` table because that would orphan and leak the actual storage objects in
the S3 backend. Instead, invoke the storage API within Postgres via the `http` extension.
Enable the [http extension for the `extensions` schema](https://app.supabase.com/project/_/database/extensions) in the Dashboard.
Then, define the following SQL functions in the SQL Editor to delete
storage objects via the API:
```SQL
create or replace function delete_storage_object(bucket text, object text, out status int, out content varchar)
returns record
language 'plpgsql'
security definer
as $$
declare
project_url varchar := '<YOURPROJECTURL>';
service_role_key varchar := '<YOURSERVICEROLEKEY>'; -- full access needed
url varchar := project_url||'/storage/v1/object/'||bucket||'/'||object;
begin
select
into status, content
result.status::int, result.content::varchar
FROM extensions.http((
'DELETE',
url,
ARRAY[extensions.http_header('authorization','Bearer '||service_role_key)],
NULL,
NULL)::extensions.http_request) as result;
end;
$$;
create or replace function delete_avatar(avatar_url text, out status int, out content varchar)
returns record
language 'plpgsql'
security definer
as $$
begin
select
into status, content
result.status, result.content
from public.delete_storage_object('avatars', avatar_url) as result;
end;
$$;
```
Next, add a trigger that removes any obsolete avatar whenever the
profile is updated or deleted:
```SQL
create or replace function delete_old_avatar()
returns trigger
language 'plpgsql'
security definer
as $$
declare
status int;
content varchar;
begin
if coalesce(old.avatar_url, '') <> ''
and (tg_op = 'DELETE' or (old.avatar_url <> new.avatar_url)) then
select
into status, content
result.status, result.content
from public.delete_avatar(old.avatar_url) as result;
if status <> 200 then
raise warning 'Could not delete avatar: % %', status, content;
end if;
end if;
if tg_op = 'DELETE' then
return old;
end if;
return new;
end;
$$;
create trigger before_profile_changes
before update of avatar_url or delete on public.profiles
for each row execute function public.delete_old_avatar();
```
Finally, delete the `public.profile` row before a user is deleted.
If this step is omitted, you won't be able to delete users without
first manually deleting their avatar image.
```SQL
create or replace function delete_old_profile()
returns trigger
language 'plpgsql'
security definer
as $$
begin
delete from public.profiles where id = old.id;
return old;
end;
$$;
create trigger before_delete_user
before delete on auth.users
for each row execute function public.delete_old_profile();
```
@@ -264,6 +264,7 @@ export const menuItems: NavMenu = {
{ name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons', items: [] },
{ name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] },
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
{ name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes', items: [] },
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
{
@@ -275,6 +276,7 @@ export const menuItems: NavMenu = {
{ name: 'Performance Tuning', url: '/guides/platform/performance', items: [] },
{ name: 'Permissions', url: '/guides/platform/permissions', items: [] },
{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] },
{ name: 'Troubleshooting', url: '/guides/platform/troubleshooting', items: [] },
],
},
{
@@ -339,6 +341,7 @@ export const menuItems: NavMenu = {
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
{ name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] },
{ name: 'WeWeb', url: '/guides/integrations/weweb', items: [] },
{ name: 'ILLA', url: '/guides/integrations/illa', items: [] },
],
},
],
@@ -95,7 +95,13 @@ const home = [
level: 'reference_dart',
},
{
label: 'Tools Reference',
label: 'Python',
icon: '/img/icons/menu/reference-python',
href: '/reference/python/introduction',
level: 'reference_python',
},
{
label: 'Tools',
},
{
label: 'Management API',
@@ -106,7 +112,7 @@ const home = [
{
label: 'Supabase CLI',
icon: '/img/icons/menu/reference-cli',
href: '/reference/cli/introduction',
href: '/guides/cli',
level: 'reference_javascript',
},
],
@@ -48,6 +48,9 @@ export const gettingstarted = {
{ name: 'ReactJS', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
{ name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter', items: [] },
{ name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit', items: [] },
{ name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs', items: [] },
{ name: 'Vue', url: '/guides/getting-started/quickstarts/vue', items: [] },
],
},
{
@@ -320,6 +323,11 @@ export const auth = {
{ name: 'Overview', url: '/guides/auth/auth-helpers', items: [] },
{ name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui', items: [] },
{ name: 'Next.js', url: '/guides/auth/auth-helpers/nextjs', items: [] },
{
name: 'Next.js Server Components',
url: '/guides/auth/auth-helpers/nextjs-server-components',
items: [],
},
{ name: 'Remix', url: '/guides/auth/auth-helpers/remix', items: [] },
{ name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit', items: [] },
],
@@ -421,14 +429,30 @@ export const functions = {
label: 'Edge Functions',
url: '/guides/functions',
items: [
{ name: 'Quickstart', url: '/guides/functions/quickstart', items: [] },
{ name: 'Auth', url: '/guides/functions/auth', items: [] },
{ name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] },
{ name: 'Best Practices', url: '/guides/functions/best-practices', items: [] },
{ name: 'Quickstart', url: '/guides/functions/quickstart', items: [] },
{ name: 'CORS', url: '/guides/functions/cors', items: [] },
{ name: 'Debugging', url: '/guides/functions/debugging', items: [] },
{ name: 'Import Maps', url: '/guides/functions/import-maps', items: [] },
{ name: 'Local Development', url: '/guides/functions/local-development', items: [] },
{ name: 'Managing Secrets', url: '/guides/functions/secrets', items: [] },
{ name: 'Schedule Functions', url: '/guides/functions/schedule-functions', items: [] },
{
name: 'Examples',
url: '/guides/functions/examples',
items: [{ name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] }],
items: [
{
name: 'Cloudflare Turnstile',
url: '/guides/functions/examples/cloudflare-turnstile',
items: [],
},
{ name: 'GitHub Actions', url: '/guides/functions/examples/github-actions', items: [] },
{ name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] },
{ name: 'Storage Caching', url: '/guides/functions/examples/storage-caching', items: [] },
{ name: 'Stripe Webhooks', url: '/guides/functions/examples/stripe-webhooks', items: [] },
{ name: 'Telegram Bot', url: '/guides/functions/examples/telegram-bot', items: [] },
],
},
],
}
@@ -464,6 +488,25 @@ export const storage = {
],
}
export const supabase_cli = {
icon: 'reference-cli',
title: 'Supabase CLI',
url: '/guides/cli',
items: [
{ name: 'Overview', url: '/guides/cli', items: [] },
{ name: 'Local Development', url: '/guides/cli/local-development', items: [] },
{ name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] },
{
name: 'Reference',
url: undefined,
items: [
{ name: 'Commands', url: '/reference/cli/introduction', items: [] },
{ name: 'Configuration', url: '/reference/cli/config', items: [] },
],
},
],
}
export const platform = {
icon: 'platform',
label: 'Platform',
@@ -484,6 +527,7 @@ export const platform = {
items: [
{ name: 'Access Control', url: '/guides/platform/access-control', items: [] },
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
{ name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes', items: [] },
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
{
@@ -519,6 +563,17 @@ export const platform = {
url: undefined,
items: [{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] }],
},
{
name: 'Troubleshooting',
url: undefined,
items: [
{
name: 'HTTP and Project Issues',
url: '/guides/platform/troubleshooting',
items: [],
},
],
},
],
}
@@ -556,26 +611,6 @@ export const resources = {
},
],
},
{
name: 'Supabase CLI',
items: [
{
name: 'Overview',
url: '/guides/resources/supabase-cli',
items: [],
},
{
name: 'Local Development',
url: '/guides/resources/supabase-cli/local-development',
items: [],
},
{
name: 'Managing Environments',
url: '/guides/resources/supabase-cli/managing-environments',
items: [],
},
],
},
],
}
@@ -672,6 +707,7 @@ export const integrations = {
{ name: 'Directus', url: '/guides/integrations/directus', items: [] },
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
{ name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] },
{ name: 'ILLA', url: '/guides/integrations/illa', items: [] },
],
},
{
@@ -748,17 +784,24 @@ export const reference_javascript_v2 = {
export const reference_dart_v0 = {
icon: 'reference-dart',
title: 'dart',
title: 'Flutter',
url: '/guides/reference/dart',
parent: '/reference',
}
export const reference_dart_v1 = {
icon: 'reference-dart',
title: 'dart',
title: 'Flutter',
url: '/guides/reference/dart',
parent: '/reference',
}
export const reference_python_v2 = {
icon: 'reference-python',
title: 'Python',
url: '/guides/reference/python',
parent: '/reference',
}
export const reference_cli = {
icon: 'reference-cli',
title: 'Supabase CLI',
@@ -11,6 +11,9 @@ import spec_js_v1 from '~/../../spec/supabase_js_v1.yml' assert { type: 'yml' }
import spec_dart_v1 from '~/../../spec/supabase_dart_v1.yml' assert { type: 'yml' }
// @ts-expect-error
import spec_dart_v0 from '~/../../spec/supabase_dart_v0.yml' assert { type: 'yml' }
// @ts-expect-error
import spec_python_v2 from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' }
// import { gen_v3 } from '~/lib/refGenerator/helpers'
import apiCommonSections from '~/../../spec/common-api-sections.json'
import cliCommonSections from '~/../../spec/common-cli-sections.json'
@@ -47,6 +50,7 @@ export type RefIdOptions =
| 'reference_javascript_v2'
| 'reference_dart_v0'
| 'reference_dart_v1'
| 'reference_python_v2'
| 'reference_cli'
| 'reference_api'
| 'reference_self_hosting_auth'
@@ -56,6 +60,7 @@ export type RefIdOptions =
export type RefKeyOptions =
| 'javascript'
| 'dart'
| 'python'
| 'cli'
| 'api'
| 'self-hosting-auth'
@@ -65,16 +70,6 @@ export type RefKeyOptions =
const NavigationMenu = () => {
const router = useRouter()
let version = ''
if (router.asPath.includes('v1')) {
version = '_v1'
}
if (router.asPath.includes('v0')) {
version = '_v0'
}
function handleRouteChange(url: string) {
switch (url) {
case `/docs`:
@@ -110,6 +105,9 @@ const NavigationMenu = () => {
case url.includes(`/docs/guides/integrations`) && url:
menuState.setMenuLevelId('integrations')
break
case url.includes(`/docs/guides/cli`) && url:
menuState.setMenuLevelId('supabase_cli')
break
// JS v1
case url.includes(`/docs/reference/javascript/v1`) && url:
menuState.setMenuLevelId('reference_javascript_v1')
@@ -126,6 +124,13 @@ const NavigationMenu = () => {
case url.includes(`/docs/reference/dart`) && url:
menuState.setMenuLevelId('reference_dart_v1')
break
// puthon v2 (latest)
case url.includes(`/docs/reference/python`) && url:
menuState.setMenuLevelId('reference_python_v2')
break
case url.includes(`/docs/reference/cli/config`) && url:
menuState.setMenuLevelId('supabase_cli')
break
case url.includes(`/docs/reference/cli`) && url:
menuState.setMenuLevelId('reference_cli')
break
@@ -165,6 +170,7 @@ const NavigationMenu = () => {
const isFunctionsActive = 'functions' === level
const isRealtimeActive = 'realtime' === level
const isStorageActive = 'storage' === level
const issupabase_cliActive = 'supabase_cli' === level
const isPlatformActive = 'platform' === level
const isResourcesActive = 'resources' === level
const isSelfHosting = 'self_hosting' === level
@@ -175,6 +181,7 @@ const NavigationMenu = () => {
const isReference_Javascript_V2 = 'reference_javascript_v2' === level
const isReference_Dart_V0 = 'reference_dart_v0' === level
const isReference_Dart_V1 = 'reference_dart_v1' === level
const isReference_Python_V2 = 'reference_python_v2' === level
const isReference_Cli = 'reference_cli' === level
const isReference_Api = 'reference_api' === level
const isReference_Self_Hosting_Auth = 'reference_self_hosting_auth' === level
@@ -191,6 +198,7 @@ const NavigationMenu = () => {
<NavigationMenuGuideList id={'functions'} active={isFunctionsActive} />
<NavigationMenuGuideList id={'realtime'} active={isRealtimeActive} />
<NavigationMenuGuideList id={'storage'} active={isStorageActive} />
<NavigationMenuGuideList id={'supabase_cli'} active={issupabase_cliActive} />
<NavigationMenuGuideList id={'platform'} active={isPlatformActive} />
<NavigationMenuGuideList id={'resources'} active={isResourcesActive} />
<NavigationMenuGuideList id={'self_hosting'} active={isSelfHosting} />
@@ -229,6 +237,14 @@ const NavigationMenu = () => {
lib="dart"
spec={spec_dart_v1}
/>
<NavigationMenuRefList
key={'reference-python-menu-v2'}
id={'reference_python_v2'}
active={isReference_Python_V2}
commonSections={libCommonSections}
lib="python"
spec={spec_python_v2}
/>
{/* // Tools */}
<NavigationMenuRefList
key={'reference-cli-menu'}
@@ -25,6 +25,10 @@ const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
active,
spec,
}) => {
const filteredSections = commonSections.filter((section) => {
return !section.excludes?.includes(id)
})
return (
<div
className={[
@@ -38,7 +42,7 @@ const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
!active ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '',
].join(' ')}
>
<NavigationMenuRefListItems id={id} lib={lib} commonSections={commonSections} spec={spec} />
<NavigationMenuRefListItems id={id} lib={lib} commonSections={filteredSections} spec={spec} />
</div>
)
}
@@ -179,7 +179,7 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
if (!sections) console.error('no common sections imported')
const menu = NavItems[id]
//console.log(menu)
const databaseFunctions = find(sections, { title: 'Database' })
? find(sections, { title: 'Database' }).items
: []
+2
View File
@@ -20,6 +20,7 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx'
import ProjectSetup from './MDX/project_setup.mdx'
import SocialProviderSetup from './MDX/social_provider_setup.mdx'
import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx'
import StorageManagement from './MDX/storage_management.mdx'
import { Mermaid } from 'mdx-mermaid/lib/Mermaid'
import InlineCodeTag from './CustomHTMLElements/InlineCode'
import React from 'react'
@@ -49,6 +50,7 @@ const components = {
ProjectSetup,
SocialProviderSetup,
SocialProviderSettingsSupabase,
StorageManagement,
Mermaid,
Extensions,
Alert: (props: any) => (
@@ -1,25 +1,12 @@
import { IconChevronRight } from '~/../../packages/ui'
import ReactMarkdown from 'react-markdown'
import { IconChevronRight, Tabs } from '~/../../packages/ui'
// @ts-expect-error
import spec from '~/../../spec/cli_v1_commands.yaml' assert { type: 'yml' }
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import Options from '~/components/Options'
import Param from '~/components/Params'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
interface ICommonFunc {
id: string
title: string
slug: string
product: string
libs: string
items: ICommonFunc[]
}
interface IRefFunctionSection {
funcData: ICommonFunc
commonFuncData: ICommonFunc
spec: any
}
import RefDetailCollapse from './RefDetailCollapse'
export type Flag = {
id: string
@@ -37,16 +24,25 @@ export type AcceptedValue = {
description?: string
}
export type Example = {
id: string
name: string
code: string
response: string
description?: string
}
export type Command = {
id: string
title: string
description: string
flags?: Flag[]
summary: string
tags?: []
links?: []
subcommands?: []
tags?: string[]
links?: string[]
subcommands?: string[]
usage?: string
examples?: Example[]
}
const CliCommandSection = (props) => {
@@ -70,17 +66,17 @@ const CliCommandSection = (props) => {
' mb-16',
].join(' ')}
>
<p className="capitalize mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
{command.summary}
</p>
{command.description ? (
<div className="prose">
<ReactMarkdown>{command.description}</ReactMarkdown>
</div>
) : (
<p className="capitalize mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
{command.summary}
</p>
)}
</header>
{/* {command.usage && (
<CodeBlock language="bash" className="relative">
{command.usage}
</CodeBlock>
)} */}
{command.subcommands.length > 0 && (
<div className="mb-3">
<h3 className="text-lg text-scale-1200 mb-3">Available Commands</h3>
@@ -126,13 +122,82 @@ const CliCommandSection = (props) => {
</div>
</div>
</RefSubLayout.Details>
<RefSubLayout.Examples>
{command.usage && (
<CodeBlock language="bash" className="relative">
{command.usage}
</CodeBlock>
)}
</RefSubLayout.Examples>
{(command.examples || command.usage) && (
<RefSubLayout.Examples>
<div className="overflow-hidden w-full">
<Tabs
defaultActiveId={`${command.id}-basic-usage`}
size="tiny"
type="rounded-pills"
scrollable
>
{command.examples ? (
command.examples.map((example) => {
const exampleId = `${command.id}-${example.id}`
return (
<Tabs.Panel
id={exampleId}
key={exampleId}
label={example.name}
className="flex flex-col gap-3"
>
<CodeBlock
className="useless-code-block-class"
language="bash"
hideLineNumbers={true}
>
{example.code}
</CodeBlock>
<RefDetailCollapse
id={`${exampleId}-response`}
label="Response"
defaultOpen={false}
>
<CodeBlock
className="useless-code-block-class rounded !rounded-tl-none !rounded-tr-none border border-scale-500"
language="bash"
hideLineNumbers={true}
>
{example.response}
</CodeBlock>
</RefDetailCollapse>
{example.description && (
<RefDetailCollapse
id={`${exampleId}-notes`}
label="Notes"
defaultOpen={false}
>
<div className="bg-scale-300 border border-scale-500 rounded !rounded-tl-none !rounded-tr-none prose max-w-none px-5 py-2">
<ReactMarkdown className="text-sm">{example.description}</ReactMarkdown>
</div>
</RefDetailCollapse>
)}
</Tabs.Panel>
)
})
) : (
// TODO: remove this block once all commands have examples
<Tabs.Panel
id={`${command.id}-basic-usage`}
key={`${command.id}-basic-usage`}
label="Basic usage"
className="flex flex-col gap-3"
>
<CodeBlock
className="useless-code-block-class"
language="bash"
hideLineNumbers={true}
>
{command.usage}
</CodeBlock>
</Tabs.Panel>
)}
</Tabs>
</div>
</RefSubLayout.Examples>
)}
</RefSubLayout.Section>
)
}
@@ -143,18 +143,24 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
label={example.name}
className="flex flex-col gap-3"
>
{example.description && (
<div className="prose">
<ReactMarkdown className="text-sm">
{example.description}
</ReactMarkdown>
</div>
)}
<CodeBlock
className="useless-code-block-class"
language={codeBlockLang}
hideLineNumbers={true}
>
{exampleString +
(example.code &&
example.code
.replace(/```/g, '')
.replace('js', '')
.replace('ts', '')
.replace('dart', ''))}
</CodeBlock>
{((tables && tables.length > 0) || sql) && (
<RefDetailCollapse
id={`${example.id}-${exampleIndex}-data`}
label="Example data source"
label="Data source"
defaultOpen={false}
>
<>
@@ -189,28 +195,14 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
</RefDetailCollapse>
)}
<CodeBlock
className="useless-code-block-class"
language={codeBlockLang}
hideLineNumbers={true}
>
{exampleString +
(example.code &&
example.code
.replace(/```/g, '')
.replace('js', '')
.replace('ts', '')
.replace('dart', ''))}
</CodeBlock>
{response && (
<RefDetailCollapse
id={`${example.id}-${exampleIndex}-response`}
label="Example response"
label="Response"
defaultOpen={false}
>
<CodeBlock
className="useless-code-block-class"
className="useless-code-block-class rounded !rounded-tl-none !rounded-tr-none border border-scale-500"
language={codeBlockLang}
hideLineNumbers={true}
>
@@ -218,6 +210,20 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
</CodeBlock>
</RefDetailCollapse>
)}
{example.description && (
<RefDetailCollapse
id={`${example.id}-${exampleIndex}-notes`}
label="Notes"
defaultOpen={false}
>
<div className="bg-scale-300 border border-scale-500 rounded !rounded-tl-none !rounded-tr-none prose max-w-none px-5 py-2">
<ReactMarkdown className="text-sm">
{example.description}
</ReactMarkdown>
</div>
</RefDetailCollapse>
)}
</Tabs.Panel>
)
})}
@@ -15,6 +15,7 @@ const CliGlobalFlagsHandler = () => {
return (
<Param
{...flag}
id={`${spec.id}-${flag.id}`}
isOptional={flag.required === undefined ? true : !flag.required}
></Param>
)
+7
View File
@@ -468,6 +468,13 @@
"installed_version": "1.1",
"comment": "generate universally unique identifiers (UUIDs)"
},
{
"name": "vector",
"schema": "extensions",
"default_version": "0.4.0",
"installed_version": null,
"comment": "vector data type with similarity search"
},
{
"name": "xml2",
"schema": null,
+2 -2
View File
@@ -2,7 +2,7 @@ const nonGeneratedReferencePages = [
'docs/reference/javascript',
'docs/reference/javascript/installing',
'docs/reference/javascript/release-notes',
'docs/reference/javascript/upgrade-guide',
'docs/reference/javascript/v1/upgrade-guide',
'docs/reference/javascript/typescript-support',
'docs/reference/javascript/v1',
'docs/reference/javascript/v1/installing',
@@ -11,7 +11,7 @@ const nonGeneratedReferencePages = [
'docs/reference/dart',
'docs/reference/dart/installing',
'docs/reference/dart/initializing',
'docs/reference/dart/upgrade-guide',
'docs/reference/dart/v0/upgrade-guide',
'docs/reference/dart/v0',
'docs/reference/api',
'docs/reference/api/release-notes',
+1 -1
View File
@@ -17,7 +17,7 @@ hideTitle: true
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
The CLI is still under development, but it contains all the functionality for working with your Supabase projects and the Supabase Platform.
- Run Supabase locally: [`supabase start`](/docs/reference/cli/usage#supabase-start)
- Run Supabase locally: [`supabase init`](/docs/reference/cli/usage#supabase-init) and [`supabase start`](/docs/reference/cli/usage#supabase-start)
- Manage database migrations: [`supabase migration`](/docs/reference/cli/usage#supabase-migration)
- CI/CD for releasing to production: [`supabase db push`](/docs/reference/cli/usage#supabase-db-push)
- Manage your Supabase projects: [`supabase projects`](/docs/reference/cli/usage#supabase-projects)
-50
View File
@@ -1,50 +0,0 @@
---
id: initializing
title: 'Initializing'
slug: initializing
---
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Initialize Supabase with the static `initialize()` method on `Supabase` class.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="flutter"
>
<TabPanel id="flutter" label="Flutter">
```dart main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://xyzcompany.supabase.co',
anonKey: 'public-anon-key',
);
runApp(MyApp());
}
```
</TabPanel>
<TabPanel id="dart" label="Other Dart Project">
```dart
final supabase = SupabaseClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
);
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
+8 -7
View File
@@ -13,11 +13,12 @@ hideTitle: true
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's Flutter
library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can
use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
Deno Edge Functions, build login and user management functionality, and manage large files.
<br />
<br />
We also provide [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
This reference documents every object and method available in Supabase's Flutter
library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can
use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
Deno Edge Functions, build login and user management functionality, and manage large files.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
</div>
@@ -0,0 +1,28 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-dart.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Flutter Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">supabase-flutter</h2>
</div>
</div>
<div className="max-w-xl">
<Admonition type="caution">
You're viewing the docs for an older version of the `supabase-flutter` library. Learn how to
[upgrade to the latest version](/docs/reference/dart/v0/upgrade-guide).
</Admonition>
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter).
You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
Deno Edge Functions, build login and user management functionality, and manage large files.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
</div>
@@ -4,23 +4,35 @@ title: Upgrade to supabase-flutter v1
description: 'Learn how to upgrade to supabase-flutter v1.'
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
supabase-flutter focuses on improving the developer experience and making it easier to use. This guide will help you upgrade from supabase-flutter v0 to v1.
supabase-flutter focuses on improving the developer experience and making it easier to use. This guide demonstrates how to upgrade from supabase-flutter v0 to v1.
## Upgrade the client library
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Update the package in your pubspec.yaml file.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```yaml
supabase_flutter: ^1.0.0
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Error handling
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
The way supabase-flutter throws error has changed in v1. In v0, errors were returned as a response. In v1, errors are thrown as exceptions. This makes it more intuitive as a Flutter developer to handle errors.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -52,11 +64,20 @@ try {
</TabPanel>
</Tabs>
## Auth classes / methods
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Auth classes and methods
### Usage of `SupabaseAuthState` and `SupabaseAuthRequiredState` classes
In v0, `SupabaseAuthState` and `SupabaseAuthRequiredState` were required to handle automatic token refresh and to listen to auth state change. In v1, `SupabaseAuthState` and `SupabaseAuthRequiredState` are deprecated, and token refresh will happen automatically just by initializing Supabase. [`onAuthStateChange`](/docs/reference/dart/upgrade-guide#listening-to-auth-state-change) can be used to action on auth state change.
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
In v0, `SupabaseAuthState` and `SupabaseAuthRequiredState` were required to handle automatic token refresh and to listen to auth state change. In v1, `SupabaseAuthState` and `SupabaseAuthRequiredState` are deprecated, and token refresh will happen automatically just by initializing Supabase. [`onAuthStateChange`](#listening-to-auth-state-change) can be used to action on auth state change.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
@@ -97,10 +118,19 @@ await Supabase.initialize(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Listening to auth state change
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
`onAuthStateChange` now returns a `Stream`.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -135,9 +165,18 @@ authSubscription.cancel();
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Sign in with email and password
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
`signIn()` has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
@@ -161,8 +200,17 @@ await supabase.auth.signInWithPassword(email: email, password: password);
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Sign in with magic link
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -185,8 +233,17 @@ await supabase.auth.signInWithOtp(email: email);
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Sign in with a third-party OAuth provider
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -218,8 +275,17 @@ await supabase.auth.signInWithOAuth(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Sign in with phone
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -248,8 +314,17 @@ await supabase.auth.signInWithPassword(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Sign in with phone using OTP
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -281,8 +356,17 @@ await supabase.auth.verifyOTP(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Reset password for email
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -312,8 +396,17 @@ await supabase.auth.resetPasswordForEmail(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Get the user's current session
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -336,8 +429,17 @@ final Session? session = supabase.auth.currentSession;
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Get the logged-in user
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -360,8 +462,17 @@ final User? user = supabase.auth.currentUser;
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Update user data for a logged-in user
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -390,16 +501,23 @@ await supabase.updateUser(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Data methods
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default.
`.insert()` / `.upsert()` / `.update()` / `.delete()` no longer return rows by default. Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
Also, calling `.execute()` at the end of the query was a requirement in v0, but deprecated in v1.
Also, calling `.execute()` at the end of the query was a requirement in v0, but in v1 `.execute` is deperecated.
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Insert without returning inserted data
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -426,8 +544,17 @@ await supabase.from('my_table').insert(data);
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Insert with returning inserted data
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -453,12 +580,21 @@ final insertedData = await supabase.from('my_table').insert(data).select();
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Realtime methods
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Stream
`.stream()` no longer needs the `.execute()` at the end. Also, filtering by `eq` is a lot easier now. `primaryKey` is now a named parameter to make it more obvious what to pass.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -486,8 +622,17 @@ supabase.from('my_table')
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Subscribe
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -522,8 +667,17 @@ channel.on(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Unsubscribe
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -545,3 +699,6 @@ await supabase.removeChannel(channel);
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -13,8 +13,15 @@ hideTitle: true
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's isomorphic JavaScript
library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to
database changes, invoke Deno Edge Functions, build login and user management functionality, and
manage large files.
<Admonition type="caution">
You're viewing the docs for an older version of the `supabase-js` library. Learn how to [upgrade
to the latest version](/docs/reference/javascript/v1/upgrade-guide).
</Admonition>
This reference documents every object and method available in Supabase's isomorphic JavaScript
library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to
database changes, invoke Deno Edge Functions, build login and user management functionality, and
manage large files.
</div>
@@ -0,0 +1,21 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-python.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Python Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">@supabase-community/supabase-py</h2>
</div>
</div>
<div className="max-w-xl">
This reference documents every object and method available in the
[supabase-py](https://github.com/supabase-community/supabase-py) library from the Supabase
community. You can use `supabase-py` to interact with your Postgres database, listen to database
changes, invoke Deno Edge Functions, build login and user management functionality, and manage
large files.
</div>
@@ -0,0 +1,10 @@
---
id: release-notes
title: Release Notes
---
The community is actively working on the library and we will be upgrading the Authentication library, `gotrue-py`, to mirror the Supabase-js v2 lib.
## Storage Transformations
We currently support [image transformations](https://supabase.com/docs/guides/storage/image-transformations) in our storage library.
@@ -1,439 +0,0 @@
---
id: upgrade-guide
title: Upgrade to supabase-js v2
description: 'Learn how to upgrade to supabase-js v2.'
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
supabase-js v2 focuses on "quality-of-life" improvements for developers and addresses some of the largest pain points in v1. v2 includes type support, a rebuilt Auth library with async methods, improved errors, and more.
No new features will be added to supabase-js v1 , but we'll continuing merging security fixes to v1, with maintenance patches for the next 3 months.
## Upgrade the client library
```sh
npm install @supabase/supabase-js@2
```
_Optionally_ if you are using custom configuration with `createClient` then follow below:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts title=src/supabaseClient.ts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
schema: 'custom',
persistSession: false,
})
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts title=src/supabaseClient.ts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
db: {
schema: 'custom',
},
auth: {
persistSession: true,
},
})
```
</TabPanel>
</Tabs>
Read more about the [constructor options](/docs/reference/javascript/release-notes#explicit-constructor-options).
### Auth methods
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
#### Sign in with email and password
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { user, error } = await supabase.auth.signIn({ email, password })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const {
data: { user },
error,
} = await supabase.auth.signInWithPassword({ email, password })
```
</TabPanel>
</Tabs>
#### Sign in with magic link
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { error } = await supabase.auth.signIn({ email })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { error } = await supabase.auth.signInWithOtp({ email })
```
</TabPanel>
</Tabs>
#### Sign in with a third-party provider
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { error } = await supabase.auth.signIn({ provider })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { error } = await supabase.auth.signInWithOAuth({ provider })
```
</TabPanel>
</Tabs>
#### Sign in with phone
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { error } = await supabase.auth.signIn({ phone, password })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { error } = await supabase.auth.signInWithPassword({ phone, password })
```
</TabPanel>
</Tabs>
#### Sign in with phone using OTP
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { error } = await supabase.auth.api.sendMobileOTP(phone)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { data, error } = await supabase.auth.signInWithOtp({ phone })
// After receiving a SMS with a OTP.
const { data, error } = await supabase.auth.verifyOtp({ phone, token })
```
</TabPanel>
</Tabs>
#### Reset password for email
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { data, error } = await supabase.auth.api.resetPasswordForEmail(email)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
```
</TabPanel>
</Tabs>
#### Get the user's current session
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const session = supabase.auth.session()
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const {
data: { session },
} = await supabase.auth.getSession()
```
</TabPanel>
</Tabs>
#### Get the logged-in user
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const user = supabase.auth.user()
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const {
data: { session },
} = await supabase.auth.getSession()
const { user } = session
```
</TabPanel>
</Tabs>
#### Update user data for a logged-in user
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { user, error } = await supabase.auth.update({ attributes })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const {
data: { user },
error,
} = await supabase.auth.updateUser({ attributes })
```
</TabPanel>
</Tabs>
#### Use a custom `access_token` JWT with Supabase
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { user, error } = supabase.auth.setAuth(access_token)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: {
headers: {
Authorization: `Bearer ${access_token}`,
},
},
})
```
</TabPanel>
</Tabs>
#### Cookie methods
The cookie-related methods like `setAuthCookie` and `getUserByCookie` have been removed.
For Next.js you can use the [Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to help you manage cookies.
If you can't use the Auth Helpers, you can use [server-side rendering](https://supabase.com/docs/guides/auth/server-side-rendering).
Some the [PR](https://github.com/supabase/gotrue-js/pull/340) for additional background information.
### Data methods
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276).
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
#### Insert and return data
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { data, error } = await supabase.auth.insert({ new_data })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { data, error } = await supabase.auth.insert({ new_data }).select()
```
</TabPanel>
</Tabs>
#### Update and return data
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id).select()
```
</TabPanel>
</Tabs>
### Realtime methods
#### Subscribe
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const userListener = supabase
.from('users')
.on('*', (payload) => handleAllEventsPayload(payload.new))
.subscribe()
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const userListener = supabase
.channel('public:user')
.on('postgres_changes', { event: '*', schema: 'public', table: 'user' }, (payload) =>
handleAllEventsPayload()
)
.subscribe()
```
</TabPanel>
</Tabs>
#### Unsubscribe
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
userListener.unsubscribe()
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
supabase.removeChannel(userListener)
```
</TabPanel>
</Tabs>
+8
View File
@@ -39,6 +39,10 @@ const levelsData = {
icon: '/docs/img/icons/menu/storage',
name: 'Storage',
},
supabase_cli: {
icon: '/docs/img/icons/menu/reference-cli',
name: 'Supabase CLI',
},
platform: {
icon: '/docs/img/icons/menu/platform',
name: 'Platform',
@@ -71,6 +75,10 @@ const levelsData = {
icon: '/docs/img/icons/menu/reference-dart',
name: 'Dart Reference v0.0',
},
reference_python_v2: {
icon: '/docs/img/icons/menu/reference-python',
name: 'Python Reference v2.0',
},
reference_cli: {
icon: '/docs/img/icons/menu/reference-cli',
name: 'CLI Reference',
-161
View File
@@ -1,161 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
title: 'Overview',
description: 'An overview of Supabase\'s architecture and product principles.',
}
Supabase is open source. We choose open source tools which are scalable and make them simple to use.
Supabase is not a 1-to-1 mapping of Firebase. While we are building many of the features that Firebase offers, we are not going about it the same way:
our technological choices are quite different; everything we use is open source; and wherever possible, we use and support existing tools rather than developing from scratch.
Most notably, we use Postgres rather than a NoSQL store. This choice was deliberate. We believe that no other database offers the functionality required to compete with Firebase,
while maintaining the scalability required to go beyond it.
## Architecture
Each Supabase project consists of several tools:
![Supabase Architecture](/docs/img/supabase-architecture.png)
### PostgreSQL (Database)
PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase.
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/)
- Language: C
### Studio (Dashboard)
An open source Dashboard for managing your database and services.
- Official Docs: [Supabase docs](/docs)
- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio)
- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE)
- Language: TypeScript
### GoTrue (Auth)
A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers.
- Official Docs: [Supabase Auth reference docs](/docs/reference/auth)
- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue)
- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE)
- Language: Go
### PostgREST (API)
A standalone web server that turns your PostgreSQL database directly into a RESTful API.
We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API.
- Official Docs: [postgrest.org](https://postgrest.org/)
- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest)
- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE)
- Language: Haskel
### Realtime (API & multiplayer)
A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes.
- Official Docs: [Supabase Realtime docs](/docs/guides/realtime)
- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime)
- License: [Apache 2](https://github.com/supabase/realtime/blob/main/LICENSE)
- Language: Elixir
### Storage API (large file storage)
An S3-compatible object storage service that stores metadata in Postgres.
- Official Docs: [Supabase Storage reference docs](/docs/reference/storage)
- Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api)
- License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### Deno (Edge Functions)
A modern runtime for JavaScript and TypeScript.
- Official Docs: [deno.land](https://deno.land/)
- Source code: [github.com/denoland/deno](https://github.com/denoland/deno)
- License: [MIT](https://github.com/denoland/deno/blob/main/LICENSE.md)
- Language: TypeScript / Rust
### postgres-meta (Database management)
A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries.
- Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/)
- Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta)
- License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### PgBouncer
A lightweight connection pooler for PostgreSQL. This is useful for connecting to Postgres when using Serverless functions.
- Official Docs: [pgbouncer.org](http://www.pgbouncer.org/)
- Source code: [pgbouncer/pgbouncer](https://github.com/pgbouncer/pgbouncer)
- License: [ISC](https://github.com/pgbouncer/pgbouncer/blob/master/COPYRIGHT)
- Language: C
### Kong (API Gateway)
A cloud-native API gateway, built on top of Nginx.
- Official Docs: [docs.konghq.com](https://docs.konghq.com/)
- Source code: [github.com/kong/kong](https://github.com/kong/kong)
- License: [Apache 2.0](https://github.com/Kong/kong/blob/master/LICENSE)
- Language: Lua
## Product Principles
It is our goal to provide an architecture that any large-scale company would design for themselves,
and then provide tooling around that architecture that is easy-to-use for indie-developers and small teams.
We use a series of principles to ensure that scalability and usability are never mutually exclusive:
### Everything works in isolation
Each system must work as a standalone tool with as few moving parts as possible.
The litmus test for this is: "Can a user run this product with nothing but a Postgres database?"
### Everything is integrated
Supabase is composable. Even though every product works in isolation, each product on the platform needs to 10x the other products.
For integration, each tool should expose an API and Webhooks.
### Everything is extensible
We're deliberate about adding a new tool, and prefer instead to extend an existing one.
This is the opposite of many cloud providers whose product offering expands into niche use-cases. We provide _primitives_ for developers, which allow them to achieve any goal.
Less, but better.
### Everything is portable
To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product.
We use existing standards to increase portability (like pg_dump an CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard.
This forces us compete on experience. We aim to be the best Postgres hosting service.
### Play the long game
We sacrifice short-term wins for long-term gains. For example, it is tempting to run a fork of Postgres with additional functionality which only our customers need.
Instead, we prefer to support efforts to upstream missing functionality so that the entire community benefits. This has the additional benefit of ensuring portability and longevity.
### Build for developers
"Developers" are a specific profile of user: they are _builders_.
When assessing impact as a function of effort, developers have a large efficiency due to the type of products and systems they can build.
As the profile of a developer changes over time, Supabase will continue to evolve the product to fit this evolving profile.
### Support existing tools
Supabase supports existing tools and communities wherever possible. Supabase is more like a "community of communities" - each tool typically has its own community
which we work with.
Open source is something we approach [collaboratively](https://supabase.com/blog/supabase-series-b#giving-back): we employ maintainers, sponsor projects, invest in businesses, and develop our own open source tools.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -10,6 +10,8 @@ export const meta = {
This submodule provides experimental convenience helpers for implementing user authentication in Next.js Server Components - the `app` directory. For examples using the `pages` directory check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs).
> For a complete implementation example, check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components).
> To learn more about fetching and caching Supabase data with Next.js 13 Server Components, check out our [blog](/blog/fetching-and-caching-supabase-data-in-next-js-server-components) or [live stream](https://www.youtube.com/watch?v=QH0P5xZt5wY).
## Install the Next.js helper library
@@ -52,6 +54,8 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
## Creating a Supabase Client
### Server-side
<Tabs
scrollable
size="small"
@@ -60,23 +64,13 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
>
<TabPanel id="js" label="JavaScript">
Create a new file at `/utils/supabase-browser.js` and populate with the following:
```js title="/utils/supabase-browser.js"
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
export default createBrowserSupabaseClient()
```
This will be used any time we need to create a Supabase client _client-side_ - in `useEffect`, for example.
Create a new file at `/utils/supabase-server.js` and populate with the following:
```js title="/utils/supabase-server.js"
import { headers, cookies } from 'next/headers'
import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs'
export default () =>
export const createClient = () =>
createServerComponentSupabaseClient({
headers,
cookies,
@@ -85,33 +79,19 @@ export default () =>
> This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data.
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Create a new file at `/utils/supabase-browser.ts` and populate with the following:
```ts
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { Database } from '../lib/database.types'
export default createBrowserSupabaseClient<Database>()
```
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
This will be used any time we need to create a Supabase client _client-side_ - in `useEffect`, for example.
Create a new file at `/utils/supabase-server.ts` and populate with the following:
```ts title="/utils/supabase-server.ts"
import { headers, cookies } from 'next/headers'
import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { Database } from '../lib/database.types'
export default () =>
import type { Database } from '../lib/database.types'
export const createClient = () =>
createServerComponentSupabaseClient<Database>({
headers,
cookies,
@@ -122,14 +102,12 @@ export default () =>
> This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data.
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
</TabPanel>
</Tabs>
## Middleware
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
Middleware runs before every route declared in the `matcher` array. Since we don't have access to set cookies or headers from Server Components, we need to create a Middleware Supabase client and refresh the user's session by calling `getSession()`.
Next, we need a middleware file to refresh the user's session on navigation.
<Tabs
scrollable
@@ -139,7 +117,7 @@ Middleware runs before every route declared in the `matcher` array. Since we don
>
<TabPanel id="js" label="JavaScript">
Create a new file at `/app/middleware.js` and populate with the following:
Create a new `/app/middleware.js` file and populate with the following:
```jsx title="middleware.js"
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
@@ -156,27 +134,25 @@ export async function middleware(req) {
return res
}
export const config = {
matcher: ['/optional-session', '/required-session', '/realtime'],
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Create a new file at `/app/middleware.ts` and populate with the following:
Create a new `/app/middleware.ts` file and populate with the following:
```tsx title="middleware.ts"
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import type { Database } from '../lib/database.types'
export async function middleware(req: NextRequest) {
const res = NextResponse.next()
const supabase = createMiddlewareSupabaseClient({ req, res })
const supabase = createMiddlewareSupabaseClient<Database>({ req, res })
const {
data: { session },
@@ -184,20 +160,34 @@ export async function middleware(req: NextRequest) {
return res
}
export const config = {
matcher: ['/optional-session', '/required-session', '/realtime'],
}
```
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createMiddlewareSupabaseClient` to add type support to the Supabase client.
</TabPanel>
</Tabs>
> Any Server Component route that uses a Supabase client must be added to this middleware's `matcher` array. Without this, the Server Component may try to make a request to Supabase with an expired `access_token`.
We can now use our server-side Supabase client to fetch data in Server Components.
## Supabase Listener
```jsx
import 'server-only'
We need to set up a listener to fetch fresh data whenever our user logs in or out.
import { createClient } from '../../utils/supabase-server'
// do not cache this page
export const revalidate = 0
export default async function ServerComponent() {
const supabase = createClient()
const { data } = await supabase.from('posts').select('*')
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
}
```
### Client-side
We still need a Supabase instance client-side for authentication and realtime subscriptions. It is important, when using Supabase client-side, to have a single instance of a client. We can share this singleton instance across our components using providers and React context.
<Tabs
scrollable
@@ -207,286 +197,291 @@ We need to set up a listener to fetch fresh data whenever our user logs in or ou
>
<TabPanel id="js" label="JavaScript">
Create a `/components/supabase-listener.jsx` file and add the following:
Create a new file at `/utils/supabase-browser.js` and populate with the following:
```js title="/utils/supabase-browser.js"
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
export const createClient = () => createBrowserSupabaseClient()
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Create a new file at `/utils/supabase-browser.ts` and populate with the following:
```ts
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { Database } from '../lib/database.types'
export const createClient = () => createBrowserSupabaseClient<Database>()
```
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
</TabPanel>
</Tabs>
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase at `/components/supabase-provider.jsx` and populate with the following:
```jsx title=components/supabase-provider.jsx
'use client'
import { createContext, useContext, useState } from 'react'
import { createClient } from '../utils/supabase-browser'
const Context = createContext()
export default function SupabaseProvider({ children }) {
const [supabase] = useState(() => createClient())
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== accessToken) {
router.refresh()
}
})
return () => subscription.unsubscribe()
}, [accessToken])
return (
<Context.Provider value={{ supabase }}>
<>{children}</>
</Context.Provider>
)
}
export const useSupabase = () => useContext(Context)
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase at `/components/supabase-provider.tsx` and populate with the following:
```tsx title=components/supabase-provider.tsx
'use client'
import { createContext, useContext, useState } from 'react'
import { createClient } from '../utils/supabase-browser'
import type { SupabaseClient } from '@supabase/auth-helpers-nextjs'
import type { Database } from '../lib/database.types'
type SupabaseContext = {
supabase: SupabaseClient<Database>
}
const Context = createContext<SupabaseContext>()
export default function SupabaseProvider({ children }: { children: React.ReactNode }) {
const [supabase] = useState(() => createClient())
return (
<Context.Provider value={{ supabase }}>
<>{children}</>
</Context.Provider>
)
}
export const useSupabase = () => useContext(Context)
```
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
</TabPanel>
</Tabs>
We need to set up a listener to fetch fresh data whenever our user logs in or out. For this we need to check whether our client and server sessions match. Let's start by installing the `server-only` package.
```bash
npm install server-only
```
This will ensure that any component that imports this package will be a Server Component, and excluded from the browser bundle.
Next, let's modify our root layout to fetch the user's session, wrap our application in our Supabase Provider, and pass the server access token as a prop to the `<SupabaseListener />` component (we will create this next).
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```jsx title=app/layout.jsx
import 'server-only'
import SupabaseListener from '../components/supabase-listener'
import SupabaseProvider from '../components/supabase-provider'
import './globals.css'
import { createClient } from '../utils/supabase-server'
// do not cache this layout
export const revalidate = 0
export default async function RootLayout({ children }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<SupabaseProvider>
<SupabaseListener serverAccessToken={session?.access_token} />
{children}
</SupabaseProvider>
</body>
</html>
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
```tsx title=app/layout.tsx
import 'server-only'
import SupabaseListener from '../components/supabase-listener'
import SupabaseProvider from '../components/supabase-provider'
import './globals.css'
import { createClient } from '../utils/supabase-server'
// do not cache this layout
export const revalidate = 0
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<SupabaseProvider>
<SupabaseListener serverAccessToken={session?.access_token} />
{children}
</SupabaseProvider>
</body>
</html>
)
}
```
</TabPanel>
</Tabs>
And now create our Supabase listener component that uses the singleton Supabase instance to listen for auth changes.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```jsx title="/components/supabase-listener.jsx"
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import supabase from '../utils/supabase-browser'
import { useSupabase } from './supabase-provider'
export default function SupabaseListener({ accessToken }) {
export default function SupabaseListener({ serverAccessToken }) {
const { supabase } = useSupabase()
const router = useRouter()
useEffect(() => {
const { data: subscription } = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== accessToken) {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== serverAccessToken) {
router.refresh()
}
})
return () => subscription.unsubscribe()
}, [accessToken])
return () => {
subscription.unsubscribe()
}
}, [serverAccessToken, router, supabase])
return null
}
```
> `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`.
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `accessToken` prop, which will be the server's state for our user (we'll set this up next). If the `accessToken` from the server and the new `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
Lastly, fetch the server-side session in the `RootLayout` and pass it to our new `<SupabaseListener />` component.
```jsx title="/app/layout.jsx"
import SupabaseListener from '../components/supabase-listener'
import createClient from '../utils/supabase-server'
export default async function RootLayout({ children }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
// html and head section omitted
<body>
<SupabaseListener accessToken={session?.access_token} />
{children}
</body>
)
}
```
We don't want Next.js to cache this `session` value, so we need to export a `revalidate` value of `0` in the layout file.
```jsx
export const revalidate = 0
```
We also want to tell Next.js to explicitly exclude this component's code from the client bundle by installing the `server-only` package:
```bash
npm install server-only
```
And importing it at the top of our component.
```jsx
import 'server-only'
```
The entire Layout component should look something like this:
```jsx title="/app/layout.jsx"
import 'server-only'
import SupabaseListener from '../components/supabase-listener'
import './globals.css'
import createClient from '../utils/supabase-server'
// do not cache this layout
export const revalidate = 0
export default async function RootLayout({ children }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.jsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<SupabaseListener accessToken={session?.access_token} />
{children}
</body>
</html>
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Create a `/components/supabase-listener.tsx` file and add the following:
```tsx title="/components/supabase-listener.tsx"
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import supabase from '../utils/supabase-browser'
import { useSupabase } from './supabase-provider'
export default function SupabaseListener({ accessToken }: { accessToken?: string }) {
export default function SupabaseListener({ serverAccessToken }: { serverAccessToken?: string }) {
const { supabase } = useSupabase()
const router = useRouter()
useEffect(() => {
supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== accessToken) {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== serverAccessToken) {
router.refresh()
}
})
}, [accessToken])
return () => {
subscription.unsubscribe()
}
}, [serverAccessToken, router, supabase])
return null
}
```
</TabPanel>
</Tabs>
> `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`.
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `accessToken` prop, which will be the server's state for our user (we'll set this up next). If the `accessToken` from the server and the new `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `serverAccessToken` prop, which is the server's state for our user. If the `serverAccessToken` and the new session's `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
Lastly, fetch the server-side session in the `RootLayout` and pass it to our new `<SupabaseListener />` component.
```tsx title="/app/layout.tsx"
import SupabaseListener from '../components/supabase-listener'
import createClient from '../utils/supabase-server'
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
// html and head section omitted
<body>
<SupabaseListener accessToken={session?.access_token} />
{children}
</body>
)
}
```
> Again, TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerComponentSupabaseClient` to add type support to the Supabase client.
We don't want Next.js to cache this `session` value, so we need to export a `revalidate` value of `0`.
```tsx
export const revalidate = 0
```
We also want to tell Next.js to explicitly exclude this component's code from the client bundle by installing the `server-only` package:
```bash
npm install server-only
```
And importing it at the top of our component.
```tsx
import 'server-only'
```
The entire Layout component should look something like this:
```tsx title="/app/layout.tsx"
import 'server-only'
import SupabaseListener from '../components/supabase-listener'
import './globals.css'
import createClient from '../utils/supabase-server'
// do not cache this layout
export const revalidate = 0
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<SupabaseListener accessToken={session?.access_token} />
{children}
</body>
</html>
)
}
```
</TabPanel>
</Tabs>
Supabase can now be used in any Client or Server component.
## Server Components
Next.js recommends fetching data in Server Components whenever possible.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```jsx
import 'server-only'
import createClient from '../../utils/supabase-server'
// do not cache this page
export const revalidate = 0
export default async function ServerComponent() {
const supabase = createClient()
const { data } = await supabase.from('posts').select('*')
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
```tsx
import 'server-only'
import createClient from '../../utils/supabase-server'
// do not cache this page
export const revalidate = 0
export default async function ServerComponent() {
const supabase = createClient()
const { data } = await supabase.from('posts').select('*')
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
}
```
</TabPanel>
</Tabs>
## Client Components
While Next.js recommend doing all data fetching in Server Components, we still need Supabase client-side for things like authentication and subscribing to realtime updates.
Now we can use our `useSupabase` hook throughout our client-side components.
### Authentication
We can call any of [Supabase's authentication methods](https://supabase.com/docs/reference/javascript/v1/auth-signin) - such as `supabase.auth.signInWithOAuth` - from a client component.
<Tabs
scrollable
size="small"
@@ -498,30 +493,33 @@ We can call any of [Supabase's authentication methods](https://supabase.com/docs
```jsx title="/components/login.jsx"
'use client'
import supabase from '../utils/supabase-browser'
import { useSupabase } from './supabase-provider'
// Supabase auth needs to be triggered client-side
export default function Login() {
const handleLogin = async () => {
const { error } = await supabase.auth.signInWithOAuth({
const { supabase, session } = useSupabase()
const handleEmailLogin = async () => {
await supabase.auth.signInWithPassword({
email: 'jon@supabase.com',
password: 'password',
})
}
const handleGitHubLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: 'github',
})
if (error) {
console.log({ error })
}
}
const handleLogout = async () => {
const { error } = await supabase.auth.signOut()
if (error) {
console.log({ error })
}
await supabase.auth.signOut()
}
return (
<>
<button onClick={handleLogin}>Login</button>
<button onClick={handleEmailLogin}>Email Login</button>
<button onClick={handleGitHubLogin}>GitHub Login</button>
<button onClick={handleLogout}>Logout</button>
</>
)
@@ -535,30 +533,33 @@ export default function Login() {
```tsx title="/components/login.tsx"
'use client'
import supabase from '../utils/supabase-browser'
import { useSupabase } from './supabase-provider'
// Supabase auth needs to be triggered client-side
export default function Login() {
const handleLogin = async () => {
const { error } = await supabase.auth.signInWithOAuth({
const { supabase, session } = useSupabase()
const handleEmailLogin = async () => {
await supabase.auth.signInWithPassword({
email: 'jon@supabase.com',
password: 'password',
})
}
const handleGitHubLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: 'github',
})
if (error) {
console.log({ error })
}
}
const handleLogout = async () => {
const { error } = await supabase.auth.signOut()
if (error) {
console.log({ error })
}
await supabase.auth.signOut()
}
return (
<>
<button onClick={handleLogin}>Login</button>
<button onClick={handleEmailLogin}>Email Login</button>
<button onClick={handleGitHubLogin}>GitHub Login</button>
<button onClick={handleLogout}>Logout</button>
</>
)
@@ -588,10 +589,11 @@ Create a new file at `/app/realtime/posts.jsx` and populate with the following:
'use client'
import { useEffect, useState } from 'react'
import supabase from '../../utils/supabase-browser'
import { useSupabase } from '../../components/supabase-provider'
export default function Posts({ serverPosts }) {
const [posts, setPosts] = useState(serverPosts)
const { supabase } = useSupabase()
useEffect(() => {
setPosts(serverPosts)
@@ -608,14 +610,12 @@ export default function Posts({ serverPosts }) {
return () => {
supabase.removeChannel(channel)
}
}, [serverPosts])
}, [supabase, setPosts, posts])
return <pre>{JSON.stringify(posts, null, 2)}</pre>
}
```
> The first `useEffect` is required to overwrite posts if the `serverPosts` prop changes.
This can now be used in a Server Component to subscribe to realtime updates.
Create a new file at `/app/realtime/page.jsx` and populate with the following:
@@ -623,7 +623,7 @@ Create a new file at `/app/realtime/page.jsx` and populate with the following:
```jsx title="/app/realtime/page.jsx"
import 'server-only'
import createClient from '../../utils/supabase-server'
import { createClient } from '../../utils/supabase-server'
import Posts from './posts'
// do not cache this page
@@ -647,13 +647,15 @@ Create a new file at `/app/realtime/posts.tsx` and populate with the following:
'use client'
import { useEffect, useState } from 'react'
import { Database } from '../lib/database.types'
import supabase from '../../utils/supabase-browser'
import { useSupabase } from '../../components/supabase-provider'
import type { Database } from '../../lib/database.types'
type Post = Database['public']['Tables']['posts']['Row']
export default function Posts({ serverPosts }: { serverPosts: Post[] }) {
const [posts, setPosts] = useState(serverPosts)
const { supabase } = useSupabase()
useEffect(() => {
setPosts(serverPosts)
@@ -670,13 +672,13 @@ export default function Posts({ serverPosts }: { serverPosts: Post[] }) {
return () => {
supabase.removeChannel(channel)
}
}, [serverPosts])
}, [supabase, setPosts, posts])
return <pre>{JSON.stringify(posts, null, 2)}</pre>
}
```
> The first `useEffect` is required to overwrite posts if the `serverPosts` prop changes.
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerSupabaseClient` to add type support to the Supabase client.
This can now be used in a Server Component to subscribe to realtime updates.
@@ -685,7 +687,7 @@ Create a new file at `/app/realtime/page.tsx` and populate with the following:
```tsx title="/app/realtime/page.tsx"
import 'server-only'
import createClient from '../../utils/supabase-server'
import { createClient } from '../../utils/supabase-server'
import Posts from './posts'
// do not cache this page
@@ -584,7 +584,7 @@ export async function middleware(req: NextRequest) {
}
export const config = {
matcher: '/middleware-protected',
matcher: '/middleware-protected/:path*',
}
```
@@ -782,7 +782,7 @@ export const config = {
### Migrating to v0.4.X and supabase-js v2
With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/upgrade-guide) for the full set of changes within supabase-js.
With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/v1/upgrade-guide) for the full set of changes within supabase-js.
The `/api/auth/logout` API route has been removed, please use the `signout` method instead:
@@ -12,9 +12,7 @@ Even though Supabase provides an `auth.users` table, it can be helpful to create
## Creating user tables
When you create tables to store user data, it's helpful to reference the `auth.users` table in the primary key. This ensures data integrity.
For example, a `public.profiles` table might look like this:
When you create tables to store user data, it's helpful to reference the `auth.users` table in the primary key to ensure data integrity. Also specify the `on delete cascade` clause when referencing `auth.users`. Omitting it may cause problems when deleting users. For example, a `public.profiles` table might look like this:
```sql
create table public.profiles (
@@ -28,9 +26,11 @@ create table public.profiles (
alter table public.profiles enable row level security;
```
<Admonition type="note">
<Admonition type="caution">
Make sure to specify the `on delete cascade` clause when referencing `auth.users`. Omitting it may cause problems when deleting users.
Only use primary keys as [foreign key references](https://www.postgresql.org/docs/current/tutorial-fk.html) for schemas and tables like `auth.users` which are managed by Supabase. PostgreSQL lets you specify a foreign key reference for columns backed by a unique index (not necessarily primary keys).
Primary keys are **guaranteed not to change**. Columns, indices, constraints or other database objects managed by Supabase **may change at any time** and you should be careful when referencing them directly.
</Admonition>
@@ -74,14 +74,12 @@ When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/aut
```js
async function signInWithAzure() {
const { data, error } = await supabase.auth.signInWithOAuth(
{
provider: 'azure',
},
{
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
scopes: 'email',
}
)
},
})
}
```
@@ -1,12 +1,9 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'cli',
title: 'Supabase CLI',
description:
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
sidebar_label: 'Overview',
toc_max_heading_level: 2,
}
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
@@ -113,7 +110,8 @@ brew upgrade supabase
## See also
- [Supabase CLI Reference](/docs/reference/cli/usage)
- [Supabase CLI Reference](/docs/reference/cli/introduction)
- [Supabase CLI Configuration](/docs/reference/cli/config)
- [Local Development](/docs/guides/cli/local-development)
- [Managing Environments](/docs/guides/cli/managing-environments)
@@ -34,9 +34,8 @@ supabase login
<Admonition type="note">
If you installed the Supabase CLI via NPM you may have to use `npx supabase login`.
</Admonition>
</Admonition>
## Initialize your project
@@ -215,8 +215,6 @@ jobs:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
- name: Start Supabase local development setup
run: supabase start
@@ -256,8 +254,6 @@ jobs:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
- run: |
supabase link --project-ref $STAGING_PROJECT_ID
@@ -289,8 +285,6 @@ jobs:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
- run: |
supabase link --project-ref $PRODUCTION_PROJECT_ID
+1 -1
View File
@@ -217,7 +217,7 @@ import { createClient, useQuery } from 'urql'
// Prepare API key and Authorization header
const headers = {
apikey: <SUPABASE_ANON_KEY>,
authorization: `Bearer ${<SUPABASE_ANON_KEY}`>
authorization: `Bearer ${<SUPABASE_ANON_KEY>}`,
}
// Create GraphQL client
@@ -8,6 +8,15 @@ export const meta = {
Supabase APIs are generated from your database, which means that we can use database introspection to generate type-safe API definitions.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/7CqlTU9aOR4"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Generating types using Supabase CLI
The Supabase CLI is a single binary Go application that provides everything you need to setup a local development environment.
@@ -63,6 +63,8 @@ The schedule uses the standard cron syntax, in which \* means "run every time pe
* * * * *
```
You can use [crontab.guru](https://crontab.guru/) to help validate your cron schedules.
## Examples
### Delete data every week
@@ -71,8 +73,8 @@ Delete old data on Saturday at 3:30am (GMT):
```sql
select cron.schedule (
'webhook-every-minute', -- name of the cron job
'* * * * *', -- every minute
'saturday-cleanup', -- name of the cron job
'30 3 * * 6', -- Saturday at 3:30am (GMT)
$$ delete from events where event_time < now() - interval '1 week' $$
);
```
@@ -85,6 +87,26 @@ Vacuum every day at 3:00am (GMT)
SELECT cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM');
```
### Invoke Supabase Edge Function every minute
Make a POST request to a Supabase Edge Function every minute. Note: this requires the [`pg_net` extension](/docs/guides/database/extensions/pgnet) to be enabled.
```sql
select
cron.schedule(
'invoke-function-every-minute',
'* * * * *', -- every minute
$$
select
net.http_post(
url:='https://project-ref.functions.supabase.co/function-name',
headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb,
body:=concat('{"time": "', now(), '"}')::jsonb
) as request_id;
$$
);
```
### Unschedule a job
Unschedules a job called `'nightly-vacuum'`
@@ -53,11 +53,11 @@ Procedural languages are automatically installed within `pg_catalog`, so you don
</TabPanel>
</Tabs>
## `http_get` [#http_get]
## `http_get`
Creates an HTTP GET request returning the request's ID. HTTP requests are not started until the transaction is committed.
### Signature
### Signature [#get-signature]
<Admonition type="caution">
@@ -86,7 +86,7 @@ net.http_get(
language plpgsql
```
### Usage
### Usage [#get-usage]
```sql
select net.http_get('https://news.ycombinator.com') as request_id;
@@ -98,13 +98,13 @@ request_id
After triggering `http_get`, use [`http_get_result`](#http_get_result) to get the result of the request.
## `http_post` [#http_post]
## `http_post`
Creates an HTTP POST request with a JSON body, returning the request's ID. HTTP requests are not started until the transaction is committed.
The body's character set encoding matches the database's `server_encoding` setting.
### Signature
### Signature [#post-signature]
<Admonition type="caution">
@@ -134,7 +134,7 @@ net.http_post(
language plpgsql
```
### Usage
### Usage [#post-usage]
```sql
select
@@ -150,6 +150,21 @@ request_id
After triggering `http_post`, use [`http_get_result`](#http_get_result) to get the result of the request.
## Examples
### Invoke a Supabase Edge Function
Make a POST request to a Supabase Edge Function with auth header and JSON body payload:
```sql
select
net.http_post(
url:='https://project-ref.functions.supabase.co/function-name',
headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb,
body:='{"name": "pg_net"}'::jsonb
) as request_id;
```
## Resources
- Source code: [github.com/supabase/pg_net](https://github.com/supabase/pg_net/)
@@ -8,6 +8,15 @@ export const meta = {
Postgres has built-in functions to handle `Full Text Search` queries. This is like a "search engine" within Postgres.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/b-mgca_2Oe4"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Preparation
For this guide we'll use the following example data:
+1 -1
View File
@@ -77,7 +77,7 @@ You must define the "data type" when you create a column.
### Data types
Every column is a predefined type. PostgreSQL provides many [default types](https://www.postgresql.org/docs/current/datatype.html), and you can even design your own (or use extensions)
if the default types don't fit your needs.
if the default types don't fit your needs. You can use any data type that Postgres supports via the SQL editor. We only support a subset of these in the Table Editor in an effort to keep the experience simple for people with less experience with databases.
<details>
<summary>Show/Hide default data types</summary>
@@ -35,6 +35,15 @@ jobs:
- run: supabase functions deploy your-function-name --project-ref $PROJECT_ID
```
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/6OMVWiiycLs"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/.github/workflows/deploy.yaml).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
+59
View File
@@ -0,0 +1,59 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'functions-cors',
title: 'CORS (Cross-Origin Resource Sharing)',
description: 'Add CORS headers to invoke functions from the browser.',
}
To invoke the functions from the browser, you need to handle [CORS Preflight](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) requests.
See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts).
### Recommended setup
We recommend adding a `corst.ts` file within a [`_shared` folder](/docs/guides/functions/quickstart#organizing-your-edge-functions) which makes it easy to reuse the CORS headers across functions:
```ts cors.ts
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
```
You can then import and use the CORS headers within your functions:
```ts index.ts
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { corsHeaders } from '../_shared/cors.ts'
console.log(`Function "browser-with-cors" up and running!`)
serve(async (req) => {
// This is needed if you're planning to invoke your function from a browser.
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const { name } = await req.json()
const data = {
message: `Hello ${name}!`,
}
return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
})
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
})
}
})
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,20 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'functions-debugging',
title: 'Debugging Functions',
description: 'Debug functions in production.',
}
You can debug your deployed Edge Functions using the ["Functions" section](https://app.supabase.com/project/_/functions) of the Dashboard. There are two debugging tools available:
- Invocations: shows the Request and Response for each execution.
- Logs: shows any platform events, including deployments and errors.
![Function invocations.](/docs/img/guides/functions/function-logs.png)
When [developing locally](/docs/guides/functions/local-development) you will see error messages and console log statements printed to your local terminal window.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,96 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'examples-cloudflare-turnstile',
title: 'Cloudflare Turnstile',
description: 'Protecting Forms with Cloudflare Turnstile.',
}
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/OwW0znboh60"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
[Clouflare Turnstile](https://www.cloudflare.com/products/turnstile/) is a friendly, free CAPTCHA replacement, and it works seamlessly with Supabase Edge Functions to protect your forms. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/cloudflare-turnstile).
## Setup
- Follow these steps to set up a new site: https://developers.cloudflare.com/turnstile/get-started/
- Add the Cloudflare Turnstile widget to your site: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/
## Code
Create a new function in your project:
```bash
supabase functions new cloudflare-turnstile
```
And add the code to the `index.ts` file:
```ts index.ts
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { corsHeaders } from '../_shared/cors.ts'
console.log('Hello from Cloudflare Trunstile!')
function ips(req: Request) {
return req.headers.get('x-forwarded-for')?.split(/\s*,\s*/)
}
serve(async (req) => {
// This is needed if you're planning to invoke your function from a browser.
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
const { token } = await req.json()
const clientIps = ips(req) || ['']
const ip = clientIps[0]
// Validate the token by calling the
// "/siteverify" API endpoint.
let formData = new FormData()
formData.append('secret', Deno.env.get('CLOUDFLARE_SECRET_KEY') ?? '')
formData.append('response', token)
formData.append('remoteip', ip)
const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
const result = await fetch(url, {
body: formData,
method: 'POST',
})
const outcome = await result.json()
console.log(outcome)
if (outcome.success) {
return new Response('success', { headers: corsHeaders })
}
return new Response('failure', { headers: corsHeaders })
})
```
## Deploy the server-side validation Edge Functions
- https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
```bash
supabase functions deploy cloudflare-turnstile
supabase secrets set CLOUDFLARE_TURNSTILE_SECRET_KEY=your_secret_key
```
## Invoke the function from your site
```js
const { data, error } = await supabase.functions.invoke('cloudflare-turnstile', {
body: { token },
})
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,49 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'examples-github-actions',
title: 'GitHub Actions',
description: 'Deploying Edge Functions with GitHub Actions.',
}
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/6OMVWiiycLs"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
Use the Supabase CLI together with GitHub Actions to automatically deploy our Supabase Edge Functions. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/github-action-deploy).
```yaml deploy.yaml
name: Deploy Function
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
PROJECT_ID: zdtdtxajzydjqzuktnqx
steps:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
- run: supabase functions deploy github-action-deploy --project-ref $PROJECT_ID
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -17,6 +17,10 @@ export const meta = {
Generate Open Graph images with Deno and Supabase Edge Functions. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/opengraph).
## Code
Create a `handler.tsx` file to construct the OG image in React:
```tsx handler.tsx
import React from 'https://esm.sh/react@18.2.0'
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts'
@@ -42,6 +46,8 @@ export default function handler(req: Request) {
}
```
Create an `index.ts` file to execute the handler on incoming requests:
```ts index.ts
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import handler from './handler.tsx'
@@ -0,0 +1,22 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'examples-storage-caching',
title: 'Caching Images with Supabase Storage CDN',
description: 'Integrate Edge Functions with Supabase Storage to cache images on the Edge (CDN).',
}
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/wW6L52v9Ldo"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
Integrate Edge Functions with Supabase Storage to cache images on the Edge (CDN). [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/og-image-with-storage-cdn).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,22 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'examples-stripe-webhooks',
title: 'Handling Stripe Webhooks',
description: 'Handling signed Stripe Webhooks with Edge Functions.',
}
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/6OMVWiiycLs"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
Handling signed Stripe Webhooks with Edge Functions. [View on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/stripe-webhooks/index.ts).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,22 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'examples-telegram-bot',
title: 'Telegram Bot',
description: 'Building a Telegram Bot with Edge Functions.',
}
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/AWfE3a9J_uo"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
Handle Telegram Bot Webhooks with the [grammY framework](https://grammy.dev/). grammY is an open source Telegram Bot Framework which makes it easy to handle and respond to incoming messages. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/telegram-bot).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,62 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'functions-import-maps',
title: 'Import Maps',
description: 'Using Import Maps.',
}
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/ILr3cneZuFk"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
Since [Supabase CLI](https://supabase.com/docs/guides/resources/supabase-cli) version `1.33.0` [import maps](https://deno.land/manual@v1.29.2/node/import_maps) can be used with Supabase Edge Functions.
[Import maps](https://github.com/WICG/import-maps#the-import-map) is a web-platform standard that allows you to use bare specifiers with Deno without having to install the Node.js package locally.
So if we want to do the following in our code:
```ts, ignore
import lodash from "lodash";
```
We can accomplish this using an import map, and we don't even have to install the `lodash` package locally. We would want to create a JSON file (for example **import_map.json**) with the following:
```json
{
"imports": {
"lodash": "https://cdn.skypack.dev/lodash"
}
}
```
## Import Map Placement
We recommend creating one `import_map.json` within the `/supabase/functions` folder (see [Organizing your Edge Functions](/docs/guides/functions/quickstart#organizing-your-edge-functions)), similar to a `package.json` file, to define imports that can be used across all of your project's functions.
Alternatively, you can create one `import_map.json` file in each function folder, which will take priority over a top-level file.
Lastly, you can override this default behaviour by providing the `--import-map <string>` flag to the `serve` and `deploy` commands.
## Visual Studio Code Configuration
In order for vscode to understand the imports correctly, you need to specify the `deno.importMap` flag in your `.vscode/settings.json` file:
```json settings.json
{
"deno.enable": true,
"deno.unstable": true,
"deno.importMap": "./supabase/functions/import_map.json"
}
```
For a full guide on developing with Deno in Visual Studio Code, see [this guide](https://deno.land/manual@v1.25.4/vscode_deno).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,60 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'functions-local-development',
title: 'Local Development',
description: 'Run your functions locally.',
}
You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/usage#supabase-functions-serve):
```bash
supabase start # start the supabase stack
supabase functions serve hello-world # start the Function watcher
```
The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server.
### Invoking functions locally
While serving your local Function, you can invoke it using curl:
```bash
curl --request POST 'http://localhost:54321/functions/v1/hello-world' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
--header 'Content-Type: application/json' \
--data '{ "name":"Functions" }'
```
or using one of the [client libraries](/docs#reference-documentation), e.g. using [supabase-js](/docs/reference/javascript/functions-invoke):
```js
import { createClient } from '@supabase/supabase-js'
// Use the credentials outputted in your terminal when running `supabase start`
const supabase = createClient(
'http://localhost:54321',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'
)
const { data, error } = await supabase.functions.invoke('hello-world', {
body: { name: 'Functions' },
})
```
You should see the response `{ "message":"Hello Functions!" }`.
If you execute Function with a different payload the response will change. <br />
Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again!
<Admonition type="note">
- Edge Functions don't serve HTML content (`GET` requests that return `text/html` are rewritten to `text/plain`).
- The `Authorization` header is required. You can use either the `ANON` key, the `SERVICE_ROLE` key, or a logged-in user's JWT.
- The Function is proxied through the local API (`http://localhost:54321`)
</Admonition>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+43 -117
View File
@@ -3,7 +3,7 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'functions-quickstart',
title: 'Edge Functions Quickstart',
description: 'Globally distributed TypeScript functions.',
description: 'Globally distributed TypeScript Functions.',
sidebar_label: 'Quickstart',
}
@@ -55,9 +55,9 @@ If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webho
</Admonition>
## Execute remote functions
## Invoking remote functions
You can execute Edge Functions using curl. Copy the curl command from the Dashboard. It should look like this:
You can invoke Edge Functions using curl:
```bash
curl --request POST 'https://<project_ref>.functions.supabase.co/hello-world' \
@@ -66,135 +66,61 @@ curl --request POST 'https://<project_ref>.functions.supabase.co/hello-world' \
--data '{ "name":"Functions" }'
```
<Admonition type="note">
If you receive an error `Invalid JWT`, find the `ANON_KEY` of your project in the Dashboard under `Settings > API`.
</Admonition>
or using one of the [client libraries](/docs#reference-documentation), e.g. using [supabase-js](/docs/reference/javascript/functions-invoke):
```js
import { createClient } from '@supabase/supabase-js'
// Create a single supabase client for interacting with your database
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key')
const { data, error } = await supabase.functions.invoke('hello-world', {
body: { name: 'Functions' },
})
```
After invoking your Edge Function you should see the response `{ "message":"Hello Functions!" }`.
## Debug functions
## Database Functions vs Edge Functions
You can debug your deployed Edge Functions using the "Functions" section of the Dashboard. There are two types debugging tools available:
For data-intensive operations we recommend using [Database Functions](/docs/guides/database/functions), which are executed within your database
and can be called remotely using the [REST and GraphQL API](/docs/guides/api).
- Invocations: shows the Request and Response for each execution.
- Logs: shows any platform events, including deployments and errors.
For use-cases which require low-latency we recommend [Edge Functions](/docs/guides/functions), which are globally-distributed and can be written in TypeScript.
![Function invocations.](/docs/img/guides/functions/function-logs.png)
## Organizing your Edge Functions
## Develop locally
You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/usage#supabase-functions-serve):
We recommend developing “fat functions”. This means that you should develop few large functions, rather than many small functions. One common pattern when developing Functions is that you need to share code between two or more Functions. To do this, you can store any shared code in a folder prefixed with an underscore (`_`). We recommend this folder structure:
```bash
supabase start # start the supabase stack
supabase functions serve hello-world # start the Function watcher
└── supabase
├── functions
│ ├── import_map.json # A top-level import map to use across functions.
│ ├── _shared
│ │ ├── supabaseAdmin.ts # Supabase client with SERVICE_ROLE key.
│ │ └── supabaseClient.ts # Supabase client with ANON key.
│ │ └── cors.ts # Reusable CORS headers.
│ ├── function-one # Use hyphens to name functions.
│ │ └── index.ts
│ └── function-two
│ └── index.ts
├── migrations
└── config.toml
```
The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server.
## Naming Edge Functions
### Invoke functions locally
We recommend using hyphens to name functions because hyphens are the most URL-friendly of all the naming conventions (snake_case, camelCase, PascalCase).
While serving your local Function, you can execute it using curl:
## Using HTTP Methods
```bash
curl --request POST 'http://localhost:54321/functions/v1/hello-world' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
--header 'Content-Type: application/json' \
--data '{ "name":"Functions" }'
```
You should see the response `{ "message":"Hello Functions!" }`.
<details>
<summary>Implementation details</summary>
- Edge Functions don't serve HTML content (`GET` requests that return `text/html` are rewritten to `text/plain`).
- The `Authorization` header is required. You can use either the `ANON` key, the `SERVICE_ROLE` key, or a logged-in user's JWT.
- The Function is proxied through the local API (`http://localhost:54321`)
</details>
<br />
If you execute Function with a different payload the response will change. <br />
Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again!
## Secrets and Environment Variables
It's common that you will need to use sensitive information or environment-specific variables inside your Edge Functions. You can access these using Deno's built-in handler
```js
Deno.env.get(MY_SECRET_NAME)
```
### Default secrets
By default, Edge Functions have access to these secrets:
- `SUPABASE_URL`: The API gateway for your Supabase project.
- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have [Row Level Security](/docs/guides/auth/row-level-security) enabled.
- `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass [Row Level Security](/docs/guides/auth/row-level-security).
- `SUPABASE_DB_URL`: The URL for your [PostgreSQL database](/docs/guides/database). You can use this to connect directly to your database.
### Local secrets
Let's create a local file for storing our secrets, and inside it we can store a secret `MY_NAME`:
```jsx
echo "MY_NAME=Yoda" >> ./supabase/.env.local
```
This creates a new file `./supabase/.env.local` for storing your local development secrets.
<Admonition type="caution">
Never check your .env files into Git!
</Admonition>
Now let's access this environment variable `MY_NAME` inside our Function. Anywhere in your function, add this line:
```jsx
console.log(Deno.env.get('MY_NAME'))
```
Now we can invoke our function locally, by serving it with our new `.env.local` file:
```bash
supabase functions serve hello-world --env-file ./supabase/.env.local
```
When the function starts you should see the name “Yoda” output to the terminal.
### Production secrets
Let's create a `.env` for production. In this case we'll just use the same as our local secrets:
```bash
cp ./supabase/.env.local ./supabase/.env
```
This creates a new file `./supabase/.env` for storing your production secrets.
<Admonition type="caution">
Never check your `.env` files into Git!
</Admonition>
Let's push all the secrets from the `.env` file to our remote project using [`supabase secrets set`](/docs/reference/cli/usage#supabase-secrets-set):
```bash
supabase secrets set --env-file ./supabase/.env
# You can also set secrets individually using:
supabase secrets set MY_NAME=Chewbacca
```
You don't need to re-deploy after setting your secrets.
To see all the secrets which you have set remotely, use [`supabase secrets list`](/docs/reference/cli/usage#supabase-secrets-list):
```bash
supabase secrets list
```
Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`. A function can be designed to perform different actions based on a request's HTTP method. See the [example on building a RESTful service](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/restful-tasks) to learn how to handle different HTTP methods in your function.
## Limitations
@@ -0,0 +1,42 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'schedule-functions',
title: 'Schedule Functions',
description: 'Schedule Functions with pg_cron.',
}
The hosted Supabase Platform supports the [`pg_cron` extension](/docs/guides/database/extensions/pgcron), a simple cron-based job scheduler for PostgreSQL that runs inside the database.
In combination with the [`pg_net` extension](/docs/guides/database/extensions/pgnet), this allows us to invoke functions periodically on a set schedule.
## Examples
### Invoke Function every minute
Make a POST request to a Supabase Edge Function every minute:
```sql
select
cron.schedule(
'invoke-function-every-minute',
'* * * * *', -- every minute
$$
select
net.http_post(
url:='https://project-ref.functions.supabase.co/function-name',
headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb,
body:=concat('{"time": "', now(), '"}')::jsonb
) as request_id;
$$
);
```
## Resources
- [`pg_net` extension](/docs/guides/database/extensions/pgnet)
- [`pg_cron` extension](/docs/guides/database/extensions/pgcron)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,89 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'functions-secrets',
title: 'Secrets and Environment Variables',
description: 'Managing secrets and environment variables.',
}
It's common that you will need to use sensitive information or environment-specific variables inside your Edge Functions. You can access these using Deno's built-in handler
```js
Deno.env.get(MY_SECRET_NAME)
```
### Default secrets
By default, Edge Functions have access to these secrets:
- `SUPABASE_URL`: The API gateway for your Supabase project.
- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have [Row Level Security](/docs/guides/auth/row-level-security) enabled.
- `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass [Row Level Security](/docs/guides/auth/row-level-security).
- `SUPABASE_DB_URL`: The URL for your [PostgreSQL database](/docs/guides/database). You can use this to connect directly to your database.
### Local secrets
Let's create a local file for storing our secrets, and inside it we can store a secret `MY_NAME`:
```bash
echo "MY_NAME=Yoda" >> ./supabase/.env.local
```
This creates a new file `./supabase/.env.local` for storing your local development secrets.
<Admonition type="caution">
Never check your .env files into Git!
</Admonition>
Now let's access this environment variable `MY_NAME` inside our Function. Anywhere in your function, add this line:
```jsx
console.log(Deno.env.get('MY_NAME'))
```
Now we can invoke our function locally, by serving it with our new `.env.local` file:
```bash
supabase functions serve hello-world --env-file ./supabase/.env.local
```
When the function starts you should see the name “Yoda” output to the terminal.
### Production secrets
Let's create a `.env` for production. In this case we'll just use the same as our local secrets:
```bash
cp ./supabase/.env.local ./supabase/.env
```
This creates a new file `./supabase/.env` for storing your production secrets.
<Admonition type="caution">
Never check your `.env` files into Git!
</Admonition>
Let's push all the secrets from the `.env` file to our remote project using [`supabase secrets set`](/docs/reference/cli/usage#supabase-secrets-set):
```bash
supabase secrets set --env-file ./supabase/.env
# You can also set secrets individually using:
supabase secrets set MY_NAME=Chewbacca
```
You don't need to re-deploy after setting your secrets.
To see all the secrets which you have set remotely, use [`supabase secrets list`](/docs/reference/cli/usage#supabase-secrets-list):
```bash
supabase secrets list
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,140 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Use Supabase with SolidJS',
subtitle:
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SolidJS app.',
breadcrumb: 'Framework Quickstarts',
}
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
-- Create the table
CREATE TABLE countries (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
-- Insert some sample data into the table
INSERT INTO countries (name) VALUES ('United States');
INSERT INTO countries (name) VALUES ('Canada');
INSERT INTO countries (name) VALUES ('Mexico');
````
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Create a SolidJS app">
Create a SolidJS app using the `degit` command.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npx degit solidjs/templates/js my-app
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Install the Supabase client library">
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SolidJS app.
Navigate to the SolidJS app and install `supabase-js`.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
cd my-app && npm install @supabase/supabase-js
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Query data from the app">
In `App.jsx`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
Add a `getCountries` function to fetch the data and display the query result to the page.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```jsx src/App.jsx
import { createClient } from "@supabase/supabase-js";
import { createEffect, createSignal, For } from "solid-js";
const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>');
function App() {
const [countries, setCountries] = createSignal();
createEffect(() => {
getCountries();
});
async function getCountries() {
const { data } = await supabase.from("countries").select();
setCountries(data);
}
return (
<ul>
<For each={countries()}>{(country) => <li>{country.name}</li>}</For>
</ul>
);
}
export default App;
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Start the app">
Start the app and go to http://localhost:3000 in a browser and you should see the list of countries.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm run dev
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
@@ -0,0 +1,166 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Use Supabase with SvelteKit',
subtitle:
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SvelteKit app.',
breadcrumb: 'Framework Quickstarts',
}
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
-- Create the table
CREATE TABLE countries (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
-- Insert some sample data into the table
INSERT INTO countries (name) VALUES ('United States');
INSERT INTO countries (name) VALUES ('Canada');
INSERT INTO countries (name) VALUES ('Mexico');
````
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Create a SvelteKit app">
Create a SvelteKit app using the `npm create` command.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm create svelte@latest myapp
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Install the Supabase client library">
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SvelteKit app.
Navigate to the SvelteKit app and install `supabase-js`.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
cd my-app && npm install @supabase/supabase-js
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Create the Supabase client">
Create a `/src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```js src/lib/supabaseClient.js
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Query data from the app">
Use `load` method to fetch the data server-side and display the query results as a simple list.
Create `+page.server.js` file in the `routes` directory with the following code.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```js src/routes/+page.server.js
import { supabase } from "$lib/supabaseClient";
export async function load() {
const { data } = await supabase.from("countries").select();
return {
countries: data ?? [],
};
}
```
</StepHikeCompact.Code>
<StepHikeCompact.Details title="">
Replace the existing content in your `+page.svelte` file in the `routes` directory with the following code.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```svelte src/routes/+page.svelte
<script>
export let data;
let { countries } = data;
$: ({ countries } = data);
</script>
<ul>
{#each countries as country}
<li>{country.name}</li>
{/each}
</ul>
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Details title="Start the app">
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm run dev
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
@@ -0,0 +1,153 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Use Supabase with Vue',
subtitle:
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Vue app.',
breadcrumb: 'Framework Quickstarts',
}
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
-- Create the table
CREATE TABLE countries (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
-- Insert some sample data into the table
INSERT INTO countries (name) VALUES ('United States');
INSERT INTO countries (name) VALUES ('Canada');
INSERT INTO countries (name) VALUES ('Mexico');
````
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Create a Vue app">
Create a Vue app using the `npm init` command.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sh Terminal
npm init vue@latest my-app
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Install the Supabase client library">
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a Vue app.
Navigate to the Vue app and install `supabase-js`.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
cd my-app && npm install @supabase/supabase-js
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Create the Supabase client">
Create a `/src/lib` directory in your Vue app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```js src/lib/supabaseClient.js
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Query data from the app">
Replace the existing content in your `App.vue` file with the following code.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```vue src/App.vue
<script setup>
import { ref, onMounted } from 'vue'
import { supabase } from './lib/supabaseClient'
const countries = ref([])
async function getCountries() {
const { data } = await supabase.from('countries').select()
countries.value = data
}
onMounted(() => {
getCountries()
})
</script>
<template>
<ul>
<li v-for="country in countries" :key="country.id">{{ country.name }}</li>
</ul>
</template>
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Details title="Start the app">
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```bash Terminal
npm run dev
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
@@ -530,6 +530,10 @@ export class AccountComponent implements OnInit {
}
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -500,6 +500,10 @@ Now you will need to run the prebuild command to get the application working on
expo prebuild
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -793,6 +793,10 @@ class _AccountPageState extends State<AccountPage> {
}
```
### Storage management
<StorageManagement />
Congratulations, that is it! You have now built a fully functional user management app using Flutter and Supabase!
## See also
@@ -522,6 +522,10 @@ template: `
`
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -495,6 +495,10 @@ return (
<Avatar url={profile.avatar_url} onUpload={updateProfile}></Avatar>
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -555,6 +555,10 @@ export default defineComponent({
</script>
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -718,6 +718,10 @@ return (
)
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -420,6 +420,10 @@ And then we can add the widget to the Account page:
That is it! You should now be able to upload a profile photo to Supabase Storage.
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -397,6 +397,10 @@ return (
)
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -771,6 +771,10 @@ return (
)
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -415,6 +415,10 @@ return (
)
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -368,6 +368,10 @@ And then we can add the widget to the Account page:
</form>
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -434,6 +434,10 @@ And then we can add the widget to the Account page:
</form>
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
@@ -379,6 +379,10 @@ And then we can add the widget to the Account page:
</template>
```
### Storage management
<StorageManagement />
## Next steps
At this stage you have a fully functional application!
+33 -26
View File
@@ -18,7 +18,7 @@ Once created, the pipeline backfills all your historical data from Firestore and
Before you begin, you'll need:
- An [Estuary account](https://go.estuary.dev/sign-up).
- An Estuary account. [Head to the web app to start for free](https://dashboard.estuary.dev).
- For your Firestore database:
@@ -31,36 +31,35 @@ Before you begin, you'll need:
You'll start by creating a **capture**, a task in Flow that connects to your data source system: in this case, Firestore. This process will create one or more data **collections**, backed by a real-time data lake.
1. Go to the Flow web application at [dashboard.estuary.dev](http://dashboard.estuary.dev) and sign in using the credentials provided by your Estuary account manager.
1. Go to the [**Captures** tab](https://dashboard.estuary.dev/captures) of the Flow web app and choose **New Capture**.
2. Click the **Captures** tab and choose **New Capture**.
3. Locate and select the **Google Firestore** card.
2. Locate and select the **Google Firestore** card.
A form appears with the properties required for a Firestore capture.
4. Set a name for your capture.
3. Set a name for your capture.
Click inside the **Name** field to generate a drop-down menu of available **prefixes** and select one (likely, this will be the name of your organization). Append a unique capture name after the `/` to create the full name, for example, `acmeCo/myFirestoreCapture`.
5. Fill out the required properties for Firestore.
4. Fill out the required properties for Firestore.
- **Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
- **Credentials**: The JSON service account key created per the prerequisites.
**Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
**Credentials**: The JSON service account key created per the prerequisites.
6. Click **Discover Endpoint**.
5. Click **Next**.
Flow uses the provided configuration to initiate a connection with Firestore. It generates a capture specification and details of the collections that it will create, once published.
Flow uses the provided configuration to initiate a connection with Firestore. It maps each collection in the Firestore database to a Flow collection.
7. Use the **Specification Editor** to view the [JSON schemas](https://docs.estuary.dev/concepts/schemas/) for each collection and make sure they are formatted correctly for your needs. If they're not, you can edit them.
6. Optionally, use the **Collection Selector** to remove any collections you don't need to migrate to Supabase.
8. Click **Save and publish**.
7. Click **Save and Publish**.
You'll see a notification when the capture publishes successfully.
You'll see a notification when the capture publishes successfully.
The data currently in your Firestore database has been captured, and future updates to it will be captured continuously.
The data currently in your Firestore database has been captured to Flow, and future updates to it will be captured continuously.
Click **Materialize Collections** to continue.
Click **Materialize Collections** to continue.
## Step 2: Materialize your collections to Postgres
@@ -72,23 +71,31 @@ A form appears with the properties required for a Postgres materialization.
2. Choose a unique name for your materialization like you did when naming your capture; for example, `acmeCo/mySupabaseMaterialization`.
3. Fill out the required properties for PostgreSQL. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
3. Fill out the required properties for Postgres. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
- **Address**: Format at `<host>:<port>`.
- **User**: Usually, this is `postgres`.
- **Password**: The password you set when you created your Supabase project.
**Address**: Format at `<host>:<port>`.
**User**: Usually, this is `postgres`.
**Password**: The password you set when you created your Supabase project.
4. Scroll down to view the **Collection Selector** and fill in the **Table** field for each collection.
4. Click **Next**.
The collections you just created have already been selected, but you must provide names for the tables to which they'll be materialized.
Flow initiates a connection with the database and the **Collection Selector** expands.
It's populated with your collections from Firestore, each mapped to a Postgres table.
5. Click **Discover Endpoint**.
5. For each collection, apply a stricter JSON schema.
This ensure that the less-structured Firestore data will be written to a Postgres table in the correct shape.
Flow uses the provided configuration to initiate a connection to your Supabase Postgres database and generate a specification.
In the Collection Selector, choose a collection and click its **Specification** tab.
Click **Schema Inference**. Flow scans the data in your collection and infers a new schema to use for materialization.
Review the new schema and click **Apply Inferred Schema**.
6. Click **Save and Publish**. You'll see a notification when the full materialization publishes successfully.
6. Click **Save and Publish**. You'll see a notification when the materialization publishes successfully.
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
## Resources
@@ -0,0 +1,107 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'illa',
title: 'ILLA',
description:
'Get started with Supabase and ILLA, a low-code platform for developers that enables the rapid development and deployment of internal tools.',
}
This tutorial outlines the process of creating an Admin Panel using ILLA Builder and Supabase in a few simple steps. ILLA is a low-code platform for developers that enables the rapid development and deployment of internal tools. It allows for creating pages by dragging and dropping UI components, connecting to any database or API, and writing JavaScript. To learn more about ILLA and give it a try, visit their website at [https://www.illacloud.com/](https://www.illacloud.com/). Let's begin!
### Step 1: Set up your Back end on Supabase
On the [Supabase dashboard](https://app.supabase.com/projects), click `New project` and set the name to adminPanel.
![Create Supabase Project for ILLA Admin Panel](/docs/img/guides/integrations/illa/supabase-illa-project.png)
Create a new table by clicking on the `Create a new table` .
Supabase offers a variety of options for populating tables with data, including writing queries, creating schemas through a user interface, and uploading CSV files.
![Create Supabase Table for ILLA Admin Panel](/docs/img/guides/integrations/illa/supabase-table-1.png)
![Config Supabase Table for ILLA Admin Panel](/docs/img/guides/integrations/illa/supabase-table-2.png)
Fill out the info in the table. The database is now set up.
### Step 2: Build UI on ILLA Builder
On [ILLA Builder](https://fast-try.illacloud.com/), click Create New to create a new application.
![Create new project on ILLA Builder](/docs/img/guides/integrations/illa/supabase-illa-create-project.png)
Drag components from the `Insert` panel to the canvas.
Select the components on the canvas and configure the property on the `Inspect` panel.
As seen in the below screenshot, we have built a simple admin panel.
![Build UI with ILLA Builder](/docs/img/guides/integrations/illa/supabase-illa-UI.png)
### Step 3: Connect to Supabase and config CRUD
Note down the database connection information under [Project Settings](https://app.supabase.com/project/hdcfnsxpwwgboqomdrhp/settings/database) in Supabase.
![Note information in supabase](/docs/img/guides/integrations/illa/supabase-information.png)
In the Action List, click `+ New` and select Supabase DB.
![Connect ILLA to Supabase](/docs/img/guides/integrations/illa/supabase-illa-connect.png)
Fill out the form to connect to your Supabase instance. Test connection and save resource.
![Config Supabase in ILLA](/docs/img/guides/integrations/illa/supabase-illa-connect-2.png)
Click `Create Action` to create an action with the Supabase resource and config your CRUD.
![Select Supabase resource in ILLA](/docs/img/guides/integrations/illa/supabase-illa-select.png)
Use `{{` to get the front-end input data. The following is an example of the User Management page in the Admin Panel.
Search for a user by the name inputted in input1
```
SELECT *
FROM user
WHERE name = "{{input1.value}}"
;
```
Update user data. Update user information when id matches
```
UPDATE user
SET name = "{{input3.value}}"
, email = "{{input4.value}}"
WHERE id="{{input2.value}}"
;
```
Insert user data
```
INSERT INTO user VALUES("{{input5.value}}","{{input6.value}}","{{input7.value}}");
```
Delete a user by id
```
DELETE FROM user WHERE id = "{{input2.value}}";
```
### Step 4: Show data on components
Configure the properties of components with `{{` . For example:
![Show Supabase data on ILLA components](/docs/img/guides/integrations/illa/supabase-illa-show-data.png)
## Resources
- [ILLA Cloud official website](https://www.illacloud.com/)
- [ILLA Cloud GitHub](https://github.com/illacloud/illa-builder)
- [ILLA Cloud documentation](https://www.illacloud.com/docs/about-illa)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+17 -2
View File
@@ -23,7 +23,14 @@ Note your application key from the Keys and Credentials section - this will be u
# Web
[![Keyri-Supabase Web Integration Video](http://img.youtube.com/vi/jrjrcpc2PFQ/0.jpg)](http://www.youtube.com/watch?v=jrjrcpc2PFQ 'Supabase & Keyri | Web Integration Overview')
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/jrjrcpc2PFQ"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
For your web app, first download KeyriQR.html (available [here](https://raw.githubusercontent.com/Keyri-Co/library-keyri-connect/main/KeyriQR.html)) and save it to a public directory.
@@ -64,7 +71,15 @@ That's it!
# Mobile
[![Keyri-Supabase Mobile Integration Video](http://img.youtube.com/vi/oGMsSKyh6tc/0.jpg)](http://www.youtube.com/watch?v=oGMsSKyh6tc 'Supabase & Keyri | Mobile Integration Overview')
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/oGMsSKyh6tc"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
### Install Flutter
@@ -14,7 +14,18 @@ export const meta = {
- Emails
- In-app notifications
This guide steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.
Here is William giving us the overview of how OneSignal can work with Supabase to send notifications to your users.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/mw0DLwItue4"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
In this guide, we will build a similar app and steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.
![Entity Diagram](/docs/img/guides/integrations/onesignal/diagram.png)
@@ -68,7 +68,7 @@ By default, Prisma migrations will try to drop the `postgres` database, which ca
Create a shadow database in your PostgreSQL server within the same Supabase project using the `psql` CLI and the `DATABASE_URL` from the previous steps (or use the local database).
```bash
psql postgresql://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.red:5432
psql postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432
```
After you connect to your project's PostgreSQL instance, create another database (e.g., `postgres_shadow`):
@@ -83,8 +83,8 @@ In the `.env` file, update `DATABASE_URL` and `SHADOW_DATABASE_URL` to the conne
```env
# .env
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres"
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres_shadow"
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow"
```
In the `schema.prisma` file, change the `provider` from "sqlite" to `"postgresql"` and add the `shadowDatabaseUrl` property.
@@ -145,8 +145,8 @@ When migrating, you need to use the non-pooled connection URL (like the one used
```env
# .env
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:6543/postgres?pgbouncer=true&connection_limit=1"
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres_shadow"
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true&connection_limit=1"
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow"
```
Prisma Migrate uses database transactions to check out the current state of the database and the migrations table. However, the Migration Engine is designed to use a single connection to the database, and does not support connection pooling with PgBouncer. If you attempt to run Prisma Migrate commands in any environment that uses PgBouncer for connection pooling, you might see the following error:
@@ -19,15 +19,11 @@ This value is also reported in the [database settings page](https://app.supabase
## Database storage management
Your project will be changed to read-only mode once it reaches 95% of your subscription quota, preventing further writes to your project.
In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`.
Supabase uses network-attached storage to balance performance with scalability. For Pro and Enterprise projects, database storage expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the database storage quota. Automatic database storage expansion can only occur once every six hours. Pro projects can store up to 1024TB.
Regular operation (read-write mode) is automatically re-enabled once database storage usage falls under 95%.
All projects enter read-only mode when you reach 95% of the database storage quota. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the database storage quota.
If you are on Pro or Enterprise, database storage expands automatically, however you may also encounter this error if fill the database faster than it can expand.
Supabase uses network-attached storage, which balances performance with scalability. Pro projects can store up to 1024TB.
If you intend to grow beyond this, reach out to our team to learn more about our Enterprise tier.
If you need more than 1024TB of database storage or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
### Increasing available database storage
@@ -73,6 +73,11 @@ After developing your project and deciding it's Production Ready, you should run
- Supabase provides CAPTCHA protection on the signup, sign-in and password reset endpoints. Please refer to [our guide](/docs/guides/auth/auth-captcha) on how to protect against abuse using this method.
### Email Link Validity
- When working with enterprise systems, email scanners may scan and make a `GET` request to the reset password link or sign up link in your email. Since links in Supabase Auth are single use, a user who opens an email post-scan to click on a link will receive an error. To get around this problem,
consider altering the email template to replace the original magic link with a link to a domain you control. The domain can present the user with a "Sign-in" button which redirect the user to the original magic link URL when clicked.
## Next steps
This checklist is always growing so be sure to check back frequently, and also feel free to suggest additions and amendments by making a PR on [GitHub](https://github.com/supabase/supabase).
@@ -0,0 +1,41 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'http-status-codes',
title: 'HTTP Status Codes',
description: 'HTTP Status Codes used by the Supabase platform',
}
The Supabase platform offers several HTTP APIs for each project. These APIs can use the status codes to indicate the state of the project, and the request being processed. The status codes returned for requests can be access via the [logs explorer](../platform/logs#logs-explorer).
## 2XX Success
2XX status codes indicate that the request was processed successfully.
## 3XX Redirects
3XX status codes indicate that the client must initiate another course of action to have the request processed successfully. The most popular usage of 3XX codes is to redirect the client to a different location.
## 4XX Client Errors
4XX status codes indicate an issue on the client's end with the request being made. These could include missing or invalid auth information, a malformed request, making too many requests in too short a time period ("rate limiting"), or a network issue on the client's end.
## 5XX Server or Project Errors
5XX status codes indicate that the project was unable to process the request successfully, but not because of an issue with the client's request.
5XX status codes can be the result of the project not having enough [compute](/docs/guides/platform/compute-add-ons) to process a complex request being made by a client or not being able to keep up with the volume of requests made against the project.
### 54X Project Errors
54X status codes are custom codes used by the Supabase platform to indicate the state of the project.
#### 540 Project Paused
The project the request was being made against has been paused. The project cannot process requests until it is un-paused by the owner.
Free-tier projects may be paused due to inactivity, on request by the owner, or in rare instances, due to abuse.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -12,6 +12,8 @@ In some cases, access to new features require upgrading or migrating your Supaba
## Upgrade your project
<Admonition type="note">This is only available for projects on the Free plan.</Admonition>
When you pause and restore a project, the restored database includes the latest features. This method _does_ include downtime, so be aware that your project will be inaccessible for a short period of time.
1. On the [General Settings](https://app.supabase.com/project/_/settings/general) page in the Dashboard, click **Pause project**. You will be redirected to the home screen as your project is pausing. This process can take several minutes.
@@ -0,0 +1,57 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'troubeshooting',
title: 'Troubleshooting',
description: 'Diagnosing and fixing issues with your hosted Supabase project.',
}
Learn how to diagnose and fix common issues with your Supabase project.
## HTTP API Issues
Symptoms:
- HTTP timeouts
- 5xx response codes
- High response times
### Under-provisioned resources
The most common class of issues that causes HTTP timeouts and 5xx response codes is the under-provisioning of resources for your project. This can cause your project to be unable to service the traffic it is receiving.
Each Supabase project is provisioned with [segregated compute resources](../platform/compute-add-ons). This allows the project to serve unlimited requests, as long as they can be handled using the resources that have been provisioned. Complex queries, or queries that process larger amounts of data, will require higher amounts of resources. As such, the amount of resources that can handle a high volume of simple queries (or queries involving small amounts of data), will likely be unable to handle a similar volume of complex queries.
You can view the resource utilization of your Supabase Project using the [reports in the Dashboard](https://app.supabase.com/project/_/reports/database).
Some common solutions for this issue are:
- [Upgrading](https://app.supabase.com/project/_/settings/billing/subscription) to a [larger compute add-on](../platform/compute-add-ons) in order to serve higher volumes of traffic.
- [Optimizing the queries](../platform/performance#examining-query-performance) being executed.
- [Using fewer Postgres connections](../platform/performance#configuring-clients-to-use-fewer-connections) can reduce the amount of resources needed on the project.
- [Restarting](https://app.supabase.com/project/_/settings/general) the database. This only temporarily solves the issue by terminating any ongoing workloads that might be tying up your compute resources.
If your [Daily Disk IO budget](../platform/compute-add-ons#disk-io-bandwidth) has been drained, you will need to either wait for it to be replenished the next day, or upgrade to a larger compute add-on to increase the budget available to your project.
## Unable to connect to your Supabase Project
Symptom: You're unable to connect to your Postgres database directly, but can open the Project in the [Supabase Dashboard](https://app.supabase.com/project/_/).
### Too many open connections
Errors about too many open connections can be _temporarily_ resolved by [restarting the database](https://app.supabase.com/project/_/settings/general). However, this won't solve the underlying issue for a permanent solution.
- If you're receiving a `No more connections allowed (max_client_conn)` error:
- Configure your applications and services to [use fewer connections](../platform/performance#configuring-clients-to-use-fewer-connections).
- [Upgrade](https://app.supabase.com/project/_/settings/billing/subscription) to a [larger compute add-on](../platform/compute-add-ons) to increase the number of available connections.
- If you're receiving a `sorry, too many clients already` or `remaining connection slots are reserved for non-replication superuser connections` error message in addition to the above suggestions, switch to using the [connection pooler](/docs/guides/database/connecting-to-postgres#connection-pool) instead.
### Connection refused
If you receive a `connection refused` error after a few initial failed connection attempts, your client has likely been temporarily blocked in order to protect the database from brute-force attacks. You can wait 30 minutes before trying again with the correct password, or you can [contact support](https://app.supabase.com/support/new) with your client's IP address to manually unblock you.
If you're also unable to open the project using the [Supabase Dashboard](https://app.supabase.com/project/_/), review the solutions for [under-provisioned projects](#under-provisioned-resources).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -121,7 +121,7 @@ channel.subscribe(async (status) => {
})
```
## Track and display which users are online
## Track and Display Which Users Are Online
[Presence](/docs/guides/realtime#presence) stores and synchronize shared state across clients. The `sync` event is triggered whenever the shared state changes. The `join` event is triggered when new clients join the channel and `leave` event is triggered when clients leave.
-14
View File
@@ -64,20 +64,6 @@ export const meta = {
</div>
<div className="grid gap-16 md:grid-cols-2">
<div>
### Supabase CLI
Develop your project locally, manage your Supabase projects, handle database migrations, CI/CD workflows, and generate types from your database schema, all from the comfort of your Terminal.
[Learn more](/docs/guides/resources/supabase-cli)
</div>
</div>
{/* end of container */}
</div>
@@ -95,6 +95,10 @@ POSTGRES_PORT=5432
Once you have done this, you can safely comment out the `db` section of the `docker-compose` file, and remove any instances where the services `depends_on` the `db` image.
Supabase services require your external database to be initialized with a specific schema. Refer to our [postgres/migrations](https://github.com/supabase/postgres/tree/develop/migrations) repository for instructions on running these migrations.
Note that you need superuser permission on the postgres role to perform the initial schema migration. Once completed, the postgres role will be demoted to non-superuser to prevent abuse.
### Setting database's `log_min_messages`
By default, `docker compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime.
@@ -117,10 +117,8 @@ using ( bucket_id = 'public' );
-- 1. Allow logged-in access to any files in the "restricted" bucket
create policy "Restricted Access"
on storage.objects for select
using (
bucket_id = 'restricted'
and auth.role() = 'authenticated'
);
to authenticated
using ( bucket_id = 'restricted' );
```
### Allow individual access to a file
@@ -14,6 +14,15 @@ Supabase Storage offers the functionality to transform and resize images dynamic
above](https://supabase.com/pricing).
</Admonition>
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/dLqSmxX3r7I"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Get a public URL for a transformed image
You can pass a `transform` option to the functions you are currently using to interact with your objects. This returns the public URL that serves the resized image.
@@ -59,6 +68,32 @@ supabase.storage.from('bucket').download('image.jpg', {
})
```
## Automatic Image Optimisation (WebP)
When using the image transformation API we will automatically find the best format supported by the browser and return that to the client, without any code change.
For instance, if you use Chrome when viewing a jpeg image and using transformation options, you'll see that the content-type returned is `webp`.
As a result, this will lower the bandwidth that you send to your users and your application will load much faster.
<Admonition type="note">
We currently only support WebP. AVIF support will come in the near future
</Admonition>
**Disabling automatic optimisation:**
In case you'd like to return the original format of the image and **opt-out** from the automatic image optimization detection, you can pass the `format=origin` parameter when requesting a transformed image,
this is also supported in the JavaScript SDK starting from v2.2.0
```ts
await storage.from('bucket').download('image.jpeg', {
transform: {
width: 200,
height: 200,
format: 'origin',
},
})
```
## NextJS Loader
You can use Supabase Image Transformation to optimise your NextJS images using a custom [Loader](https://nextjs.org/docs/api-reference/next/image#loader-configuration).
@@ -69,7 +104,7 @@ To get started, create a `supabase-image-loader.js` file in your NextJS project
const projectId = '' // your supabase project id
export default function supabaseLoader({ src, width, height, quality }) {
return `https://${projectId}.supabase.co/storage/v1/${src}?width=${width}&height=${
return `https://${projectId}.supabase.co/storage/v1/render/image/public/${src}?width=${width}&height=${
height || 0
}&quality=${quality || 75}`
}
@@ -149,6 +184,34 @@ supabase.storage.from('bucket').download('image.jpg', {
| BMP | `bmp` | ☑️ | ☑️ |
| TIFF | `tiff` | ☑️ | ☑️ |
## Self Hosting
Our solution to image resizing and optimisation can be self-hosted as with any other Supabase product.
Under the hood we use the awesome [Imgproxy](https://imgproxy.net/)
#### Imgproxy Configuration:
Simply deploy an imgproxy container with the following configuration:
```yaml
imgproxy:
image: darthsim/imgproxy
environment:
- IMGPROXY_ENABLE_WEBP_DETECTION=true
- IMGPROXY_JPEG_PROGRESSIVE=true
```
Note: make sure that this service can only be reachable within an internal network and not exposed to the public internet
#### Storage API Configuration:
Once [Imgproxy](https://imgproxy.net/) is deployed we need to configure a couple of environment variables in your self-hosted [storage-api](https://github.com/supabase/storage-api) service as follows:
```shell
ENABLE_IMAGE_TRANSFORMATION=true
IMGPROXY_URL=yourinternalimgproxyurl.internal.com
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-159
View File
@@ -1,159 +0,0 @@
import Layout from '~/layouts/DefaultLayout'
import Link from 'next/link'
import { GlassPanel } from 'ui'
export const meta = {
title: 'Tutorials',
}
### Webapps
<div className="grid grid-cols-12 gap-6 not-prose">
{webapps.map((item) => {
return (
<Link href={`/${item.href}`} key={item.title} passHref>
<a className={'col-span-4'}>
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
{item.description}
</GlassPanel>
</a>
</Link>
)
})}
</div>
### Mobile
<div className="grid grid-cols-12 gap-6 not-prose">
{mobile.map((item) => {
return (
<Link href={`/${item.href}`} key={item.title} passHref>
<a className={'col-span-4'}>
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
{item.description}
</GlassPanel>
</a>
</Link>
)
})}
</div>
### Low code
<div className="grid grid-cols-12 gap-6 not-prose">
{lowCode.map((item) => {
return (
<Link href={`/${item.href}`} key={item.title} passHref>
<a className={'col-span-4'}>
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
{item.description}
</GlassPanel>
</a>
</Link>
)
})}
</div>
export const webapps = [
{
title: 'NextJS',
href: '/guides/tutorials/with-nextjs',
description:
'Learn how to build a user management app with NextJS and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/nextjs-icon',
},
{
title: 'React',
href: '/guides/tutorials/with-react',
description:
'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/react-icon',
},
{
title: 'Vue 3',
href: '/guides/tutorials/with-vue-3',
description:
'Learn how to build a user management app with Vue 3 and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/vuejs-icon',
},
{
title: 'Nuxt 3',
href: '/guides/tutorials/with-nuxt-3',
description:
'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/nuxt-icon',
},
{
title: 'Angular',
href: '/guides/tutorials/with-angular',
description:
'Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/angular-icon',
},
{
title: 'RedwoodJS',
href: '/guides/tutorials/with-redwoodjs',
description:
'Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/redwood-icon',
},
{
title: 'Svelte',
href: '/guides/tutorials/with-svelte',
description:
'Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/svelte-icon',
},
{
title: 'SvelteKit',
href: '/guides/tutorials/with-sveltekit',
description:
'Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/svelte-icon',
},
]
export const mobile = [
{
title: 'Flutter',
href: '/guides/tutorials/with-flutter',
description:
'Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/flutter-icon',
},
{
title: 'Expo',
href: '/guides/tutorials/with-expo',
description:
'Learn how to build a user management app with Expo and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/expo-icon',
},
]
export const lowCode = [
{
title: 'Ionic React',
href: '/guides/tutorials/with-ionic-react',
description:
'Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/ionic-icon',
},
{
title: 'Ionic Vue',
href: '/guides/tutorials/with-ionic-vue',
description:
'Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/ionic-icon',
},
{
title: 'Ionic Angular',
href: '/guides/tutorials/with-ionic-angular',
description:
'Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/ionic-icon',
},
]
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+19 -4
View File
@@ -69,13 +69,28 @@ export const meta = {
</Link>
<Link href={`/guides/getting-started/quickstarts/nextjs`} passHref>
<a className="no-underline">
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
</a>
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
</a>
</Link>
<Link href={`/guides/getting-started/quickstarts/flutter`} passHref>
<a className="no-underline">
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
</a>
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
</a>
</Link>
<Link href={`/guides/getting-started/quickstarts/sveltekit`} passHref>
<a className="no-underline">
<IconPanel title="SvelteKit" icon="/docs/img/icons/svelte-icon" />
</a>
</Link>
<Link href={`/guides/getting-started/quickstarts/solidjs`} passHref>
<a className="no-underline">
<IconPanel title="SolidJS" icon="/docs/img/icons/solidjs-icon" />
</a>
</Link>
<Link href={`/guides/getting-started/quickstarts/vue`} passHref>
<a className="no-underline">
<IconPanel title="Vue" icon="/docs/img/icons/vuejs-icon" />
</a>
</Link>
</div>
</div>
-77
View File
@@ -1,77 +0,0 @@
import Layout from '~/layouts/DefaultLayout'
import Link from 'next/link'
// import { useTheme } from 'common/Providers'
import { GlassPanel } from 'ui'
{/* // export const { isDarkMode } = useTheme() */}
export const meta = {
title: 'Auth',
}
## Add and manage email and password, passwordless, OAuth, and mobile logins to your project through a suite of identity providers and APIs.
### Popular Guides
<div className="grid grid-cols-12 gap-6 not-prose">
{topArticles.map((item) => {
return (
<Link
href={`/${item.href}`}
key={item.title}
passHref
>
<a className={item.span ?? 'col-span-4'}>
<GlassPanel
title={item.title}
header="/docs/img/cards/sample-card-header-4.svg"
span="col-span-6"
background={false}
//* isDarkMode={isDarkMode}
>
{item.description}
</GlassPanel>
</a>
</Link>
)})}
</div>
export const topArticles = [
{
title: 'Enable Captcha Protection',
header: '/docs/img/cards/sample-card-header-4.svg',
href: '/guides/auth/auth-captcha',
description: 'Keep your platform safe from bots and malicious scripts by implementing hCaptcha.',
span: 'col-span-6',
},
{
title: 'Auth Helpers',
header: '/docs/img/cards/sample-card-header-4.svg',
href: '/guides/auth/auth-helpers',
description: 'A collection of framework-specific utilities for implementing Supabase Auth.',
span: 'col-span-6',
},
{
title: 'Email Login',
header: '/docs/img/cards/sample-card-header-4.svg',
href: '/guides/auth/auth-email',
description: 'Learn how to add an email and password login flow to your platform.',
},
{
title: 'Phone Login',
header: '/docs/img/cards/sample-card-header-4.svg',
href: '/guides/auth/phone-login',
description: 'Learn how to add a login flow so your users can access your platform through one-time passwords via SMS.',
},
{
title: 'Social Login',
header: '/docs/img/cards/sample-card-header-4.svg',
href: '/guides/auth/social-login',
description: 'Learn how to add a login flow so your users can access your platform through social providers like Apple, Facebook, or Google.',
},
]
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
View File
Whitespace-only changes.
+30 -7
View File
@@ -2,16 +2,31 @@
import specFile from '~/../../spec/cli_v1_config.yaml' assert { type: 'yml' }
import { Parameter } from '~/lib/refGenerator/refTypes'
import ReactMarkdown from 'react-markdown'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import { Heading } from '~/components/CustomHTMLElements'
// Parameters are grouped on the page by tag
const TAGS = ['general', 'auth', 'api', 'database', 'dashboard', 'local']
const TAGS = ['general', 'auth', 'api', 'database', 'dashboard', 'local', 'edge-functions']
const tocList = TAGS.map((tag) =>
specFile.parameters
.filter((param: Parameter) => param.tags[0] === tag)
.map((parameter) => {
console.log('the param', parameter)
const text = parameter.id
const link = `#${parameter.id}`
const level = '2'
return { text, link, level }
})
).flat()
export default function Config() {
return (
<div>
<div className="flex my-16">
<div className="grid grid-cols-12 relative gap-4">
<div className="relative col-span-12 md:col-span-9 transition-all ease-out duration-100">
<div className="w-full prose">
<h1 className="text-4xl mb-16">{specFile.info.title}</h1>
<h1 className="">CLI configuration</h1>
<div className="max-w-xs w-32 h-[1px] bg-gradient-to-r from-brand-800 to-brand-900 my-8"></div>
<ReactMarkdown>{specFile.info.description}</ReactMarkdown>
<div>
{TAGS.map((tag) =>
@@ -22,11 +37,11 @@ export default function Config() {
{index === 0 && <h2 className="text-xl capitalize">{tag}</h2>}
<div className="mt-8">
<div>
<h2 className="text-xl font-medium text-scale-1200 font-mono">
<Heading tag="h2" parseAnchors={false} customAnchor={parameter.id}>
<span className="mr-2">$</span>
{parameter.title}
</h2>
<div className="grid" id={parameter.id}>
</Heading>
<div className="grid">
<div className="border-b pb-8" key={parameter.id}>
<div className=" mb-16">
<p className="mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
@@ -63,6 +78,14 @@ export default function Config() {
</div>
</div>
</div>
<div className="md:col-span-3">
<div className="sticky top-20 border-l">
<span className="block font-mono text-xs uppercase text-scale-1200 pl-5 mb-4">
On this page
</span>
<GuidesTableOfContents list={tocList} />
</div>
</div>
</div>
)
}
@@ -0,0 +1,30 @@
import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json'
import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json'
// @ts-expect-error
import spec from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
const sections = flattenSections(clientLibsCommonSections)
export default function PyReference(props) {
return (
<RefSectionHandler
sections={sections}
spec={spec}
typeSpec={typeSpec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, '/python')
}
export function getStaticPaths() {
return handleRefGetStaticPaths()
}
@@ -0,0 +1,44 @@
import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json'
import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json'
// @ts-expect-error
import spec from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/router'
import RefSEO from '~/components/reference/RefSEO'
const sections = flattenSections(clientLibsCommonSections)
export default function PyReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.id === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
sections={filteredSection}
spec={spec}
typeSpec={typeSpec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, '/python')
}
export function getStaticPaths() {
return handleRefGetStaticPaths()
}
-724
View File
@@ -1,724 +0,0 @@
import Layout from '~/layouts/tutorials/TutorialLayout'
export const meta = {
title: 'Creating a user management dashboard with NextJS and Supabase',
author: 'Rich Haines',
video: 'https://www.youtube-nocookie.com/embed/0Fs96oZ4se0',
}
In this 25-minute guide, we’ll be building a collaborative user managment app using NextJS and Supabase.
As users add and move rectangles in a canvas, changes will be automatically synced and persisted, allowing for a canvas that updates in real-time across clients.
Users will also be able to see other users selections, and undo and redo actions.
![Supabase User Management example](/docs/img/user-management-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management).
## Building the App
Let's start building the Next.js app from scratch.
### Initialize a Next.js app
We can use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize
an app called `supabase-nextjs`:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<Tabs.Panel id="js" label="JavaScript">
```bash
npx create-next-app@latest --use-npm supabase-nextjs
cd supabase-nextjs
```
</Tabs.Panel>
<Tabs.Panel id="ts" label="TypeScript">
```bash
npx create-next-app@latest --ts --use-npm supabase-nextjs
cd supabase-nextjs
```
</Tabs.Panel>
</Tabs>
Then install the Supabase client library: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env.local`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env.local
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
And one optional step is to update the CSS file `styles/globals.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-ts-user-management/styles/globals.css).
### Set up a Login component
#### Supabase Auth Helpers
Next.js is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and middleware edge-functions.
It can be challenging to authenticate your users in all these different environments, that's why we've created the [Supabase Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to make user management and data fetching within Next.js as easy as possible.
Install the auth helpers for React and Next.js
```bash
npm install @supabase/auth-helpers-react @supabase/auth-helpers-nextjs
```
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<Tabs.Panel id="js" label="JavaScript">
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
```jsx title=pages/_app.js
import '../styles/globals.css'
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider } from '@supabase/auth-helpers-react'
function MyApp({ Component, pageProps }) {
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
return (
<SessionContextProvider
supabaseClient={supabaseClient}
initialSession={pageProps.initialSession}
>
<Component {...pageProps} />
</SessionContextProvider>
)
}
export default MyApp
```
</Tabs.Panel>
<Tabs.Panel id="ts" label="TypeScript">
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
```jsx lines=2,8 title=pages/_app.tsx
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
function MyApp({
Component,
pageProps,
}: AppProps<{
initialSession: Session,
}>) {
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
return (
<SessionContextProvider
supabaseClient={supabaseClient}
initialSession={pageProps.initialSession}
>
<Component {...pageProps} />
</SessionContextProvider>
)
}
export default MyApp
```
See the [Auth Helpers docs](/docs/guides/auth/auth-helpers/nextjs#usage-with-typescript) for more details on usage with TypeScript.
</Tabs.Panel>
</Tabs>
#### Supabase Auth UI
We can use the [Supabase Auth UI](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui) a pre-built React component for authenticating users via OAuth, email, and magic links.
Install the Supabase Auth UI for React
```bash
npm install @supabase/auth-ui-react
```
Add the `Auth` component to your home page
```jsx title=pages/index.js
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
const Home = () => {
const session = useSession()
const supabase = useSupabaseClient()
return (
<div className="container" style={{ padding: '50px 0 100px 0' }}>
{!session ? (
<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
) : (
<p>Account page will go here.</p>
)}
</div>
)
}
export default Home
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<Tabs.Panel id="js" label="JavaScript">
Let's create a new component for that called `Account.js` within a `components` folder.
```tsx title=components/Account.js
import { useState, useEffect } from 'react'
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
export default function Account({ session }) {
const supabase = useSupabaseClient()
const user = useUser()
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState(null)
const [website, setWebsite] = useState(null)
const [avatar_url, setAvatarUrl] = useState(null)
useEffect(() => {
getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
alert('Error loading user data!')
console.log(error)
} finally {
setLoading(false)
}
}
async function updateProfile({ username, website, avatar_url }) {
try {
setLoading(true)
const updates = {
id: user.id,
username,
website,
avatar_url,
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
alert('Profile updated!')
} catch (error) {
alert('Error updating the data!')
console.log(error)
} finally {
setLoading(false)
}
}
return (
<div className="form-widget">
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<button
className="button primary block"
onClick={() => updateProfile({ username, website, avatar_url })}
disabled={loading}
>
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button className="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
)
}
```
</Tabs.Panel>
<Tabs.Panel id="ts" label="TypeScript">
Let's create a new component for that called `Account.tsx` within a `components` folder.
```tsx title=components/Account.tsx
import { useState, useEffect } from 'react'
import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react'
import { Database } from '../utils/database.types'
type Profiles = Database['public']['Tables']['profiles']['Row']
export default function Account({ session }: { session: Session }) {
const supabase = useSupabaseClient<Database>()
const user = useUser()
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState<Profiles['username']>(null)
const [website, setWebsite] = useState<Profiles['website']>(null)
const [avatar_url, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
useEffect(() => {
getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
if (!user) throw new Error('No user')
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
alert('Error loading user data!')
console.log(error)
} finally {
setLoading(false)
}
}
async function updateProfile({
username,
website,
avatar_url,
}: {
username: Profiles['username']
website: Profiles['website']
avatar_url: Profiles['avatar_url']
}) {
try {
setLoading(true)
if (!user) throw new Error('No user')
const updates = {
id: user.id,
username,
website,
avatar_url,
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
alert('Profile updated!')
} catch (error) {
alert('Error updating the data!')
console.log(error)
} finally {
setLoading(false)
}
}
return (
<div className="form-widget">
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<button
className="button primary block"
onClick={() => updateProfile({ username, website, avatar_url })}
disabled={loading}
>
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button className="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
)
}
```
</Tabs.Panel>
</Tabs>
### Launch!
Now that we have all the components in place, let's update `pages/index.js`:
```jsx lines=3,14 title=pages/index.js
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
import Account from '../components/Account'
const Home = () => {
const session = useSession()
const supabase = useSupabaseClient()
return (
<div className="container" style={{ padding: '50px 0 100px 0' }}>
{!session ? (
<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
) : (
<Account session={session} />
)}
</div>
)
}
export default Home
```
Once that's done, run this in a terminal window:
```bash
npm run dev
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
photos and videos.
### Create an upload widget
Let's create an avatar widget for the user so that they can upload a profile photo. We can start by creating a new component:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<Tabs.Panel id="js" label="JavaScript">
```jsx title=components/Avatar.js
import React, { useEffect, useState } from 'react'
import { useSupabaseClient } from '@supabase/auth-helpers-react'
export default function Avatar({ uid, url, size, onUpload }) {
const supabase = useSupabaseClient()
const [avatarUrl, setAvatarUrl] = useState(null)
const [uploading, setUploading] = useState(false)
useEffect(() => {
if (url) downloadImage(url)
}, [url])
async function downloadImage(path) {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error)
}
}
const uploadAvatar = async (event) => {
try {
setUploading(true)
if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${uid}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file, { upsert: true })
if (uploadError) {
throw uploadError
}
onUpload(filePath)
} catch (error) {
alert('Error uploading avatar!')
console.log(error)
} finally {
setUploading(false)
}
}
return (
<div>
{avatarUrl ? (
<img
src={avatarUrl}
alt="Avatar"
className="avatar image"
style={{ height: size, width: size }}
/>
) : (
<div className="avatar no-image" style={{ height: size, width: size }} />
)}
<div style={{ width: size }}>
<label className="button primary block" htmlFor="single">
{uploading ? 'Uploading ...' : 'Upload'}
</label>
<input
style={{
visibility: 'hidden',
position: 'absolute',
}}
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading}
/>
</div>
</div>
)
}
```
</Tabs.Panel>
<Tabs.Panel id="ts" label="TypeScript">
```tsx title=components/Avatar.tsx
import React, { useEffect, useState } from 'react'
import { useSupabaseClient } from '@supabase/auth-helpers-react'
import { Database } from '../utils/database.types'
type Profiles = Database['public']['Tables']['profiles']['Row']
export default function Avatar({
uid,
url,
size,
onUpload,
}: {
uid: string
url: Profiles['avatar_url']
size: number
onUpload: (url: string) => void
}) {
const supabase = useSupabaseClient<Database>()
const [avatarUrl, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
const [uploading, setUploading] = useState(false)
useEffect(() => {
if (url) downloadImage(url)
}, [url])
async function downloadImage(path: string) {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error)
}
}
const uploadAvatar: React.ChangeEventHandler<HTMLInputElement> = async (event) => {
try {
setUploading(true)
if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${uid}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file, { upsert: true })
if (uploadError) {
throw uploadError
}
onUpload(filePath)
} catch (error) {
alert('Error uploading avatar!')
console.log(error)
} finally {
setUploading(false)
}
}
return (
<div>
{avatarUrl ? (
<img
src={avatarUrl}
alt="Avatar"
className="avatar image"
style={{ height: size, width: size }}
/>
) : (
<div className="avatar no-image" style={{ height: size, width: size }} />
)}
<div style={{ width: size }}>
<label className="button primary block" htmlFor="single">
{uploading ? 'Uploading ...' : 'Upload'}
</label>
<input
style={{
visibility: 'hidden',
position: 'absolute',
}}
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading}
/>
</div>
</div>
)
}
```
</Tabs.Panel>
</Tabs>
### Add the new widget
And then we can add the widget to the Account page:
```jsx title=components/Account.js
// Import the new component
import Avatar from './Avatar'
// ...
return (
<div className="form-widget">
{/* Add to the body */}
<Avatar
uid={user.id}
url={avatar_url}
size={150}
onUpload={(url) => {
setAvatarUrl(url)
updateProfile({ username, website, avatar_url: url })
}}
/>
{/* ... */}
</div>
)
```
## Next steps
At this stage you have a fully functional application!
- See the complete [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) and deploy it to Vercel.
- Explore the [pre-built Auth UI for React](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui).
- Explore the [Auth Helpers for Next.js](https://supabase.com/docs/guides/auth/auth-helpers/nextjs).
- Explore the [Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers).
- See the [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) template on GitHub.
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export default ({ children }) => <Layout meta={meta} children={children} />
+3 -2
View File
@@ -15,11 +15,11 @@ Chase Granberry
Chris Copplestone
Danny Ng
Dave Wilson
David Thyresson
Div Arora
Egor Romanov
Fabrizio Fenoglio
Francisco Mazzoni
Haydn Maley
Hieu Pham
Inian P
Isaiah Hamilton
@@ -30,6 +30,7 @@ Jonny Summers-Muir
Joshen Lim
Kang Ming Tay
Karlo Ison
Kevin Brolly
Kevin Grüneberg
Lakshmipathi G
Laura C
@@ -47,7 +48,7 @@ Paul Copplestone
Pavel Borisov
Qiao Han
Ramiro Nuñez Dosio
Rodrigo Mansueli Nunes
Rodrigo Martins Mansueli
Rory Wilding
Stanislav M
Steve Chavez
Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Loaded 100 of 322 files, more files were not shown because too many files have changed in this diff. Show more