mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
Merge branch 'master' into feat/app-router
This commit is contained in:
commit
bbcf5bd0bf
902 files changed
+28755
-12842
No files matched your search
@@ -3,6 +3,9 @@
|
||||
|
||||
/apps/studio/ @supabase/Dashboard
|
||||
|
||||
# temporary until we've ironed out all the problems with sync workflow
|
||||
/apps/docs/content/troubleshooting/ @charislam
|
||||
|
||||
/apps/www/ @supabase/marketing
|
||||
/apps/www/public/images/blog @supabase/marketing
|
||||
/apps/www/lib/redirects.js
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: '[Docs] Sync troubleshooting guides to GitHub Discussions'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'apps/docs/content/troubleshooting/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-troubleshooting:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
SEARCH_GITHUB_APP_ID: ${{ secrets.SEARCH_GITHUB_APP_ID }}
|
||||
SEARCH_GITHUB_APP_INSTALLATION_ID: ${{ secrets.SEARCH_GITHUB_APP_INSTALLATION_ID }}
|
||||
SEARCH_GITHUB_APP_PRIVATE_KEY: ${{ secrets.SEARCH_GITHUB_APP_PRIVATE_KEY }}
|
||||
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SEARCH_SUPABASE_URL }}
|
||||
SUPABASE_SECRET_KEY: ${{ secrets.SEARCH_SUPABASE_SERVICE_ROLE_KEY }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
apps/docs
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run Troubleshooting script
|
||||
run: npm --prefix=apps/docs run troubleshooting:sync
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
||||
with:
|
||||
commit-message: '[bot] sync troubleshooting guides to db'
|
||||
title: '[bot] sync troubleshooting guides to db'
|
||||
author: 'github-docs-sync-bot <github-docs-sync-bot@supabase.com>'
|
||||
branch: 'bot/docs-sync-troubleshooting'
|
||||
branch-suffix: 'random'
|
||||
@@ -39,6 +39,7 @@ You will need to install and configure the following dependencies on your machin
|
||||
- [Git](http://git-scm.com/)
|
||||
- [Node.js v20.x (LTS)](http://nodejs.org)
|
||||
- [npm](https://www.npmjs.com/) version 10.x.x or higher
|
||||
- [make](https://www.gnu.org/software/make/) or the equivalent to `build-essentials` for your OS
|
||||
- [Docker](https://docs.docker.com/get-docker/) (to run studio locally)
|
||||
|
||||
## Local development
|
||||
|
||||
@@ -654,6 +654,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"dialog-centered-off": {
|
||||
name: "dialog-centered-off",
|
||||
type: "components:example",
|
||||
registryDependencies: ["dialog","button"],
|
||||
component: React.lazy(() => import("@/registry/default/example/dialog-centered-off")),
|
||||
source: "",
|
||||
files: ["registry/default/example/dialog-centered-off.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"drawer-demo": {
|
||||
name: "drawer-demo",
|
||||
type: "components:example",
|
||||
@@ -1787,6 +1798,72 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-demo": {
|
||||
name: "inner-side-menu-demo",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-demo")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-demo.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-static-titles": {
|
||||
name: "inner-side-menu-static-titles",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-static-titles")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-static-titles.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-loading": {
|
||||
name: "inner-side-menu-loading",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-loading")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-loading.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-multiple-sections": {
|
||||
name: "inner-side-menu-multiple-sections",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-multiple-sections")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-multiple-sections.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-empty": {
|
||||
name: "inner-side-menu-empty",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-empty")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-empty.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"inner-side-menu-with-search": {
|
||||
name: "inner-side-menu-with-search",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/inner-side-menu-with-search")),
|
||||
source: "",
|
||||
files: ["registry/default/example/inner-side-menu-with-search.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-demo": {
|
||||
name: "multi-select-demo",
|
||||
type: "components:example",
|
||||
@@ -1798,13 +1875,68 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-form": {
|
||||
name: "multi-select-form",
|
||||
"multi-select-disabled": {
|
||||
name: "multi-select-disabled",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-form")),
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-disabled")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-form.tsx"],
|
||||
files: ["registry/default/example/multi-select-disabled.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-badge-limit-wrap": {
|
||||
name: "multi-select-badge-limit-wrap",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-badge-limit-wrap")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-badge-limit-wrap.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-badge-limit": {
|
||||
name: "multi-select-badge-limit",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-badge-limit")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-badge-limit.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-combobox": {
|
||||
name: "multi-select-combobox",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-combobox")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-combobox.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-deletable-badge": {
|
||||
name: "multi-select-deletable-badge",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-deletable-badge")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-deletable-badge.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"multi-select-inline-search-input": {
|
||||
name: "multi-select-inline-search-input",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/multi-select-inline-search-input")),
|
||||
source: "",
|
||||
files: ["registry/default/example/multi-select-inline-search-input.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
|
||||
@@ -102,6 +102,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/info-tooltip',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Inner Side Menu',
|
||||
href: '/docs/fragments/inner-side-menu',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Form Item Layout',
|
||||
href: '/docs/fragments/form-item-layout',
|
||||
|
||||
@@ -4,6 +4,14 @@ description: Latest updates and announcements.
|
||||
toc: false
|
||||
---
|
||||
|
||||
## 18th Oct 2024 - Update multi-select
|
||||
|
||||
Improved Multi Select with combobox, badge limit, and more examples.
|
||||
|
||||
- [Multi Select](/design-system/components/multi-select)
|
||||
|
||||
[PR](https://github.com/supabase/supabase/pull/29695)
|
||||
|
||||
## 12th June 2024 - Button styles updated
|
||||
|
||||
- [`<Button />`](/design-system/docs/components/button) styles updated
|
||||
|
||||
@@ -13,44 +13,6 @@ source:
|
||||
|
||||
<ComponentPreview name="dialog-demo" peekCode wide />
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs defaultValue="cli">
|
||||
|
||||
<TabsList>
|
||||
<TabsTrigger value="cli">CLI</TabsTrigger>
|
||||
<TabsTrigger value="manual">Manual</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="cli">
|
||||
|
||||
```bash
|
||||
npx shadcn-ui@latest add dialog
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="manual">
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step>Install the following dependencies:</Step>
|
||||
|
||||
```bash
|
||||
npm install @radix-ui/react-dialog
|
||||
```
|
||||
|
||||
<Step>Copy and paste the following code into your project.</Step>
|
||||
|
||||
<ComponentSource name="dialog" />
|
||||
|
||||
<Step>Update the import paths to match your project setup.</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
@@ -85,6 +47,23 @@ import {
|
||||
|
||||
<ComponentPreview name="dialog-close-button" />
|
||||
|
||||
### Centered behaviour
|
||||
|
||||
You can control whether the dialog is centered by passing `centered={false}` to the `DialogContent` component.
|
||||
|
||||
```tsx {3}
|
||||
<Dialog>
|
||||
<ContextMenuTrigger>Click here</ContextMenuTrigger>
|
||||
<DialogContent centered={false}>
|
||||
{/*
|
||||
* Content in here
|
||||
*/}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
<ComponentPreview name="dialog-centered-off" />
|
||||
|
||||
## Notes
|
||||
|
||||
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or
|
||||
@@ -93,7 +72,7 @@ To activate the `Dialog` component from within a `Context Menu` or `Dropdown Men
|
||||
```tsx {14-25}
|
||||
<Dialog>
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>Right click</ContextMenuTrigger>
|
||||
<ContextMenuTrigger>Dialog not centered</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem>Open</ContextMenuItem>
|
||||
<ContextMenuItem>Download</ContextMenuItem>
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Inner Side Menu
|
||||
description: InnerSideMenu is a component that provides a collapsible side menu with multiple sections.
|
||||
component: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="inner-side-menu-demo" peekCode wide />
|
||||
|
||||
## Examples
|
||||
|
||||
{/* ### Multiple sections */}
|
||||
|
||||
{/* <ComponentPreview name="inner-side-menu-multiple-sections" peekCode /> */}
|
||||
|
||||
### Collapsible menus
|
||||
|
||||
Inner Side Menu uses the `<InnerSideMenuCollapsible />` component to wrap the menu items.
|
||||
This is a wrapper component around the `<Collapsible />` component.
|
||||
|
||||
```jsx {10-12,16-17}
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
function app() {
|
||||
return (
|
||||
<InnerSideMenuCollapsible key={category} defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title={category} />
|
||||
<InnerSideMenuCollapsibleContent>
|
||||
<InnerSideMenuItem href="/dashboard">Dashboard</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/team">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/settings">Settings</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Static titles
|
||||
|
||||
If you don't need the Collapsible features, you can instead use `<InnerSideBarTitle />`.
|
||||
This doesn't require the use of the `<InnerSideMenuCollapsible />` parent component wrapped around it.
|
||||
|
||||
You can wrap the `<InnerSideBarItem />` conponents however you like. Below we've put a `<div/ >` around them.
|
||||
|
||||
```jsx {1}
|
||||
<InnerSideBarTitle>Projects</InnerSideBarTitle>
|
||||
<div className="mt-2">
|
||||
<InnerSideMenuItem href="/dashboard">Dashboard</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/team">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/settings">Settings</InnerSideMenuItem>
|
||||
</div>
|
||||
```
|
||||
|
||||
<ComponentPreview name="inner-side-menu-static-titles" />
|
||||
|
||||
### Loading state
|
||||
|
||||
Use `<InnerSideMenuItemLoading />` to show a loading state for menu items.
|
||||
|
||||
This item is a wrapper around the `<Skeleton />` component, and also includes some y-padding so the items don't layout shift when shifting to loaded state and to look visually separated in a list.
|
||||
|
||||
<ComponentPreview name="inner-side-menu-loading" />
|
||||
|
||||
### With search
|
||||
|
||||
Use the following components to add a search input to the menu.
|
||||
|
||||
```jsx
|
||||
<InnerSideBarFilters>
|
||||
<InnerSideBarFilterSearchInput
|
||||
name="search-input"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
aria-labelledby="Search items"
|
||||
/>
|
||||
</InnerSideBarFilters>
|
||||
```
|
||||
|
||||
There is also a Filter Dropdown component that can be used to sort the menu items.
|
||||
This can be used inside `<InnerSideBarFilterSearchInput />`.
|
||||
|
||||
```jsx {9-16}
|
||||
<InnerSideBarFilters>
|
||||
<InnerSideBarFilterSearchInput
|
||||
name="search-input"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
aria-labelledby="Search items"
|
||||
>
|
||||
<InnerSideBarFilterSortDropdown value={sort} onValueChange={(value) => setSort(value)}>
|
||||
<InnerSideBarFilterSortDropdownItem value="alphabetical">
|
||||
Sort Alphabetically
|
||||
</InnerSideBarFilterSortDropdownItem>
|
||||
<InnerSideBarFilterSortDropdownItem value="reverse">
|
||||
Sort Reverse Alphabetically
|
||||
</InnerSideBarFilterSortDropdownItem>
|
||||
</InnerSideBarFilterSortDropdown>
|
||||
</InnerSideBarFilterSearchInput>
|
||||
</InnerSideBarFilters>
|
||||
```
|
||||
|
||||
<ComponentPreview name="inner-side-menu-with-search" />
|
||||
|
||||
### Empty list state
|
||||
|
||||
Use `<InnerSideMenuEmptyState />` to show an empty state.
|
||||
|
||||
Use the `actions` prop to add any actions like a `<Button />`.
|
||||
|
||||
```jsx {4-7}
|
||||
<InnerSideBarEmptyPanel
|
||||
title="No functions found"
|
||||
description="Create your first serverless function to get started."
|
||||
actions={
|
||||
<Button type="default" onClick={createAction}>
|
||||
Create Function
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
```
|
||||
|
||||
You can also use the prop `illustration` to pass in a custom illustration, it accets any React Node.
|
||||
|
||||
```jsx {4-7}
|
||||
<InnerSideBarEmptyPanel
|
||||
title="No functions found"
|
||||
description="Create your first serverless function to get started."
|
||||
illustration={
|
||||
<figure>
|
||||
<svg>../</svg>
|
||||
</figure>
|
||||
}
|
||||
/>
|
||||
```
|
||||
|
||||
<ComponentPreview name="inner-side-menu-empty" />
|
||||
@@ -12,31 +12,62 @@ fragment: true
|
||||
```tsx
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
} from 'ui-patterns/multi-select'
|
||||
```
|
||||
|
||||
```tsx
|
||||
<MultiSelector values={value} onValuesChange={setValue}>
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Select items" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="1">Item 1</MultiSelectorItem>
|
||||
<MultiSelectorItem value="2">Item 2</MultiSelectorItem>
|
||||
<MultiSelectorItem value="3">Item 3</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
<MultiSelector
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
selected={selected}
|
||||
onSelectedChange={onSelectedChange}
|
||||
>
|
||||
<MultiSelectorTrigger className="w-72" label="Fruits" />
|
||||
<MultiSelectorContent sameWidthAsTrigger>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Form
|
||||
### Disabled
|
||||
|
||||
<ComponentPreview name="multi-select-form" />
|
||||
<ComponentPreview name="multi-select-disabled" />
|
||||
|
||||
### Badge Combo Box
|
||||
|
||||
Use `MultiSelectorInput`to add a search input.
|
||||
|
||||
<ComponentPreview name="multi-select-combobox" />
|
||||
|
||||
### Badge Limit
|
||||
|
||||
badgeLimit: `number` | `"wrap"`.
|
||||
`badgeLimit` prop on the `MultiSelectorTrigger` component can be used to limit the number of badges displayed.
|
||||
|
||||
<ComponentPreview name="multi-select-badge-limit" />
|
||||
|
||||
### Badge Limit="wrap"
|
||||
|
||||
`badgeLimit` prop can also be "wrap" to wrap the badges to the next line.
|
||||
|
||||
<ComponentPreview name="multi-select-badge-limit-wrap" />
|
||||
|
||||
### Deletable Badge
|
||||
|
||||
Add the `deletableBadge` prop on the `MultiSelectorTrigger` component to enable badge removal directly from the badge.
|
||||
|
||||
<ComponentPreview name="multi-select-deletable-badge" />
|
||||
|
||||
### Inline search input
|
||||
|
||||
Use `mode="inline-combobox"` on the `MultiSelectorTrigger` component to add an inline search input.
|
||||
|
||||
<ComponentPreview name="multi-select-inline-search-input" />
|
||||
@@ -10,6 +10,7 @@
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"content:build": "contentlayer2 build",
|
||||
"clean": "rimraf node_modules .next .turbo",
|
||||
"typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Button, DialogSection, DialogSectionSeparator } from 'ui'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from 'ui'
|
||||
import { Input_Shadcn_ } from 'ui'
|
||||
import { Label_Shadcn_ } from 'ui'
|
||||
|
||||
export default function DialogDemo() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="default">Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]" centered={false}>
|
||||
<DialogHeader padding={'small'}>
|
||||
<DialogTitle>This dialog is not centered.</DialogTitle>
|
||||
<DialogDescription>This dialog is not centered.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogSectionSeparator />
|
||||
<DialogSection className="space-y-4" padding={'small'}>
|
||||
<div>
|
||||
<Label_Shadcn_ htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label_Shadcn_>
|
||||
<Input_Shadcn_ id="name" defaultValue="Pedro Duarte" className="col-span-3" />
|
||||
</div>
|
||||
<div>
|
||||
<Label_Shadcn_ htmlFor="username" className="text-right">
|
||||
Username
|
||||
</Label_Shadcn_>
|
||||
<Input_Shadcn_ id="username" defaultValue="@peduarte" className="col-span-3" />
|
||||
</div>
|
||||
</DialogSection>
|
||||
<DialogFooter padding={'small'}>
|
||||
<Button htmlType="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import { Card } from 'ui'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
InnerSideMenuSeparator,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuDemo() {
|
||||
return (
|
||||
<Card className="min-w-60 bg-dash-sidebar py-4 flex flex-col gap-6 h-full">
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Projects">Hello</InnerSideMenuCollapsibleTrigger>
|
||||
<InnerSideMenuCollapsibleContent className="mt-2">
|
||||
<InnerSideMenuItem href="/">Dashboard</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuSeparator />
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Projects">Hello</InnerSideMenuCollapsibleTrigger>
|
||||
<InnerSideMenuCollapsibleContent className="mt-2">
|
||||
<InnerSideMenuItem href="/" title="Dashboard">
|
||||
Dashboard
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Projects">Hello</InnerSideMenuCollapsibleTrigger>
|
||||
<InnerSideMenuCollapsibleContent className="mt-2">
|
||||
<InnerSideMenuItem href="/" title="Dashboard">
|
||||
Dashboard
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideBarShimmeringLoaders,
|
||||
InnerSideBarEmptyPanel,
|
||||
InnerSideMenuItem,
|
||||
InnerSideMenuSeparator,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
import { Button, Card } from 'ui'
|
||||
import { Heart, Pointer } from 'lucide-react'
|
||||
|
||||
export default function InnerSideMenuEmpty() {
|
||||
const [isLoading, setIsLoading] = React.useState(true)
|
||||
const [hasItems, setHasItems] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
// Simulate loading
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 2000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card className="w-64 py-4 flex flex-col gap-4 bg-dash-sidebar">
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Functions" />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
<InnerSideBarEmptyPanel
|
||||
className="mx-2"
|
||||
title="No functions found"
|
||||
description="Create your first serverless function to get started."
|
||||
illustration={<div className="text-4xl">🚀</div>}
|
||||
actions={
|
||||
<Button type="default" onClick={() => setHasItems(true)}>
|
||||
Create Function
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuSeparator />
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Functions" />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
<InnerSideBarEmptyPanel
|
||||
className="mx-2"
|
||||
title="No functions found"
|
||||
description="Create your first serverless function to get started."
|
||||
illustration={
|
||||
<figure className="relative mb-3">
|
||||
<div className="h-6 w-6 bg-surface-100 border rounded-md flex items-center justify-center">
|
||||
<Heart className="text-light" size={13} />
|
||||
</div>
|
||||
<Pointer
|
||||
className="absolute -right-[6px] -bottom-2 text-lighter"
|
||||
strokeWidth={1.5}
|
||||
size={16}
|
||||
/>
|
||||
</figure>
|
||||
}
|
||||
actions={
|
||||
<Button type="default" onClick={() => setHasItems(true)}>
|
||||
Create Function
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Card } from 'ui'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuItemLoading,
|
||||
InnerSideMenuSeparator,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuLoading() {
|
||||
return (
|
||||
<Card className="w-64 py-4 flex flex-col gap-4 bg-dash-sidebar">
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Functions" />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
<InnerSideMenuItemLoading />
|
||||
<InnerSideMenuItemLoading />
|
||||
<InnerSideMenuItemLoading />
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuSeparator />
|
||||
<InnerSideMenuCollapsible defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title="Functions" />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
<InnerSideMenuItemLoading />
|
||||
<InnerSideMenuItemLoading />
|
||||
<InnerSideMenuItemLoading />
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
InnerSideMenuSeparator,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuMultipleSections() {
|
||||
return (
|
||||
<div className="w-64">
|
||||
<InnerSideMenuCollapsible>
|
||||
<InnerSideMenuCollapsibleTrigger title="Development" />
|
||||
<InnerSideMenuCollapsibleContent>
|
||||
<InnerSideMenuItem href="#" isActive>
|
||||
API
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="#">Database</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
<InnerSideMenuSeparator />
|
||||
<InnerSideMenuCollapsible>
|
||||
<InnerSideMenuCollapsibleTrigger title="Analytics" />
|
||||
<InnerSideMenuCollapsibleContent>
|
||||
<InnerSideMenuItem href="#">Reports</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="#">Usage</InnerSideMenuItem>
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
import { Card } from 'ui'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
InnerSideMenuSeparator,
|
||||
InnerSideBarTitle,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuBasic() {
|
||||
return (
|
||||
<Card className="min-w-60 bg-dash-sidebar py-4 flex flex-col gap-6 h-full">
|
||||
<div className="px-2">
|
||||
<InnerSideBarTitle className="mb-2">Projects</InnerSideBarTitle>
|
||||
<InnerSideMenuItem href="/">Dashboard</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</div>
|
||||
<InnerSideMenuSeparator />
|
||||
<div className="px-2">
|
||||
<InnerSideBarTitle className="mb-2">Projects</InnerSideBarTitle>
|
||||
<InnerSideMenuItem href="/" title="Dashboard">
|
||||
Dashboard
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<InnerSideBarTitle className="mb-2">Projects</InnerSideBarTitle>
|
||||
<InnerSideMenuItem href="/" title="Dashboard">
|
||||
Dashboard
|
||||
</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Team</InnerSideMenuItem>
|
||||
<InnerSideMenuItem href="/">Settings</InnerSideMenuItem>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useState, useMemo } from 'react'
|
||||
import { Card } from 'ui'
|
||||
import {
|
||||
InnerSideMenuCollapsible,
|
||||
InnerSideMenuCollapsibleTrigger,
|
||||
InnerSideMenuCollapsibleContent,
|
||||
InnerSideMenuItem,
|
||||
InnerSideBarFilters,
|
||||
InnerSideBarFilterSearchInput,
|
||||
InnerSideBarFilterSortDropdown,
|
||||
InnerSideBarFilterSortDropdownItem,
|
||||
InnerSideBarEmptyPanel,
|
||||
} from 'ui-patterns/InnerSideMenu'
|
||||
|
||||
export default function InnerSideMenuWithSearch() {
|
||||
const [sort, setSort] = useState('alphabetical')
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
|
||||
const foodCategories = {
|
||||
Fruits: ['Apple', 'Banana', 'Orange', 'Strawberry'],
|
||||
Vegetables: ['Carrot', 'Broccoli', 'Spinach', 'Tomato'],
|
||||
Grains: ['Rice', 'Wheat', 'Oats', 'Quinoa'],
|
||||
Proteins: ['Chicken', 'Beef', 'Fish', 'Tofu'],
|
||||
}
|
||||
|
||||
const filteredAndSortedCategories = useMemo(() => {
|
||||
return Object.entries(foodCategories).reduce(
|
||||
(acc, [category, items]) => {
|
||||
let filteredItems = items.filter((item) =>
|
||||
item.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
|
||||
// Sort the filtered items
|
||||
filteredItems.sort((a, b) => {
|
||||
if (sort === 'alphabetical') {
|
||||
return a.localeCompare(b)
|
||||
} else {
|
||||
return b.localeCompare(a)
|
||||
}
|
||||
})
|
||||
|
||||
acc[category] = filteredItems
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, string[]>
|
||||
)
|
||||
}, [foodCategories, searchTerm, sort])
|
||||
|
||||
return (
|
||||
<Card className="w-64 py-4 flex flex-col gap-4 bg-dash-sidebar">
|
||||
<InnerSideBarFilters>
|
||||
<InnerSideBarFilterSearchInput
|
||||
name="search-input"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
aria-labelledby="Search items"
|
||||
>
|
||||
<InnerSideBarFilterSortDropdown value={sort} onValueChange={(value) => setSort(value)}>
|
||||
<InnerSideBarFilterSortDropdownItem value="alphabetical">
|
||||
Sort Alphabetically
|
||||
</InnerSideBarFilterSortDropdownItem>
|
||||
<InnerSideBarFilterSortDropdownItem value="reverse">
|
||||
Sort Reverse Alphabetically
|
||||
</InnerSideBarFilterSortDropdownItem>
|
||||
</InnerSideBarFilterSortDropdown>
|
||||
</InnerSideBarFilterSearchInput>
|
||||
</InnerSideBarFilters>
|
||||
|
||||
{Object.entries(filteredAndSortedCategories).map(([category, items]) => (
|
||||
<InnerSideMenuCollapsible key={category} defaultOpen>
|
||||
<InnerSideMenuCollapsibleTrigger title={category} />
|
||||
<InnerSideMenuCollapsibleContent className="pt-2">
|
||||
{items.length > 0 ? (
|
||||
items.map((item) => (
|
||||
<InnerSideMenuItem key={item} href="#">
|
||||
{item}
|
||||
</InnerSideMenuItem>
|
||||
))
|
||||
) : (
|
||||
<InnerSideBarEmptyPanel
|
||||
className="mx-2"
|
||||
title={`No ${category.toLowerCase()} found`}
|
||||
description={`Your search did not return any results in ${category}`}
|
||||
/>
|
||||
)}
|
||||
</InnerSideMenuCollapsibleContent>
|
||||
</InnerSideMenuCollapsible>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([
|
||||
'Apple',
|
||||
'Banana',
|
||||
'Cherry',
|
||||
'Date',
|
||||
'Elderberrie',
|
||||
])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger
|
||||
className="w-72"
|
||||
label="Select fruits"
|
||||
persistLabel
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
deletableBadge={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Minus, Plus } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button } from 'ui'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>(['Apple', 'Banana', 'Cherry'])
|
||||
const [limit, setLimit] = useState(2)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="tiny" type="default" onClick={() => setLimit(limit - 1)} disabled={limit < 1}>
|
||||
<Minus size={12} />
|
||||
</Button>
|
||||
<span className="text-sm text-foreground/90 peer-checked:line-through font-semibold hover:cursor-pointer">
|
||||
Limit: {limit}
|
||||
</span>
|
||||
<Button size="tiny" type="default" onClick={() => setLimit(limit + 1)}>
|
||||
<Plus size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger className="w-72" label="Select fruits" badgeLimit={limit} />
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger className="w-72" label="Select fruits" badgeLimit="wrap" />
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorInput placeholder="Search fruits" showResetIcon />
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>(['Apple', 'Banana'])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger
|
||||
className="w-72"
|
||||
label="Select fruits"
|
||||
deletableBadge
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -2,25 +2,42 @@ import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [value, setValue] = useState<string[]>([])
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([])
|
||||
|
||||
const fruits = [
|
||||
{ value: 'Apple', isDisabled: false },
|
||||
{ value: 'Banana', isDisabled: false },
|
||||
{ value: 'Cherry', isDisabled: false },
|
||||
{ value: 'Date', isDisabled: false },
|
||||
{ value: 'Elderberrie', isDisabled: false },
|
||||
{ value: 'Fig', isDisabled: false },
|
||||
{ value: 'Grape', isDisabled: false },
|
||||
{ value: 'Kiwi', isDisabled: true },
|
||||
{ value: 'Mango', isDisabled: false },
|
||||
{ value: 'Strawberry', isDisabled: false },
|
||||
]
|
||||
|
||||
return (
|
||||
<MultiSelector values={value} onValuesChange={setValue} size="small">
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Select items" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger
|
||||
className="w-72"
|
||||
label="Select fruits"
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="1">Item 1</MultiSelectorItem>
|
||||
<MultiSelectorItem value="2">Item 2</MultiSelectorItem>
|
||||
<MultiSelectorItem value="3">Item 3</MultiSelectorItem>
|
||||
{fruits.map(({ value, isDisabled }) => (
|
||||
<MultiSelectorItem key={value} value={value} disabled={isDisabled}>
|
||||
{value}
|
||||
</MultiSelectorItem>
|
||||
))}
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>([])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues} disabled={true}>
|
||||
<MultiSelectorTrigger className="w-72" label="Select fruits" />
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import Image from 'next/image'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import z from 'zod'
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form_Shadcn_,
|
||||
FormDescription_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormItem_Shadcn_,
|
||||
FormLabel_Shadcn_,
|
||||
FormMessage_Shadcn_,
|
||||
} from 'ui'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorInput,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
const form = z.object({
|
||||
value: z.array(z.string()).nonempty('Please select at least one person'),
|
||||
})
|
||||
|
||||
type Form = z.infer<typeof form>
|
||||
|
||||
const users = [
|
||||
{
|
||||
name: 'ThePrimeagen',
|
||||
picture: 'https://pbs.twimg.com/profile_images/1759330620160049152/2i_wkOoK_400x400.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Shadcn',
|
||||
picture: 'https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg',
|
||||
},
|
||||
{
|
||||
name: 'Theo',
|
||||
picture: 'https://pbs.twimg.com/profile_images/1605762947686375425/lsoGWWty_400x400.jpg',
|
||||
},
|
||||
]
|
||||
|
||||
const MultiSelectZod = () => {
|
||||
const multiForm = useForm<Form>({
|
||||
resolver: zodResolver(form),
|
||||
defaultValues: form.parse({ value: [users[0].name] }),
|
||||
})
|
||||
|
||||
const onSubmit = (data: Form) => {
|
||||
toast('Form submitted ', { description: JSON.stringify(data, null, 2) })
|
||||
}
|
||||
|
||||
return (
|
||||
<Form_Shadcn_ {...multiForm}>
|
||||
<form onSubmit={multiForm.handleSubmit(onSubmit)} className="space-y-3 grid gap-3 w-full">
|
||||
<FormField_Shadcn_
|
||||
control={multiForm.control}
|
||||
name="value"
|
||||
render={({ field }) => (
|
||||
<FormItem_Shadcn_ className="w-full">
|
||||
<FormLabel_Shadcn_>Invite people</FormLabel_Shadcn_>
|
||||
<MultiSelector onValuesChange={field.onChange} values={field.value} size={'small'}>
|
||||
<MultiSelectorTrigger>
|
||||
<MultiSelectorInput placeholder="Select people to invite" />
|
||||
</MultiSelectorTrigger>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
{users.map((user) => (
|
||||
<MultiSelectorItem key={user.name} value={user.name}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Image
|
||||
src={user.picture}
|
||||
alt={user.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
<span>{user.name}</span>
|
||||
</div>
|
||||
</MultiSelectorItem>
|
||||
))}
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
<FormDescription_Shadcn_>
|
||||
Select people to invite to this event
|
||||
</FormDescription_Shadcn_>
|
||||
<FormMessage_Shadcn_ />
|
||||
</FormItem_Shadcn_>
|
||||
)}
|
||||
/>
|
||||
<Button type="secondary" size="medium" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
export default MultiSelectZod
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
MultiSelector,
|
||||
MultiSelectorContent,
|
||||
MultiSelectorItem,
|
||||
MultiSelectorList,
|
||||
MultiSelectorTrigger,
|
||||
} from 'ui-patterns/multi-select'
|
||||
|
||||
export default function MultiSelectDemo() {
|
||||
const [selectedValues, setSelectedValues] = useState<string[]>(['Apple'])
|
||||
|
||||
return (
|
||||
<MultiSelector values={selectedValues} onValuesChange={setSelectedValues}>
|
||||
<MultiSelectorTrigger
|
||||
mode="inline-combobox"
|
||||
className="w-72"
|
||||
label="Select fruits"
|
||||
deletableBadge
|
||||
badgeLimit="wrap"
|
||||
showIcon={false}
|
||||
/>
|
||||
<MultiSelectorContent>
|
||||
<MultiSelectorList>
|
||||
<MultiSelectorItem value="Apple">Apple</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Banana">Banana</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Cherry">Cherry</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Date">Date</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Elderberrie">Elderberrie</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Fig">Fig</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Grape">Grape</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Kiwi">Kiwi</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Mango">Mango</MultiSelectorItem>
|
||||
<MultiSelectorItem value="Strawberry">Strawberry</MultiSelectorItem>
|
||||
</MultiSelectorList>
|
||||
</MultiSelectorContent>
|
||||
</MultiSelector>
|
||||
)
|
||||
}
|
||||
@@ -431,6 +431,12 @@ export const examples: Registry = [
|
||||
registryDependencies: ['dialog', 'button'],
|
||||
files: ['example/dialog-close-button.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'dialog-centered-off',
|
||||
type: 'components:example',
|
||||
registryDependencies: ['dialog', 'button'],
|
||||
files: ['example/dialog-centered-off.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'drawer-demo',
|
||||
type: 'components:example',
|
||||
@@ -1030,15 +1036,70 @@ export const examples: Registry = [
|
||||
type: 'components:example',
|
||||
files: ['example/info-tooltip-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-demo',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-static-titles',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-static-titles.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-loading',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-loading.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-multiple-sections',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-multiple-sections.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-empty',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-empty.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'inner-side-menu-with-search',
|
||||
type: 'components:example',
|
||||
files: ['example/inner-side-menu-with-search.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-demo',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-form',
|
||||
name: 'multi-select-disabled',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-form.tsx'],
|
||||
files: ['example/multi-select-disabled.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-badge-limit-wrap',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-badge-limit-wrap.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-badge-limit',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-badge-limit.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-combobox',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-combobox.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-deletable-badge',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-deletable-badge.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'multi-select-inline-search-input',
|
||||
type: 'components:example',
|
||||
files: ['example/multi-select-inline-search-input.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'tree-view-demo',
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# IMPORTANT: This file is checked into version control.
|
||||
# Only publicly available environment variables should be listed here.
|
||||
# Do not add secrets here. Use .env.local instead.
|
||||
|
||||
NEXT_PUBLIC_MISC_URL="https://obuldanrptloktxcffvn.supabase.co"
|
||||
NEXT_PUBLIC_MISC_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9idWxkYW5ycHRsb2t0eGNmZnZuIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTg2MTQ2ODUsImV4cCI6MjAzNDE5MDY4NX0.NFt49g6DFkc1X5khCzN5p01iAVo2TMxlx88cY1V0E2M"
|
||||
@@ -14,7 +14,7 @@ For a complete run-down on how all of our tools work together, see the main DEVE
|
||||
|
||||
## Local setup
|
||||
|
||||
[supabase.com/docs](https://supabase.com/docs) is a Next.JS site. You can get setup by following the same steps for all of our other Next.JS projects:
|
||||
[supabase.com/docs](https://supabase.com/docs) is a Next.js site. You can get setup by following the same steps for all of our other Next.js projects:
|
||||
|
||||
1. Follow the steps outlined in the Local Development section of the main [DEVELOPERS.md](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
|
||||
2. If you work at Supabase, run `dev:secrets:pull` to pull down the internal environment variables. If you're a community member, create a `.env` file and add this line to it: `NEXT_PUBLIC_IS_PLATFORM=false`
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ We use documentation specifications which can be used to generate human-readable
|
||||
- CLISpec (custom to Supabase): for CLI commands and usage.
|
||||
|
||||
The benefit of using custom specifications is that we can generate many other types from a strict schema (eg, HTML and manpages).
|
||||
It also means that we can switch to any documentation system we want. On this site we use Next.JS, but on Supabase's official website, we use a custom React site and expose only a subset of the available API for each tool.
|
||||
It also means that we can switch to any documentation system we want. On this site we use Next.js, but on Supabase's official website, we use a custom React site and expose only a subset of the available API for each tool.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { type SerializeOptions } from 'next-mdx-remote/dist/types'
|
||||
import type { SerializeOptions } from 'next-mdx-remote/dist/types'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { relative } from 'path'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
@@ -84,7 +84,8 @@ const getContent = async ({ slug }: Params) => {
|
||||
`https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}`
|
||||
)
|
||||
|
||||
const content = await response.text()
|
||||
let content = await response.text()
|
||||
content = removeRedundantH1(content)
|
||||
|
||||
return {
|
||||
pathname: `/guides/ai/python/${slug}` satisfies `/${string}`,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { type PropsWithChildren } from 'react'
|
||||
|
||||
import { IS_PLATFORM } from 'common'
|
||||
|
||||
import { supabaseMisc } from '~/lib/supabaseMisc'
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
const GuidesLayout = async ({ children }: PropsWithChildren) => {
|
||||
const partners = await getPartners()
|
||||
const partners = IS_PLATFORM ? await getPartners() : []
|
||||
const partnerNavItems = partners.map((partner) => ({
|
||||
name: partner.title,
|
||||
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
|
||||
|
||||
@@ -21,7 +21,7 @@ const AnalyticsConfigPage = async () => {
|
||||
<GuideTemplate
|
||||
meta={meta}
|
||||
editLink={newEditLink(
|
||||
'supabase/supabase/blob/master/apps/docs/pages/guides/self-hosting/analytics/config.tsx'
|
||||
'supabase/supabase/blob/master/apps/docs/app/guides/(with-sidebar)/self-hosting/analytics/config/page.tsx'
|
||||
)}
|
||||
>
|
||||
<MDXRemoteBase source={descriptionMdx} />
|
||||
|
||||
@@ -21,7 +21,7 @@ const AuthConfigPage = async () => {
|
||||
<GuideTemplate
|
||||
meta={meta}
|
||||
editLink={newEditLink(
|
||||
'supabase/supabase/blob/master/apps/docs/pages/guides/self-hosting/auth/config.tsx'
|
||||
'supabase/supabase/blob/master/apps/docs/app/guides/(with-sidebar)/self-hosting/auth/config/page.tsx'
|
||||
)}
|
||||
>
|
||||
<MDXRemoteBase source={descriptionMdx} />
|
||||
|
||||
@@ -21,7 +21,7 @@ const RealtimeConfigPage = async () => {
|
||||
<GuideTemplate
|
||||
meta={meta}
|
||||
editLink={newEditLink(
|
||||
'supabase/supabase/blob/master/apps/docs/pages/guides/self-hosting/realtime/config.tsx'
|
||||
'supabase/supabase/blob/master/apps/docs/app/guides/(with-sidebar)/self-hosting/realtime/config/page.tsx'
|
||||
)}
|
||||
>
|
||||
<MDXRemoteBase source={descriptionMdx} />
|
||||
|
||||
@@ -21,7 +21,7 @@ const StorageConfigPage = async () => {
|
||||
<GuideTemplate
|
||||
meta={meta}
|
||||
editLink={newEditLink(
|
||||
'supabase/supabase/blob/master/apps/docs/pages/guides/self-hosting/storage/config.tsx'
|
||||
'supabase/supabase/blob/master/apps/docs/app/guides/(with-sidebar)/self-hosting/storage/config/page.tsx'
|
||||
)}
|
||||
>
|
||||
<MDXRemoteBase source={descriptionMdx} />
|
||||
|
||||
@@ -4,6 +4,10 @@ import TroubleshootingPage from '~/features/docs/Troubleshooting.page'
|
||||
import { getAllTroubleshootingEntries, getArticleSlug } from '~/features/docs/Troubleshooting.utils'
|
||||
import { PROD_URL } from '~/lib/constants'
|
||||
|
||||
// 60 seconds/minute * 60 minutes/hour * 24 hours/day
|
||||
export const revalidate = 86_400
|
||||
export const dynamicParams = false
|
||||
|
||||
export default async function TroubleshootingEntryPage({
|
||||
params: { slug },
|
||||
}: {
|
||||
|
||||
@@ -16,6 +16,9 @@ import { TROUBLESHOOTING_CONTAINER_ID } from '~/features/docs/Troubleshooting.ut
|
||||
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
|
||||
import { PROD_URL } from '~/lib/constants'
|
||||
|
||||
// 60 seconds/minute * 60 minutes/hour * 24 hours/day
|
||||
export const revalidate = 86_400
|
||||
|
||||
export default async function GlobalTroubleshootingPage() {
|
||||
const troubleshootingEntries = await getAllTroubleshootingEntries()
|
||||
const keywords = await getAllTroubleshootingKeywords()
|
||||
@@ -23,7 +26,7 @@ export default async function GlobalTroubleshootingPage() {
|
||||
const errors = await getAllTroubleshootingErrors()
|
||||
|
||||
return (
|
||||
<SidebarSkeleton className="w-full max-w-screen-lg mx-auto">
|
||||
<SidebarSkeleton hideSideNav className="w-full max-w-screen-lg mx-auto">
|
||||
<div className="py-8 px-5">
|
||||
<h1 className="text-4xl tracking-tight mb-7">Troubleshooting</h1>
|
||||
<p className="text-lg text-foreground-light">
|
||||
|
||||
+5
-58
@@ -4,6 +4,7 @@ import { IconBackground, TextLink } from 'ui'
|
||||
import { IconPanel } from 'ui-patterns/IconPanel'
|
||||
|
||||
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
|
||||
import { MIGRATION_PAGES } from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
|
||||
import { GlassPanelWithIconPicker } from '~/features/ui/GlassPanelWithIconPicker'
|
||||
import { IconPanelWithIconPicker } from '~/features/ui/IconPanelWithIconPicker'
|
||||
import HomeLayout from '~/layouts/HomeLayout'
|
||||
@@ -70,60 +71,6 @@ const products = [
|
||||
},
|
||||
]
|
||||
|
||||
const migrationGuides = [
|
||||
{
|
||||
title: 'Auth0',
|
||||
icon: '/docs/img/icons/auth0-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/auth0',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
title: 'Firebase Auth',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/firebase-auth',
|
||||
},
|
||||
{
|
||||
title: 'Firestore Data',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/firestore-data',
|
||||
},
|
||||
{
|
||||
title: 'Firebase Storage',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/firebase-storage',
|
||||
},
|
||||
{
|
||||
title: 'Heroku',
|
||||
icon: '/docs/img/icons/heroku-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/heroku',
|
||||
},
|
||||
{
|
||||
title: 'Render',
|
||||
icon: '/docs/img/icons/render-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/render',
|
||||
},
|
||||
{
|
||||
title: 'Amazon RDS',
|
||||
icon: '/docs/img/icons/aws-rds-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/amazon-rds',
|
||||
},
|
||||
{
|
||||
title: 'Postgres',
|
||||
icon: '/docs/img/icons/postgres-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/postgres',
|
||||
},
|
||||
{
|
||||
title: 'MySQL',
|
||||
icon: '/docs/img/icons/mysql-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/mysql',
|
||||
},
|
||||
{
|
||||
title: 'MSSQL',
|
||||
icon: '/docs/img/icons/mssql-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/mssql',
|
||||
},
|
||||
]
|
||||
|
||||
const selfHostingOptions = [
|
||||
{
|
||||
title: 'Auth',
|
||||
@@ -268,11 +215,11 @@ const HomePage = () => (
|
||||
</div>
|
||||
|
||||
<ul className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
{migrationGuides.map((guide) => {
|
||||
{MIGRATION_PAGES.map((guide) => {
|
||||
return (
|
||||
<li key={guide.title} className="col-span-6 md:col-span-4">
|
||||
<Link href={guide.href} passHref>
|
||||
<IconPanel {...guide} background={true} showLink={false} />
|
||||
<li key={guide.name} className="col-span-6 md:col-span-4">
|
||||
<Link href={guide.url} passHref>
|
||||
<IconPanel {...guide} title={guide.name} background={true} showLink={false} />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ export default async function ReferencePage({
|
||||
}: {
|
||||
params: { slug: Array<string> }
|
||||
}) {
|
||||
console.log("Generating reference page for '%o'", slug)
|
||||
if (!Object.keys(REFERENCES).includes(slug[0].replaceAll('-', '_'))) {
|
||||
notFound()
|
||||
}
|
||||
@@ -38,6 +39,7 @@ export default async function ReferencePage({
|
||||
|
||||
return <ClientSdkReferencePage sdkId={sdkId} libVersion={version} />
|
||||
} else if (isCliReference) {
|
||||
console.log('Returning CLI reference page: %o', parsedPath)
|
||||
return <CliReferencePage />
|
||||
} else if (isApiReference) {
|
||||
return <ApiReferencePage />
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function JwtGenerator({}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="border rounded-lg p-4">
|
||||
<div className="grid mb-8">
|
||||
<label htmlFor="secret">JWT Secret:</label>
|
||||
<Input
|
||||
@@ -88,7 +88,7 @@ export default function JwtGenerator({}) {
|
||||
/>
|
||||
</div>
|
||||
<div className="grid mb-8">
|
||||
<label htmlFor="service">Preconfigured Payload:</label>
|
||||
<label htmlFor="service">Key:</label>
|
||||
<Select id="service" style={{ fontFamily: 'monospace' }} onChange={handleKeySelection}>
|
||||
<Select.Option value="anon">ANON_KEY</Select.Option>
|
||||
<Select.Option value="service">SERVICE_KEY</Select.Option>
|
||||
@@ -96,7 +96,7 @@ export default function JwtGenerator({}) {
|
||||
</div>
|
||||
|
||||
<div className="grid mb-8">
|
||||
<label htmlFor="token">Payload:</label>
|
||||
<label htmlFor="token">The JWT will be generated from this info:</label>
|
||||
<Input.TextArea
|
||||
id="token"
|
||||
type="text"
|
||||
|
||||
@@ -68,7 +68,7 @@ export async function GET(request: Request) {
|
||||
const next = searchParams.get('next') ?? '/'
|
||||
|
||||
if (code) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
||||
if (!error) {
|
||||
const forwardedHost = request.headers.get('x-forwarded-host') // original origin before load balancer
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import type { ComponentProps } from 'react'
|
||||
|
||||
import type { IconPanel } from 'ui-patterns/IconPanel'
|
||||
|
||||
import type { GlobalMenuItems, NavMenuConstant, NavMenuSection } from '../Navigation.types'
|
||||
|
||||
export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
|
||||
@@ -959,10 +963,6 @@ export const database: NavMenuConstant = {
|
||||
name: 'pg_net: Async Networking',
|
||||
url: '/guides/database/extensions/pg_net',
|
||||
},
|
||||
{
|
||||
name: 'pg_partman: Partition Maintenance',
|
||||
url: '/guides/database/extensions/pg_partman',
|
||||
},
|
||||
{
|
||||
name: 'pg_plan_filter: Restrict Total Cost',
|
||||
url: '/guides/database/extensions/pg_plan_filter',
|
||||
@@ -983,10 +983,6 @@ export const database: NavMenuConstant = {
|
||||
name: 'PostGIS: Geo queries',
|
||||
url: '/guides/database/extensions/postgis',
|
||||
},
|
||||
{
|
||||
name: 'pgmq: Queues',
|
||||
url: '/guides/database/extensions/pgmq',
|
||||
},
|
||||
{
|
||||
name: 'pgsodium (pending deprecation): Encryption Features',
|
||||
url: '/guides/database/extensions/pgsodium',
|
||||
@@ -1254,6 +1250,10 @@ export const functions: NavMenuConstant = {
|
||||
name: 'Handling Routing in Functions',
|
||||
url: '/guides/functions/routing',
|
||||
},
|
||||
{
|
||||
name: 'Background Tasks',
|
||||
url: '/guides/functions/background-tasks',
|
||||
},
|
||||
{
|
||||
name: 'Running AI Models',
|
||||
url: '/guides/functions/ai-models',
|
||||
@@ -1809,23 +1809,70 @@ export const local_development: NavMenuConstant = {
|
||||
],
|
||||
}
|
||||
|
||||
export const MIGRATION_PAGES: Partial<NavMenuSection>[] = [
|
||||
{ name: 'Amazon RDS', url: '/guides/platform/migrating-to-supabase/amazon-rds' },
|
||||
{ name: 'Auth0', url: '/guides/platform/migrating-to-supabase/auth0' },
|
||||
{ name: 'Firebase Auth', url: '/guides/platform/migrating-to-supabase/firebase-auth' },
|
||||
export const MIGRATION_PAGES: Partial<NavMenuSection & ComponentProps<typeof IconPanel>>[] = [
|
||||
{
|
||||
name: 'Firebase Firestore',
|
||||
name: 'Auth0',
|
||||
icon: '/docs/img/icons/auth0-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/auth0',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
name: 'Firebase Auth',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/firebase-auth',
|
||||
},
|
||||
{
|
||||
name: 'Firestore Data',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/firestore-data',
|
||||
},
|
||||
{
|
||||
name: 'Firebase Storage',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/firebase-storage',
|
||||
},
|
||||
{ name: 'Heroku Postgres', url: '/guides/platform/migrating-to-supabase/heroku' },
|
||||
{ name: 'MySQL', url: '/guides/platform/migrating-to-supabase/mysql' },
|
||||
{ name: 'MSSQL', url: '/guides/platform/migrating-to-supabase/mssql' },
|
||||
{ name: 'Postgres', url: '/guides/platform/migrating-to-supabase/postgres' },
|
||||
{ name: 'Render', url: '/guides/platform/migrating-to-supabase/render' },
|
||||
{
|
||||
name: 'Heroku',
|
||||
icon: '/docs/img/icons/heroku-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/heroku',
|
||||
},
|
||||
{
|
||||
name: 'Render',
|
||||
icon: '/docs/img/icons/render-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/render',
|
||||
},
|
||||
{
|
||||
name: 'Amazon RDS',
|
||||
icon: '/docs/img/icons/aws-rds-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/amazon-rds',
|
||||
},
|
||||
{
|
||||
name: 'Postgres',
|
||||
icon: '/docs/img/icons/postgres-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/postgres',
|
||||
},
|
||||
{
|
||||
name: 'Vercel Postgres',
|
||||
icon: '/docs/img/icons/vercel-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/vercel-postgres',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
name: 'Neon',
|
||||
icon: '/docs/img/icons/neon-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/neon',
|
||||
hasLightIcon: true,
|
||||
},
|
||||
{
|
||||
name: 'MySQL',
|
||||
icon: '/docs/img/icons/mysql-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/mysql',
|
||||
},
|
||||
{
|
||||
name: 'MSSQL',
|
||||
icon: '/docs/img/icons/mssql-icon',
|
||||
url: '/guides/platform/migrating-to-supabase/mssql',
|
||||
},
|
||||
]
|
||||
|
||||
export const platform: NavMenuConstant = {
|
||||
@@ -1837,7 +1884,6 @@ export const platform: NavMenuConstant = {
|
||||
name: 'Add-ons',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons' },
|
||||
{ name: 'Custom Domains', url: '/guides/platform/custom-domains' },
|
||||
{ name: 'Database Backups', url: '/guides/platform/backups' },
|
||||
{ name: 'IPv4 Address', url: '/guides/platform/ipv4-address' },
|
||||
@@ -1895,6 +1941,7 @@ export const platform: NavMenuConstant = {
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Regions', url: '/guides/platform/regions' },
|
||||
{ name: 'Compute and Disk', url: '/guides/platform/compute-and-disk' },
|
||||
{ name: 'Database Size', url: '/guides/platform/database-size' },
|
||||
{ name: 'Fly Postgres', url: '/guides/platform/fly-postgres' },
|
||||
{
|
||||
|
||||
@@ -59,6 +59,12 @@ Client libraries like python's [vecs](https://github.com/supabase/vecs) use this
|
||||
#!/usr/bin/env python3
|
||||
import vecs
|
||||
|
||||
# In practice, do not hard-code your password. Use environment variables.
|
||||
DB_CONNECTION = "postgresql://<user>:<password>@<host>:<port>/<db_name>"
|
||||
|
||||
# create vector store client
|
||||
vx = vecs.create_client(DB_CONNECTION)
|
||||
|
||||
docs = vx.get_or_create_collection(name="docs", dimension=1536)
|
||||
|
||||
docs.upsert(vectors=[
|
||||
|
||||
@@ -292,8 +292,9 @@ create function public.check_request()
|
||||
declare
|
||||
req_app_api_key text := current_setting('request.headers', true)::json->>'x-app-api-key';
|
||||
is_app_api_key_registered boolean;
|
||||
jwt_role text := current_setting('request.jwt.claims', true)::json->>'role';
|
||||
begin
|
||||
if current_role <> 'anon' then
|
||||
if jwt_role <> 'anon' then
|
||||
-- not `anon` role, allow the request to pass
|
||||
return;
|
||||
end if;
|
||||
@@ -302,7 +303,7 @@ begin
|
||||
true into is_app_api_key_registered
|
||||
from private.anon_api_keys
|
||||
where
|
||||
id = req_app_api_key
|
||||
id = req_app_api_key::uuid
|
||||
limit 1;
|
||||
|
||||
if is_app_api_key_registered is true then
|
||||
|
||||
@@ -264,6 +264,53 @@ Depending on your application requirements, data conflicts can arise when an ano
|
||||
2. Overwrite the items in the cart with those from the anonymous user
|
||||
3. Merge the items in the cart together
|
||||
|
||||
### Linking an anonymous user to an existing account
|
||||
|
||||
In some cases, you may need to link an anonymous user to an existing account rather than creating a new permanent account. This process requires manual handling of potential conflicts. Here's a general approach:
|
||||
|
||||
```javascript
|
||||
// 1. Sign in anonymously (assuming the user is already signed in anonymously)
|
||||
const { data: anonData, error: anonError } = await supabase.auth.getSession()
|
||||
|
||||
// 2. Attempt to update the user with the existing email
|
||||
const { data: updateData, error: updateError } = await supabase.auth.updateUser({
|
||||
email: 'existing_user@example.com',
|
||||
})
|
||||
|
||||
// 3. Handle the error (since the email belongs to an existing user)
|
||||
if (updateError) {
|
||||
console.log('This email belongs to an existing user. Please sign in to that account.')
|
||||
|
||||
// 4. Sign in to the existing account
|
||||
const {
|
||||
data: { user: existingUser },
|
||||
error: signInError,
|
||||
} = await supabase.auth.signInWithPassword({
|
||||
email: 'existing_user@example.com',
|
||||
password: 'user_password',
|
||||
})
|
||||
|
||||
if (existingUser) {
|
||||
// 5. Reassign entities tied to the anonymous user
|
||||
// This step will vary based on your specific use case and data model
|
||||
const { data: reassignData, error: reassignError } = await supabase
|
||||
.from('your_table')
|
||||
.update({ user_id: existingUser.id })
|
||||
.eq('user_id', anonData.session.user.id)
|
||||
|
||||
// 6. Implement your chosen conflict resolution strategy
|
||||
// This could involve merging data, overwriting, or other custom logic
|
||||
await resolveDataConflicts(anonData.session.user.id, existingUser.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to resolve data conflicts (implement based on your strategy)
|
||||
async function resolveDataConflicts(anonymousUserId, existingUserId) {
|
||||
// Implement your conflict resolution logic here
|
||||
// This could involve ignoring the anonymous user's metadata, overwriting the existing user's metadata, or merging the data of both the anonymous and existing user.
|
||||
}
|
||||
```
|
||||
|
||||
## Abuse prevention and rate limits
|
||||
|
||||
Since anonymous users are stored in your database, bad actors can abuse the endpoint to increase your database size drastically. It is strongly recommended to [enable invisible Captcha or Cloudflare Turnstile](/docs/guides/auth/auth-captcha) to prevent abuse for anonymous sign-ins. An IP-based rate limit is enforced at 30 requests per hour which can be modified in your [dashboard](/dashboard/project/_/auth/rate-limits). You can refer to the full list of rate limits [here](/docs/guides/platform/going-into-prod#rate-limiting-resource-allocation--abuse-prevention).
|
||||
|
||||
@@ -27,7 +27,7 @@ The templating system provides the following variables for use:
|
||||
|
||||
## Editing email templates
|
||||
|
||||
On hosted Supabase projects, edit your email templates on the [Email Templates](/dashboard/project/_/auth/templates) page. On self-hosted projects or in local development, edit your [configuration files](/docs/guides/cli/customizing-email-templates).
|
||||
On hosted Supabase projects, edit your email templates on the [Email Templates](/dashboard/project/_/auth/templates) page. On self-hosted projects or in local development, edit your [configuration files](/docs/guides/local-development/customizing-email-templates).
|
||||
|
||||
## Mobile Deep Linking
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ sidebar_label: 'Overview'
|
||||
---
|
||||
|
||||
<Admonition type="caution">
|
||||
The Auth helpers package is in maintenance mode and we won’t be actively improving it. We strongly recommend using the new `@supabase/ssr` package instead of `auth-helpers`. `@supabase/ssr` takes the core concepts of the Auth Helpers package and makes them available to any server framework. Check out the [migration doc](/docs/guides/auth/server-side/migrating-to-ssr-from-auth-helpers) to learn more.
|
||||
The Auth helpers package is deprecated. Use the new `@supabase/ssr` package for Server Side Authentication. `@supabase/ssr` takes the core concepts of the Auth Helpers package and makes them available to any server framework. Check out the [migration doc](/docs/guides/auth/server-side/migrating-to-ssr-from-auth-helpers) to learn more.
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ sitemapPriority: 0.5
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
The `auth-helpers` package has been replaced with the `@supabase/ssr` package. We recommend setting up Auth for your Next.js app with `@supabase/ssr` instead. See the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/nextjs) to learn how.
|
||||
The `auth-helpers` are now deprecated. Use `@supabase/ssr` to set up Auth for your Next.js app. See the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/nextjs) to learn how.
|
||||
|
||||
</Admonition>
|
||||
|
||||
@@ -20,7 +20,7 @@ The `auth-helpers` package has been replaced with the `@supabase/ssr` package. W
|
||||
size="medium"
|
||||
className="text-foreground-light border-b mt-8 pb-2"
|
||||
>
|
||||
|
||||
|
||||
<AccordionItem
|
||||
header="See legacy docs"
|
||||
id="legacy-docs"
|
||||
@@ -218,7 +218,7 @@ export async function GET(request: NextRequest) {
|
||||
const code = requestUrl.searchParams.get('code')
|
||||
|
||||
if (code) {
|
||||
const cookieStore = cookies()
|
||||
const cookieStore = await cookies()
|
||||
const supabase = createRouteHandlerClient<Database>({ cookies: () => cookieStore })
|
||||
await supabase.auth.exchangeCodeForSession(code)
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ secrets = "env(SEND_SMS_HOOK_SECRET)"
|
||||
Fill in the Hook Secret in `supabase/functions/.env`
|
||||
|
||||
```bash
|
||||
SEND_SMS_HOOK_SECRETS='v1,whsec_<base64_standard_secret>'
|
||||
SEND_SMS_HOOK_SECRET='v1,whsec_<base64_standard_secret>'
|
||||
```
|
||||
|
||||
Start the function locally:
|
||||
|
||||
@@ -6,6 +6,17 @@ subtitle: 'Use a custom email provider to send authentication messages'
|
||||
|
||||
The Send Email Hook runs before an email is sent and allows for flexibility around email sending. You can use this hook to configure a back-up email provider or add internationalization to your emails.
|
||||
|
||||
## Email Sending Behavior
|
||||
|
||||
Email sending depends on two settings: Email Provider and Auth Hook status.
|
||||
|
||||
| Email Provider | Auth Hook | Result |
|
||||
| -------------- | --------- | -------------------------------------------------------------------- |
|
||||
| Enabled | Enabled | Auth Hook handles email sending (SMTP not used) |
|
||||
| Enabled | Disabled | SMTP handles email sending (custom if configured, default otherwise) |
|
||||
| Disabled | Enabled | Email Signups Disabled |
|
||||
| Disabled | Disabled | Email Signups Disabled |
|
||||
|
||||
**Inputs**
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -351,7 +362,7 @@ create table job_queue (
|
||||
Create the hook
|
||||
|
||||
```sql
|
||||
create or replace function send_email(event jsonb) returns void as $$
|
||||
create or replace function send_email(event jsonb) returns jsonb as $$
|
||||
declare
|
||||
job_data jsonb;
|
||||
scheduled_time timestamp;
|
||||
@@ -371,8 +382,10 @@ begin
|
||||
-- Assign priority dynamically (example logic: higher priority for earlier scheduled time)
|
||||
priority := extract('epoch' from (scheduled_time - now()))::int;
|
||||
|
||||
insert into job_queue (job_data, priority, scheduled_at, max_retries)
|
||||
insert into public.job_queue (job_data, priority, scheduled_at, max_retries)
|
||||
values (job_data, priority, scheduled_time, 2);
|
||||
|
||||
return '{}'::jsonb;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
@@ -628,6 +641,11 @@ const templates = {
|
||||
},
|
||||
}
|
||||
|
||||
function generateConfirmationURL(email_data) {
|
||||
// TODO: replace the ref with your project ref
|
||||
return `https://<ref>.supabase.co/auth/v1/verify?token=${email_data.token}&type=${email_data.email_action_type}&redirect_to=${email_data.redirect_to}`
|
||||
}
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const payload = await req.text()
|
||||
const serverToken = Deno.env.get('POSTMARK_SERVER_TOKEN')
|
||||
@@ -640,8 +658,9 @@ Deno.serve(async (req) => {
|
||||
const subject = subjects[language][email_data.email_action_type] || 'Notification'
|
||||
|
||||
let template = templates[language][email_data.email_action_type]
|
||||
const confirmation_url = generateConfirmationURL(email_data)
|
||||
let htmlBody = template
|
||||
.replace('{{confirmation_url}}', email_data.confirmation_url)
|
||||
.replace('{{confirmation_url}}', confirmation_url)
|
||||
.replace('{{token}}', email_data.token || '')
|
||||
.replace('{{new_token}}', email_data.new_token || '')
|
||||
.replace('{{site_url}}', email_data.site_url || '')
|
||||
@@ -734,10 +753,16 @@ const templates = {
|
||||
reauthentication: `<h2>Confirm reauthentication</h2><p>Enter the code: {{token}}</p>`
|
||||
};
|
||||
|
||||
function generateConfirmationURL(email_data) {
|
||||
// TODO: replace the ref with your project ref
|
||||
return `https://<ref>.supabase.co/auth/v1/verify?token=${email_data.token}&type=${email_data.email_action_type}&redirect_to=${email_data.redirect_to}`
|
||||
}
|
||||
|
||||
async function sendEmailWithPostmark(user: any, email_data: any, serverToken: string): Promise<Response> {
|
||||
const subject = subjects[email_data.email_action_type] || 'Notification';
|
||||
const confirmation_url = generateConfirmationURL(email_data)
|
||||
let template = templates[email_data.email_action_type];
|
||||
let htmlBody = template.replace('{{confirmation_url}}', email_data.confirmation_url)
|
||||
let htmlBody = template.replace('{{confirmation_url}}', confirmation_url)
|
||||
.replace('{{token}}', email_data.token || '')
|
||||
.replace('{{new_token}}', email_data.new_token || '')
|
||||
.replace('{{site_url}}', email_data.site_url || '')
|
||||
@@ -765,7 +790,8 @@ async function sendEmailWithPostmark(user: any, email_data: any, serverToken: st
|
||||
async function sendEmailWithSendGrid(user: any, email_data: any, apiKey: string): Promise<Response> {
|
||||
const subject = subjects[email_data.email_action_type] || 'Notification';
|
||||
let template = templates[email_data.email_action_type];
|
||||
let htmlBody = template.replace('{{confirmation_url}}', email_data.confirmation_url)
|
||||
cont confirmation_url = generateConfirmationURL(email_data)
|
||||
let htmlBody = template.replace('{{confirmation_url}}', confirmation_url)
|
||||
.replace('{{token}}', email_data.token || '')
|
||||
.replace('{{new_token}}', email_data.new_token || '')
|
||||
.replace('{{site_url}}', email_data.site_url || '')
|
||||
|
||||
@@ -184,7 +184,7 @@ export async function GET(request: NextRequest) {
|
||||
redirectTo.pathname = next
|
||||
|
||||
if (token_hash && type) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
type,
|
||||
@@ -648,7 +648,7 @@ export async function GET(request: NextRequest) {
|
||||
redirectTo.pathname = next
|
||||
|
||||
if (token_hash && type) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
type,
|
||||
@@ -844,7 +844,7 @@ app.get("/auth/confirm", async function (req, res) {
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
async function signUpNewUser() {
|
||||
async function resetPassword() {
|
||||
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -72,9 +72,7 @@ hideToc: true
|
||||
|
||||
Start the development server, go to http://localhost:3000 in a browser, and you should see the contents of `app/page.tsx`.
|
||||
|
||||
To Sign Up a new user, navigate to http://localhost:3000/login, and click `Sign Up Now`.
|
||||
|
||||
Check out the [Supabase Auth docs](https://supabase.com/docs/guides/auth#authentication) for more authentication methods.
|
||||
To sign up a new user, navigate to http://localhost:3000/login, and click `Sign Up Now`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
@@ -88,3 +86,8 @@ hideToc: true
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Setting up Server-Side Auth for Next.js](https://supabase.com/docs/guides/auth/server-side/nextjs) for a Next.js deep dive
|
||||
- [Supabase Auth docs](https://supabase.com/docs/guides/auth#authentication) for more Supabase authentication methods
|
||||
@@ -171,8 +171,8 @@ export function createClient() {
|
||||
import { createServerClient } from '@supabase/ssr'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export function createClient() {
|
||||
const cookieStore = cookies()
|
||||
export async function createClient() {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
return createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
@@ -520,6 +520,36 @@ export async function GET(context: APIContext) {
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="astro-middleware" label="Middleware">
|
||||
|
||||
```ts middleware.ts
|
||||
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
|
||||
import { defineMiddleware } from 'astro:middleware'
|
||||
|
||||
export const onRequest = defineMiddleware(async (context, next) => {
|
||||
const supabase = createServerClient(
|
||||
import.meta.env.PUBLIC_SUPABASE_URL,
|
||||
import.meta.env.PUBLIC_SUPABASE_ANON_KEY,
|
||||
{
|
||||
cookies: {
|
||||
getAll() {
|
||||
return parseCookieHeader(context.request.headers.get('Cookie') ?? '')
|
||||
},
|
||||
setAll(cookiesToSet) {
|
||||
cookiesToSet.forEach(({ name, value, options }) =>
|
||||
context.cookies.set(name, value, options)
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return next()
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -125,8 +125,8 @@ export function createClient() {
|
||||
import { createServerClient, type CookieOptions } from '@supabase/ssr'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export function createClient() {
|
||||
const cookieStore = cookies()
|
||||
export async function createClient() {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
return createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
@@ -337,7 +337,7 @@ import { redirect } from 'next/navigation'
|
||||
import { createClient } from '@/utils/supabase/server'
|
||||
|
||||
export async function login(formData: FormData) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
// type-casting here for convenience
|
||||
// in practice, you should validate your inputs
|
||||
@@ -357,7 +357,7 @@ export async function login(formData: FormData) {
|
||||
}
|
||||
|
||||
export async function signup(formData: FormData) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
// type-casting here for convenience
|
||||
// in practice, you should validate your inputs
|
||||
@@ -429,7 +429,7 @@ export async function GET(request: NextRequest) {
|
||||
const next = searchParams.get('next') ?? '/'
|
||||
|
||||
if (token_hash && type) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
type,
|
||||
@@ -482,7 +482,7 @@ import { redirect } from 'next/navigation'
|
||||
import { createClient } from '@/utils/supabase/server'
|
||||
|
||||
export default async function PrivatePage() {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
const { data, error } = await supabase.auth.getUser()
|
||||
if (error || !data?.user) {
|
||||
|
||||
@@ -77,6 +77,20 @@ Future<void> signInWithGithub() async {
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="swift" label="Swift">
|
||||
|
||||
When your user signs in, call [signInWithWithOAuth](/docs/reference/swift/auth-signinwithoauth) with `.github` as the `Provider`:
|
||||
|
||||
```swift
|
||||
func signInWithGithub() async throws {
|
||||
try await supabase.auth.signInWithOAuth(
|
||||
provider: .github,
|
||||
redirectTo: URL(string: "my-custom-scheme://my-app-host")
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="kotlin" label="Kotlin">
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ Most web apps and websites can utilize Google's [personalized sign-in buttons](h
|
||||
1. Use the [HTML Code Generator](https://developers.google.com/identity/gsi/web/tools/configurator) to customize the look, feel, features and behavior of the Sign in with Google button.
|
||||
1. Pick the _Swap to JavaScript callback_ option, and input the name of your callback function. This function will receive a [`CredentialResponse`](https://developers.google.com/identity/gsi/web/reference/js-reference#CredentialResponse) when sign in completes.
|
||||
|
||||
To make your app compatible with Chome's third-party-cookie phase-out, make sure to set `data-use_fedcm_for_prompt` to `true`.
|
||||
To make your app compatible with Chrome's third-party-cookie phase-out, make sure to set `data-use_fedcm_for_prompt` to `true`.
|
||||
|
||||
Your final HTML code might look something like this:
|
||||
|
||||
@@ -341,9 +341,9 @@ Most web apps and websites can utilize Google's [personalized sign-in buttons](h
|
||||
// Use 'nonce' when invoking the supabase.auth.signInWithIdToken() method
|
||||
```
|
||||
|
||||
### One-tap with NextJS
|
||||
### One-tap with Next.js
|
||||
|
||||
If you're integrating Google One-Tap with your NextJS application, you can refer to the example below to get started:
|
||||
If you're integrating Google One-Tap with your Next.js application, you can refer to the example below to get started:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
@@ -128,7 +128,7 @@ If you'd like us to add a new quickstart guide, share your [feedback](https://su
|
||||
|
||||
- [Prisma](/docs/guides/database/prisma)
|
||||
- [Drizzle](/docs/guides/database/drizzle)
|
||||
- [Postgres.js](/docs/guides/database/drizzle)
|
||||
- [Postgres.js](/docs/guides/database/postgres-js)
|
||||
|
||||
### External tools
|
||||
|
||||
|
||||
@@ -85,6 +85,12 @@ CLI changes permanently overwrite default settings, so `reset all` and `set to d
|
||||
|
||||
</Admonition>
|
||||
|
||||
<Admonition type="danger">
|
||||
|
||||
In order to overwrite the default settings, you must have `Owner` or `Administrator` privileges within your organizations.
|
||||
|
||||
</Admonition>
|
||||
|
||||
#### CLI supported parameters
|
||||
|
||||
<Admonition type="tip">
|
||||
@@ -160,6 +166,6 @@ alter role "<role_name>" set "<setting_name>" to default;
|
||||
|
||||
### Considerations
|
||||
|
||||
1. Changes through the CLI must restart the database and will cause momentary disruption to existing database connections; in most cases this should not take more than a few seconds.
|
||||
1. Changes through the CLI must restart the database and will cause momentary disruption to existing database connections; in most cases this should not take more than a few seconds. However, you can use the --no-restart flag to bypass the restart and keep the connections intact. Keep in mind that this depends on the specific configuration changes you’re making. if the change requires a restart, using the --no-restart flag will prevent the restart but you won’t see those changes take effect until a restart is manually triggered.
|
||||
1. Custom Postgres Config will always override the default optimizations generated by Supabase. When changing compute add-ons, you should also review and update your custom Postgres Config to ensure they remain compatible and effective with the updated compute.
|
||||
1. Some parameters (e.g. `wal_keep_size`) can increase disk utilization, triggering disk expansion, which in turn can lead to [increases in your bill](/docs/guides/platform/compute-add-ons#disk-io).
|
||||
@@ -13,7 +13,7 @@ Features:
|
||||
- Identifies tables/columns obfuscated by views
|
||||
- Skips duplicate indexes
|
||||
|
||||
`index_advisor` is accessible directly through Supabase Studio by navigating to the [Query Performance Report](/dashboard/project/_/database/query-performance) and selecting a query and then the "indexes" tab.
|
||||
`index_advisor` is accessible directly through Supabase Studio by navigating to the [Query Performance Report](/dashboard/project/_/advisors/query-performance) and selecting a query and then the "indexes" tab.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ pg_cron is not fully supported on Fly Postgres. Read more about this [Fly Postgr
|
||||
|
||||
```sql
|
||||
-- Example: enable the "pg_cron" extension
|
||||
create extension pg_cron with schema extensions;
|
||||
create extension pg_cron with schema pg_catalog;
|
||||
|
||||
grant usage on schema cron to postgres;
|
||||
grant all privileges on all tables in schema cron to postgres;
|
||||
|
||||
@@ -309,9 +309,9 @@ order by "created" desc;
|
||||
|
||||
## Configuration
|
||||
|
||||
<Admonition type="note" label="Only Self-Hosted Deployments can Modify Configuration Variables">
|
||||
<Admonition type="note" label="Must be on pg_net v0.12.0 or above to reconfigure ">
|
||||
|
||||
See the [self-hosting docs](/docs/guides/hosting/overview#configuration) for self-hosting instructions.
|
||||
Supabase supports reconfiguring pg_net starting from v0.12.0+. For the latest release, initiate a Postgres upgrade in the [Infrastructure Settings](https://supabase.com/dashboard/project/*/settings/infrastructure).
|
||||
|
||||
</Admonition>
|
||||
|
||||
@@ -329,18 +329,11 @@ where "name" like 'pg_net%';
|
||||
|
||||
### Alter settings
|
||||
|
||||
Note, that doing `ALTER SYSTEM` requires SUPERUSER but on PostgreSQL >= 15, you can do:
|
||||
Change variables:
|
||||
|
||||
```sql
|
||||
grant alter system on parameter pg_net.ttl to <role>;
|
||||
grant alter system on parameter pg_net.batch_size to <role>;
|
||||
```
|
||||
|
||||
Then variables can be changed:
|
||||
|
||||
```sql
|
||||
alter system set pg_net.ttl to '1 hour'
|
||||
alter system set pg_net.batch_size to 500;
|
||||
alter role "postgres" set pg_net.ttl to '24 hour';
|
||||
alter role "postgres" set pg_net.batch_size to 500;
|
||||
```
|
||||
|
||||
Then reload the settings and restart the `pg_net` background worker with:
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
id: 'pg_partman'
|
||||
title: 'pg_partman: partition management'
|
||||
description: 'Automated partition management'
|
||||
---
|
||||
|
||||
[`pg_partman`](https://github.com/pgpartman/pg_partman) is a PostgreSQL extension that enables automated partition management. It removes the need to create and detach partitions for tables partitioned by time or an integer series.
|
||||
|
||||
## Enable the extension
|
||||
|
||||
To enable `pg_partman`, first enable the extension in your database inside a dedicated schema.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
create schema partman;
|
||||
create extension pg_partman schema partman;
|
||||
```
|
||||
|
||||
## Setting up Partitioning
|
||||
|
||||
pg_partman supports partitioning based on time (e.g., by day, month, or year) or by a sequential integer. Setup is performed using the `create_parent` function.
|
||||
|
||||
For time-based partitioning:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select partman.create_parent('schema_name.table_name', 'partition_column', 'time', 'daily');
|
||||
```
|
||||
|
||||
or for integer-based partitioning:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select partman.create_parent('schema_name.table_name', 'partition_column', 'integer', '1000');
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
- partman.create_parent: creates a parent table for partitioning
|
||||
- partman.run_maintenance: ensures partitions are created and dropped as per the defined settings. This should be run periodically, such as via a cron job scheduled by pg_cron
|
||||
|
||||
## Example
|
||||
|
||||
Let’s set up a simple partitioning example using pg_partman with time-based partitions.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
-- Create the table
|
||||
create table public.foo (
|
||||
id serial primary key,
|
||||
data text,
|
||||
created_at timestamp not null
|
||||
);
|
||||
|
||||
-- Create the parent table
|
||||
select
|
||||
partman.create_parent(
|
||||
'public.foo',
|
||||
'created_at',
|
||||
'time',
|
||||
'daily'
|
||||
);
|
||||
|
||||
-- Configure the partitioned table
|
||||
update
|
||||
partman.part_config
|
||||
set
|
||||
infinite_time_partitions = true,
|
||||
retention = '3 months',
|
||||
retention_keep_table=true
|
||||
where
|
||||
parent_table = 'public.foo';
|
||||
```
|
||||
|
||||
For more information about available configuration, see the [pg_partman docs](https://github.com/pgpartman/pg_partman/blob/de93f4a104b8566087f4b9b2a0edf25893672da8/doc/pg_partman.md#configuration-tables).
|
||||
|
||||
Following these statements, partitions for `public.foo` will be automatically managed as necessary when the `partman.run_maintenance()` function is called.
|
||||
|
||||
## Running Maintenance
|
||||
|
||||
It is important to call the `partman.run_maintenance()` procedure frequently to ensure partitions are created and detached according to the partitioned table's configuration.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
call partman.run_maintenance_proc();
|
||||
```
|
||||
|
||||
To automate this task, consider using `pg_cron`.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
create extension pg_cron;
|
||||
|
||||
select
|
||||
cron.schedule('@hourly', $$call partman.run_maintenance_proc()$$);
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- Official [pg_partman documentation](https://github.com/pgpartman/pg_partman/blob/master/doc/pg_partman_howto.md)
|
||||
@@ -1,707 +0,0 @@
|
||||
---
|
||||
id: 'pgmq'
|
||||
title: 'pgmq: Queues'
|
||||
description: 'pgmq: Managed queues in Postgres'
|
||||
---
|
||||
|
||||
pgmq is a lightweight message queue built on Postgres.
|
||||
|
||||
## Features
|
||||
|
||||
- Lightweight - No background worker or external dependencies, just Postgres functions packaged in an extension
|
||||
- "exactly once" delivery of messages to a consumer within a visibility timeout
|
||||
- API parity with AWS SQS and RSMQ
|
||||
- Messages stay in the queue until explicitly removed
|
||||
- Messages can be archived, instead of deleted, for long-term retention and replayability
|
||||
|
||||
## Enable the extension
|
||||
|
||||
```sql
|
||||
create extension pgmq;
|
||||
```
|
||||
|
||||
## Usage [#get-usage]
|
||||
|
||||
### Sending Messages
|
||||
|
||||
#### send
|
||||
|
||||
Send a single message to a queue.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.send(
|
||||
queue_name text,
|
||||
msg jsonb,
|
||||
delay integer default 0
|
||||
)
|
||||
returns setof bigint
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :------ | :----------------------------------------------------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
| msg | jsonb | The message to send to the queue |
|
||||
| delay | integer | Time in seconds before the message becomes visible. Defaults to 0. |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.send('my_queue', '{"hello": "world"}');
|
||||
send
|
||||
------
|
||||
4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### send_batch
|
||||
|
||||
Send 1 or more messages to a queue.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.send_batch(
|
||||
queue_name text,
|
||||
msgs jsonb[],
|
||||
delay integer default 0
|
||||
)
|
||||
returns setof bigint
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :------ | :------------------------------------------------------------------ |
|
||||
| queue_name | text | The name of the queue |
|
||||
| msgs | jsonb[] | Array of messages to send to the queue |
|
||||
| delay | integer | Time in seconds before the messages becomes visible. Defaults to 0. |
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.send_batch(
|
||||
'my_queue',
|
||||
array[
|
||||
'{"hello": "world_0"}'::jsonb,
|
||||
'{"hello": "world_1"}'::jsonb
|
||||
]
|
||||
);
|
||||
send_batch
|
||||
------------
|
||||
1
|
||||
2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Reading Messages
|
||||
|
||||
#### read
|
||||
|
||||
Read 1 or more messages from a queue. The VT specifies the delay in seconds between reading and the message becoming invisible to other consumers.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.read(
|
||||
queue_name text,
|
||||
vt integer,
|
||||
qty integer
|
||||
)
|
||||
|
||||
returns setof pgmq.message_record
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :------ | :-------------------------------------------------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
| vt | integer | Time in seconds that the message become invisible after reading |
|
||||
| qty | integer | The number of messages to read from the queue. Defaults to 1 |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.read('my_queue', 10, 2);
|
||||
msg_id | read_ct | enqueued_at | vt | message
|
||||
--------+---------+-------------------------------+-------------------------------+----------------------
|
||||
1 | 1 | 2023-10-28 19:14:47.356595-05 | 2023-10-28 19:17:08.608922-05 | {"hello": "world_0"}
|
||||
2 | 1 | 2023-10-28 19:14:47.356595-05 | 2023-10-28 19:17:08.608974-05 | {"hello": "world_1"}
|
||||
(2 rows)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### read_with_poll
|
||||
|
||||
Same as read(). Also provides convenient long-poll functionality.
|
||||
When there are no messages in the queue, the function call will wait for `max_poll_seconds` in duration before returning.
|
||||
If messages reach the queue during that duration, they will be read and returned immediately.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.read_with_poll(
|
||||
queue_name text,
|
||||
vt integer,
|
||||
qty integer,
|
||||
max_poll_seconds integer default 5,
|
||||
poll_interval_ms integer default 100
|
||||
)
|
||||
returns setof pgmq.message_record
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------------- | :------ | :-------------------------------------------------------------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
| vt | integer | Time in seconds that the message become invisible after reading. |
|
||||
| qty | integer | The number of messages to read from the queue. Defaults to 1. |
|
||||
| max_poll_seconds | integer | Time in seconds to wait for new messages to reach the queue. Defaults to 5. |
|
||||
| poll_interval_ms | integer | Milliseconds between the internal poll operations. Defaults to 100. |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.read_with_poll('my_queue', 1, 1, 5, 100);
|
||||
msg_id | read_ct | enqueued_at | vt | message
|
||||
--------+---------+-------------------------------+-------------------------------+--------------------
|
||||
1 | 1 | 2023-10-28 19:09:09.177756-05 | 2023-10-28 19:27:00.337929-05 | {"hello": "world"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### pop
|
||||
|
||||
Reads a single message from a queue and deletes it upon read.
|
||||
|
||||
Note: utilization of pop() results in at-most-once delivery semantics if the consuming application does not guarantee processing of the message.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.pop(queue_name text)
|
||||
returns setof pgmq.message_record
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :--- | :-------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq=# select * from pgmq.pop('my_queue');
|
||||
msg_id | read_ct | enqueued_at | vt | message
|
||||
--------+---------+-------------------------------+-------------------------------+--------------------
|
||||
1 | 2 | 2023-10-28 19:09:09.177756-05 | 2023-10-28 19:27:00.337929-05 | {"hello": "world"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Deleting/Archiving Messages
|
||||
|
||||
#### delete (single)
|
||||
|
||||
Deletes a single message from a queue.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.delete (queue_name text, msg_id: bigint)
|
||||
returns boolean
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :----- | :---------------------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
| msg_id | bigint | Message ID of the message to delete |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select pgmq.delete('my_queue', 5);
|
||||
delete
|
||||
--------
|
||||
t
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### delete (batch)
|
||||
|
||||
Delete one or many messages from a queue.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.delete (queue_name text, msg_ids: bigint[])
|
||||
returns setof bigint
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :------- | :----------------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
| msg_ids | bigint[] | Array of message IDs to delete |
|
||||
|
||||
Examples:
|
||||
|
||||
Delete two messages that exist.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.delete('my_queue', array[2, 3]);
|
||||
delete
|
||||
--------
|
||||
2
|
||||
3
|
||||
```
|
||||
|
||||
Delete two messages, one that exists and one that does not. Message `999` does not exist.
|
||||
|
||||
```sql
|
||||
select * from pgmq.delete('my_queue', array[6, 999]);
|
||||
delete
|
||||
--------
|
||||
6
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### purge_queue
|
||||
|
||||
Permanently deletes all messages in a queue. Returns the number of messages that were deleted.
|
||||
|
||||
```text
|
||||
purge_queue(queue_name text)
|
||||
returns bigint
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :--- | :-------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
|
||||
Example:
|
||||
|
||||
Purge the queue when it contains 8 messages;
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.purge_queue('my_queue');
|
||||
purge_queue
|
||||
-------------
|
||||
8
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### archive (single)
|
||||
|
||||
Removes a single requested message from the specified queue and inserts it into the queue's archive.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.archive(queue_name text, msg_id bigint)
|
||||
returns boolean
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :----- | :----------------------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
| msg_id | bigint | Message ID of the message to archive |
|
||||
|
||||
Returns
|
||||
Boolean value indicating success or failure of the operation.
|
||||
|
||||
Example; remove message with ID 1 from queue `my_queue` and archive it:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.archive('my_queue', 1);
|
||||
archive
|
||||
---------
|
||||
t
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### archive (batch)
|
||||
|
||||
Deletes a batch of requested messages from the specified queue and inserts them into the queue's archive.
|
||||
Returns an array of message ids that were successfully archived.
|
||||
|
||||
```text
|
||||
pgmq.archive(queue_name text, msg_ids bigint[])
|
||||
RETURNS SETOF bigint
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :------- | :------------------------------ |
|
||||
| queue_name | text | The name of the queue |
|
||||
| msg_ids | bigint[] | Array of message IDs to archive |
|
||||
|
||||
Examples:
|
||||
|
||||
Delete messages with ID 1 and 2 from queue `my_queue` and move to the archive.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.archive('my_queue', array[1, 2]);
|
||||
archive
|
||||
---------
|
||||
1
|
||||
2
|
||||
```
|
||||
|
||||
Delete messages 4, which exists and 999, which does not exist.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.archive('my_queue', array[4, 999]);
|
||||
archive
|
||||
---------
|
||||
4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Queue Management
|
||||
|
||||
#### create
|
||||
|
||||
Create a new queue.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.create(queue_name text)
|
||||
returns void
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :--- | :-------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select from pgmq.create('my_queue');
|
||||
create
|
||||
--------
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### create_partitioned
|
||||
|
||||
Create a partitioned queue.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.create_partitioned (
|
||||
queue-ue_name text,
|
||||
partition_interval text default '10000'::text,
|
||||
retention_interval text default '100000'::text
|
||||
)
|
||||
returns void
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :----------------- | :--- | :-------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
| partition_interval | text | The name of the queue |
|
||||
| retention_interval | text | The name of the queue |
|
||||
|
||||
Example:
|
||||
|
||||
Create a queue with 100,000 messages per partition, and will retain 10,000,000 messages on old partitions. Partitions greater than this will be deleted.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.create_partitioned(
|
||||
'my_partitioned_queue',
|
||||
'100000',
|
||||
'10000000'
|
||||
);
|
||||
create_partitioned
|
||||
--------------------
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### create_unlogged
|
||||
|
||||
Creates an unlogged table. This is useful when write throughput is more important that durability.
|
||||
See Postgres documentation for [unlogged tables](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-UNLOGGED) for more information.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.create_unlogged(queue_name text)
|
||||
returns void
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :--- | :-------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select pgmq.create_unlogged('my_unlogged');
|
||||
create_unlogged
|
||||
-----------------
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### detach_archive
|
||||
|
||||
Drop the queue's archive table as a member of the PGMQ extension. Useful for preventing the queue's archive table from being drop when `drop extension pgmq` is executed.
|
||||
This does not prevent the further archives() from appending to the archive table.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.detach_archive(queue_name text)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :--- | :-------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.detach_archive('my_queue');
|
||||
detach_archive
|
||||
----------------
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### drop_queue
|
||||
|
||||
Deletes a queue and its archive table.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.drop_queue(queue_name text)
|
||||
returns boolean
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :--- | :-------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.drop_queue('my_unlogged');
|
||||
drop_queue
|
||||
------------
|
||||
t
|
||||
```
|
||||
|
||||
### Utilities
|
||||
|
||||
#### set_vt
|
||||
|
||||
Sets the visibility timeout of a message to a specified time duration in the future. Returns the record of the message that was updated.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.set_vt(
|
||||
queue_name text,
|
||||
msg_id bigint,
|
||||
vt_offset integer
|
||||
)
|
||||
returns pgmq.message_record
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :------ | :-------------------------------------------------------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
| msg_id | bigint | ID of the message to set visibility time |
|
||||
| vt_offset | integer | Duration from now, in seconds, that the message's VT should be set to |
|
||||
|
||||
Example:
|
||||
|
||||
Set the visibility timeout of message 1 to 30 seconds from now.
|
||||
|
||||
```sql
|
||||
select * from pgmq.set_vt('my_queue', 11, 30);
|
||||
msg_id | read_ct | enqueued_at | vt | message
|
||||
--------+---------+-------------------------------+-------------------------------+----------------------
|
||||
1 | 0 | 2023-10-28 19:42:21.778741-05 | 2023-10-28 19:59:34.286462-05 | {"hello": "world_0"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### list_queues
|
||||
|
||||
List all the queues that currently exist.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
list_queues()
|
||||
RETURNS TABLE(
|
||||
queue_name text,
|
||||
created_at timestamp with time zone,
|
||||
is_partitioned boolean,
|
||||
is_unlogged boolean
|
||||
)
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.list_queues();
|
||||
queue_name | created_at | is_partitioned | is_unlogged
|
||||
----------------------+-------------------------------+----------------+-------------
|
||||
my_queue | 2023-10-28 14:13:17.092576-05 | f | f
|
||||
my_partitioned_queue | 2023-10-28 19:47:37.098692-05 | t | f
|
||||
my_unlogged | 2023-10-28 20:02:30.976109-05 | f | t
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### metrics
|
||||
|
||||
Get metrics for a specific queue.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
pgmq.metrics(queue_name: text)
|
||||
returns table(
|
||||
queue_name text,
|
||||
queue_length bigint,
|
||||
newest_msg_age_sec integer,
|
||||
oldest_msg_age_sec integer,
|
||||
total_messages bigint,
|
||||
scrape_time timestamp with time zone
|
||||
)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :--------- | :--- | :-------------------- |
|
||||
| queue_name | text | The name of the queue |
|
||||
|
||||
**Returns:**
|
||||
|
||||
| Attribute | Type | Description |
|
||||
| :----------------- | :----------------------- | :------------------------------------------------------------------------ |
|
||||
| queue_name | text | The name of the queue |
|
||||
| queue_length | bigint | Number of messages currently in the queue |
|
||||
| newest_msg_age_sec | integer \| null | Age of the newest message in the queue, in seconds |
|
||||
| oldest_msg_age_sec | integer \| null | Age of the oldest message in the queue, in seconds |
|
||||
| total_messages | bigint | Total number of messages that have passed through the queue over all time |
|
||||
| scrape_time | timestamp with time zone | The current timestamp |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.metrics('my_queue');
|
||||
queue_name | queue_length | newest_msg_age_sec | oldest_msg_age_sec | total_messages | scrape_time
|
||||
------------+--------------+--------------------+--------------------+----------------+-------------------------------
|
||||
my_queue | 16 | 2445 | 2447 | 35 | 2023-10-28 20:23:08.406259-05
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### metrics_all
|
||||
|
||||
Get metrics for all existing queues.
|
||||
|
||||
```text
|
||||
pgmq.metrics_all()
|
||||
RETURNS TABLE(
|
||||
queue_name text,
|
||||
queue_length bigint,
|
||||
newest_msg_age_sec integer,
|
||||
oldest_msg_age_sec integer,
|
||||
total_messages bigint,
|
||||
scrape_time timestamp with time zone
|
||||
)
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
|
||||
| Attribute | Type | Description |
|
||||
| :----------------- | :----------------------- | :------------------------------------------------------------------------ |
|
||||
| queue_name | text | The name of the queue |
|
||||
| queue_length | bigint | Number of messages currently in the queue |
|
||||
| newest_msg_age_sec | integer \| null | Age of the newest message in the queue, in seconds |
|
||||
| oldest_msg_age_sec | integer \| null | Age of the oldest message in the queue, in seconds |
|
||||
| total_messages | bigint | Total number of messages that have passed through the queue over all time |
|
||||
| scrape_time | timestamp with time zone | The current timestamp |
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
select * from pgmq.metrics_all();
|
||||
queue_name | queue_length | newest_msg_age_sec | oldest_msg_age_sec | total_messages | scrape_time
|
||||
----------------------+--------------+--------------------+--------------------+----------------+-------------------------------
|
||||
my_queue | 16 | 2563 | 2565 | 35 | 2023-10-28 20:25:07.016413-05
|
||||
my_partitioned_queue | 1 | 11 | 11 | 1 | 2023-10-28 20:25:07.016413-05
|
||||
my_unlogged | 1 | 3 | 3 | 1 | 2023-10-28 20:25:07.016413-05
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
#### message_record
|
||||
|
||||
The complete representation of a message in a queue.
|
||||
|
||||
| Attribute Name | Type | Description |
|
||||
| :------------- | :----------------------- | :--------------------------------------------------------------------- |
|
||||
| msg_id | bigint | Unique ID of the message |
|
||||
| read_ct | bigint | Number of times the message has been read. Increments on read(). |
|
||||
| enqueued_at | timestamp with time zone | time that the message was inserted into the queue |
|
||||
| vt | timestamp with time zone | Timestamp when the message will become available for consumers to read |
|
||||
| message | jsonb | The message payload |
|
||||
|
||||
Example:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
msg_id | read_ct | enqueued_at | vt | message
|
||||
--------+---------+-------------------------------+-------------------------------+--------------------
|
||||
1 | 1 | 2023-10-28 19:06:19.941509-05 | 2023-10-28 19:06:27.419392-05 | {"hello": "world"}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- Official Docs: [pgmq/api](https://tembo.io/pgmq/#creating-a-queue)
|
||||
@@ -121,7 +121,7 @@ For more information on the Index Advisor and its suggestions, see the [`index_a
|
||||
|
||||
To use the Dashboard Index Advisor:
|
||||
|
||||
1. Go to the [Query Performance](/dashboard/project/_/database/query-performance) page.
|
||||
1. Go to the [Query Performance](/dashboard/project/_/advisors/query-performance) page.
|
||||
1. Click on a query to bring up the Details side panel.
|
||||
1. Select the Indexes tab.
|
||||
1. Enable Index Advisor if prompted.
|
||||
|
||||
@@ -153,7 +153,7 @@ limit 100;
|
||||
|
||||
### Using the Query Performance page
|
||||
|
||||
Go to the [Query Performance page](/dashboard/project/_/database/query-performance?preset=slowest_execution) and filter by relevant role and query speeds. This only identifies slow-running but successful queries. Unlike the Log Explorer, it does not show you timed-out queries.
|
||||
Go to the [Query Performance page](/dashboard/project/_/advisors/query-performance?preset=slowest_execution) and filter by relevant role and query speeds. This only identifies slow-running but successful queries. Unlike the Log Explorer, it does not show you timed-out queries.
|
||||
|
||||
### Understanding roles in logs
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t
|
||||
<StepHikeCompact>
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Create a custom user for Prisma">
|
||||
- In the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new), create a Prisma db-user with full privileges on the public schema.
|
||||
- This gives you better control over Prisma's access and makes it easier to monitor using Supabase tools like the [Query Performance Dashboard](https://supabase.com/dashboard/project/_/database/query-performance) and [Log Explorer](https://supabase.com/dashboard/project/_/logs/explorer).
|
||||
- In the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new), create a Prisma db-user with full privileges on the public schema.
|
||||
- This gives you better control over Prisma's access and makes it easier to monitor using Supabase tools like the [Query Performance Dashboard](https://supabase.com/dashboard/project/_/advisors/query-performance) and [Log Explorer](https://supabase.com/dashboard/project/_/logs/explorer).
|
||||
<Admonition type="note" label="password manager">
|
||||
|
||||
For security, consider using a [password generator](https://bitwarden.com/password-generator/) for the Prisma role.
|
||||
|
||||
@@ -51,7 +51,7 @@ create index idx_orders_status on orders (status);
|
||||
|
||||
Indexes on the columns used for joining tables can help Postgres avoid scanning tables in their entirety when connecting tables.
|
||||
|
||||
- Indexing and `b.customer_id` would likely improve the performance of the join in this query.
|
||||
- Indexing `a.id` and `b.customer_id` would likely improve the performance of the join in this query.
|
||||
- Note that if `a.id` is the primary key of the `customers` table it is already indexed
|
||||
|
||||
```sql
|
||||
|
||||
@@ -14,3 +14,4 @@ Supavisor logs are available under [Pooler Logs](/dashboard/project/_/logs/poole
|
||||
| Subscribe error: \{:error, :worker_not_found} | This log event is emitted when the client tries to connect to the database, but Supavisor does not have the necessary information to route the connection. Try reconnecting to the database as it can take some time for the project information to propagate to Supavisor. | N/A |
|
||||
| Subscribe error: \{:error, \{:badrpc, \{:error, \{:erpc, :timeout}}}} | This is a timeout error when the communication between different Supavisor nodes takes longer than expected. Try reconnecting to the database. | N/A |
|
||||
| Terminating with reason :client_termination when state was :busy | This error happens when the client terminates the connection before the connection with the database is completed. | N/A |
|
||||
| Error: received invalid response to GSSAPI negotiation: S | This error happens due to "gssencmode" parameter not set to disabled. | Follow this [guide](https://github.com/orgs/supabase/discussions/30173) to resolve. |
|
||||
@@ -27,7 +27,7 @@ This video demonstrates how you can create a new customer in Stripe each time a
|
||||
|
||||
## Creating a webhook
|
||||
|
||||
1. Create a new [Database Webhook](https://supabase.com/dashboard/project/_/database/hooks) in the Dashboard.
|
||||
1. Create a new [Database Webhook](https://supabase.com/dashboard/project/_/integrations/hooks) in the Dashboard.
|
||||
1. Give your Webhook a name.
|
||||
1. Select the table you want to hook into.
|
||||
1. Select one or more events (table inserts, updates, or deletes) you want to hook into.
|
||||
|
||||
@@ -25,5 +25,5 @@ See the [self-hosting guides](/docs/guides/self-hosting) for instructions on hos
|
||||
You can automate deployments using:
|
||||
|
||||
- The [Supabase GitHub integration](/dashboard/project/_/settings/integrations) (with branching enabled)
|
||||
- The [Supabase CLI](/docs/local-development/cli) in your own continuous deployment pipeline
|
||||
- The [Supabase CLI](/docs/guides/local-development) in your own continuous deployment pipeline
|
||||
- The [Supabase Terraform provider](/docs/guides/deployment/terraform)
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
id: 'function-background-tasks'
|
||||
title: 'Background Tasks'
|
||||
description: 'How to run background tasks in an Edge Function outside of the request handler'
|
||||
subtitle: 'How to run background tasks in an Edge Function outside of the request handler'
|
||||
---
|
||||
|
||||
Edge Function instances can process background tasks outside of the request handler. Background tasks are useful for asynchronous operations like uploading a file to Storage, updating a database, or sending events to a logging service. You can respond to the request immediately and leave the task running in the background.
|
||||
|
||||
A background task can run until the Edge Function instance hits its [wall-clock limit](/docs/guides/functions/limits).
|
||||
|
||||
Here's an example of defining a background task using a custom event.
|
||||
|
||||
```ts
|
||||
// define a custom event
|
||||
class MyBackgroundTaskEvent extends Event {
|
||||
readonly taskPromise: Promise<string>
|
||||
|
||||
constructor(taskPromise) {
|
||||
super('myBackgroundTask')
|
||||
this.taskPromise = taskPromise
|
||||
}
|
||||
}
|
||||
|
||||
globalThis.addEventListener('myBackgroundTask', async (event: MyBackgroundTaskEvent) => {
|
||||
const res = await event.taskPromise
|
||||
console.log(await res.json())
|
||||
})
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const fetchPromise = fetch('https://httpbin.org/json')
|
||||
const event = new MyBackgroundTaskEvent(fetchPromise)
|
||||
globalThis.dispatchEvent(event)
|
||||
|
||||
return new Response('ok')
|
||||
})
|
||||
```
|
||||
|
||||
### Testing background tasks locally
|
||||
|
||||
When testing Edge Functions locally with Supabase CLI, the instances are terminated automatically after a request is completed. This will prevent background tasks from running to completion.
|
||||
|
||||
To prevent that, you can update the `supabase/config.toml` with the following settings:
|
||||
|
||||
```toml
|
||||
[edge_runtime]
|
||||
policy = "per_worker"
|
||||
```
|
||||
|
||||
When running with `per_worker` policy, Function won't auto-reload on edits. You will need to manually restart it by running `supabase functions serve`.
|
||||
@@ -2,169 +2,14 @@
|
||||
id: 'functions-dart-edge'
|
||||
title: 'Dart Edge'
|
||||
description: 'Write your functions using Dart.'
|
||||
tocVideo: '53jhgrDONW4'
|
||||
---
|
||||
|
||||
[Dart Edge](https://docs.dartedge.dev/), a project built and maintained by [Invertase](https://invertase.io/), enables Dart code to be run on different edge environments. This guide will walk you through how you can start using Dart Edge to write your backend code using Dart and run it on Supabase.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues.
|
||||
Please be aware that the Dart Edge project is currently not actively maintained due to numerous breaking changes in Dart's development of (WASM) support.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Prerequisites
|
||||
[Dart Edge](https://docs.dartedge.dev/) is an experimental project that enables you to write Supabase Edge Functions using Dart. It's built and maintained by [Invertase](https://invertase.io/).
|
||||
|
||||
Make sure you have [the edge CLI](https://docs.dartedge.dev/#getting-started) installed in addition to [the Supabase CLI](https://supabase.com/docs/guides/cli#installation).
|
||||
|
||||
```bash
|
||||
# install the edge CLI
|
||||
dart pub global activate edge
|
||||
```
|
||||
|
||||
## Setting up your first Dart Edge project
|
||||
|
||||
Create a new Dart Edge project using the `new` command on the edge CLI. This will create a familiar boilerplate for your Dart Edge project such as `main.dart` and `pubspec.yaml` file.
|
||||
|
||||
```bash
|
||||
edge new supabase_functions new_project
|
||||
```
|
||||
|
||||
`cd` into the project and initialize a new Supabase project inside the project you just created.
|
||||
|
||||
```bash
|
||||
cd new_project
|
||||
supabase init
|
||||
```
|
||||
|
||||
Run `pub get` to install the dependencies.
|
||||
|
||||
```bash
|
||||
dart pub get
|
||||
```
|
||||
|
||||
Open the `lib/main.dart` and look at the initial template.
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
SupabaseFunctions(fetch: (request) {
|
||||
return Response("Hello from Supabase Edge Functions!");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
`SupabaseFunctions` class has a `fetch` handler, which is expected to return a `Response` object, and has a `request` parameter, which contains information about the request such as body and headers.
|
||||
|
||||
## Supporting multiple edge functions
|
||||
|
||||
By default, Dart Edge will create a single `main.dart` file in your project, which will be compiled into a function called `dart_edge`.
|
||||
You can override this behavior and create multiple functions by creating an `edge.yaml` file in the root of your project.
|
||||
Specify how to map which of your dart files should compile to what Supabase edge functions using the `functions` option.
|
||||
|
||||
```yaml
|
||||
supabase:
|
||||
functions:
|
||||
api_handler: 'lib/api.dart'
|
||||
webhook_handler: 'lib/webhooks.dart'
|
||||
```
|
||||
|
||||
## Using Supabase client
|
||||
|
||||
Now let’s try to interact with our Supabase database. We will start by installing the dependencies.
|
||||
|
||||
### Add dependencies
|
||||
|
||||
Add [`supabase`](https://pub.dev/packages/supabase) to interact with Supabase service.
|
||||
|
||||
```bash
|
||||
dart pub add supabase
|
||||
```
|
||||
|
||||
We also need to use a special HTTP client to HTTP requests in the edge environment.
|
||||
|
||||
```bash
|
||||
dart pub add edge_http_client
|
||||
```
|
||||
|
||||
### Initialize `SupabaseClient`
|
||||
|
||||
At this point, you can initialize a `SupabaseClient` by passing the [EdgeHttpClient](https://pub.dev/packages/edge_http_client). We can access the Supabase credentials through the [environment variables](https://supabase.com/docs/guides/functions/secrets) using `Deno.env.get()` method. Note that [Supabase credentials are available by default](https://supabase.com/docs/guides/functions/secrets), so no configurations are necessary.
|
||||
|
||||
Now we have the following sample functions code to query Supabase database using the Supabase client.
|
||||
|
||||
```dart
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:supabase_functions/supabase_functions.dart';
|
||||
import 'package:edge_http_client/edge_http_client.dart';
|
||||
import 'package:supabase/supabase.dart';
|
||||
|
||||
void main() {
|
||||
final supabase = SupabaseClient(
|
||||
Deno.env.get('SUPABASE_URL')!,
|
||||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, // Use service role key to bypass RLS
|
||||
httpClient: EdgeHttpClient(),
|
||||
);
|
||||
|
||||
SupabaseFunctions(fetch: (request) async {
|
||||
// You can query `public.users` table for example.
|
||||
final List users = await supabase.from('users').select().limit(10);
|
||||
return Response.json(users);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Run the Functions
|
||||
|
||||
### Run locally
|
||||
|
||||
Create a `edge.yaml` file if you don't have one yet in the root of your project. Then, add the following configuration to specify the path to your Supabase project.
|
||||
|
||||
```yaml
|
||||
supabase:
|
||||
project_path: './supabase'
|
||||
```
|
||||
|
||||
Also, currently Dart Edge doesn't work with the latest versions of Flutter. If you do have the latest version of Flutter on your machine, you can use [Flutter Version Management (FVM)](https://fvm.app/) to use an older version of Flutter for your Dart Edge project.
|
||||
|
||||
```bash
|
||||
fvm use 3.7.12 -f
|
||||
```
|
||||
|
||||
You can read more about how to configure FVM for your project in the [FVM documentation](https://fvm.app/documentation/getting-started).
|
||||
|
||||
Run the following command to compile your Dart code. This command will start a watcher that will recompile anytime you make changes to your code.
|
||||
|
||||
```bash
|
||||
edge build supabase_functions --dev
|
||||
|
||||
# Or with FVM
|
||||
fvm dart pub run edge build supabase_functions --dev
|
||||
```
|
||||
|
||||
With docker running, start your local Supabase with the following command
|
||||
|
||||
```bash
|
||||
supabase start
|
||||
```
|
||||
|
||||
While you have the above code running, open another terminal and run the following to start the Supabase local development environment.
|
||||
|
||||
```bash
|
||||
supabase functions serve dart_edge --no-verify-jwt
|
||||
```
|
||||
|
||||
You should be able to access your local function here:
|
||||
|
||||
[http://localhost:54321/functions/v1/dart_edge](http://localhost:54321/functions/v1/dart_edge)
|
||||
|
||||
### Deploy to the Edge
|
||||
|
||||
Run the following commands to compile and deploy your function on Supabase Edge functions.
|
||||
|
||||
```bash
|
||||
edge build supabase_functions
|
||||
supabase functions deploy dart_edge
|
||||
```
|
||||
|
||||
You will be asked to provide the project reference of your Supabase instance to which you want to deploy the function. You can [link your local project to your Supabase instance](https://supabase.com/docs/guides/getting-started/local-development#link-your-project) to avoid having to provide the Supabase reference every time you deploy.
|
||||
For detailed information on how to set up and use Dart Edge with Supabase, please refer to the [official Dart Edge documentation for Supabase](https://invertase.docs.page/dart_edge/platform/supabase).
|
||||
@@ -8,28 +8,152 @@ tocVideo: 'ILr3cneZuFk'
|
||||
|
||||
Developing with Edge Functions is similar to developing with Node.js, but with a few key differences. This guide will help you understand how to manage your dependencies.
|
||||
|
||||
## Managing dependencies
|
||||
|
||||
There are two ways to manage your dependencies in Supabase Edge Functions:
|
||||
|
||||
### Using deno.json (Recommended)
|
||||
|
||||
<Admonition type="info">
|
||||
|
||||
This feature requires Supabase CLI version 1.215.0 or higher.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Each function can have its own `deno.json` or `deno.jsonc` file to manage dependencies and configure Deno-specific settings. For a complete list of supported options, see the [official Deno configuration documentation](https://docs.deno.com/runtime/manual/getting_started/configuration_file).
|
||||
|
||||
```json supabase/functions/my-function/deno.json
|
||||
{
|
||||
"imports": {
|
||||
"lodash": "https://cdn.skypack.dev/lodash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to use simplified imports:
|
||||
|
||||
```ts
|
||||
import lodash from 'lodash'
|
||||
```
|
||||
|
||||
The recommended file structure when using `deno.json`:
|
||||
|
||||
```bash
|
||||
└── supabase
|
||||
├── functions
|
||||
│ ├── function-one
|
||||
│ │ ├── index.ts
|
||||
│ │ └── deno.json # Function-specific Deno configuration
|
||||
│ └── function-two
|
||||
│ ├── index.ts
|
||||
│ └── deno.json # Function-specific Deno configuration
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
### Using Import Maps (Legacy)
|
||||
|
||||
Import Maps are a legacy way to manage dependencies, similar to a `package.json` file. While still supported, we recommend using `deno.json`. If both exist, `deno.json` takes precedence.
|
||||
|
||||
```json supabase/functions/import_map.json
|
||||
{
|
||||
"imports": {
|
||||
"lodash": "https://cdn.skypack.dev/lodash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The import map should be placed in the `/supabase/functions` folder and will be applied to all functions:
|
||||
|
||||
```bash
|
||||
└── supabase
|
||||
├── functions
|
||||
│ ├── import_map.json # Top-level import map for all functions
|
||||
│ ├── function-one
|
||||
│ │ └── index.ts
|
||||
│ └── function-two
|
||||
│ └── index.ts
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
You can override the import map location using the `--import-map <string>` flag with `serve` and `deploy` commands.
|
||||
|
||||
If using import maps with VSCode, update your `.vscode/settings.json`:
|
||||
|
||||
```json settings.json
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.unstable": [
|
||||
"bare-node-builtins",
|
||||
"byonm"
|
||||
// ... other flags ...
|
||||
],
|
||||
"deno.importMap": "./supabase/functions/import_map.json"
|
||||
}
|
||||
```
|
||||
|
||||
You can override the default import map location in two ways:
|
||||
|
||||
1. Using the `--import-map <string>` flag with `serve` and `deploy` commands
|
||||
2. Setting the `import_map` property in your `config.toml` file for specific functions:
|
||||
|
||||
```toml supabase/config.toml
|
||||
[functions.my-function]
|
||||
import_map = "./supabase/functions/my-function/custom_import_map.json"
|
||||
```
|
||||
|
||||
For more configuration options, see the [CLI Configuration documentation](/docs/guides/local-development/cli/config#functions.function_name.import_map).
|
||||
|
||||
## Importing dependencies
|
||||
|
||||
Supabase Edge Functions support:
|
||||
Supabase Edge Functions support several ways to import dependencies:
|
||||
|
||||
- The Deno [standard library](https://deno.land/std)
|
||||
- JavaScript modules from npm (https://docs.deno.com/examples/npm/)
|
||||
- Built-in [Node APIs](https://docs.deno.com/runtime/manual/node/compatibility)
|
||||
- Third party modules published to [JSR](https://jsr.io/) or [deno.land/x](https://deno.land/x)
|
||||
|
||||
You can import npm modules using the `npm:` specifier or via CDNs like [esm.sh](https://esm.sh). For example, to import the `supabase-js` package:
|
||||
### NPM Modules
|
||||
|
||||
You can import npm modules using the `npm:` specifier:
|
||||
|
||||
```ts
|
||||
import { createClient } from 'npm:@supabase/supabase-js@2'
|
||||
```
|
||||
|
||||
If you are migrating an existing Node app and if it uses a built-in Node APIs, you can import them to Deno using `node:` specifiers. For example, if your code uses `process.env`, you can import it as follows:
|
||||
### Node.js Built-ins
|
||||
|
||||
For Node.js built-in APIs, use the `node:` specifier:
|
||||
|
||||
```ts
|
||||
import process from 'node:process'
|
||||
```
|
||||
|
||||
You can learn more about `npm:` specifiers and Node built-in APIs, in [Deno's documentation](https://docs.deno.com/runtime/manual/node/npm_specifiers).
|
||||
Learn more about npm specifiers and Node built-in APIs in [Deno's documentation](https://docs.deno.com/runtime/manual/node/npm_specifiers).
|
||||
|
||||
### Importing from Private Registries
|
||||
|
||||
<Admonition type="info">
|
||||
|
||||
This feature requires Supabase CLI version 1.207.9 or higher.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Create a `.npmrc` file within `supabase/functions`. This will allow you to import the private packages into multiple functions. Alternatively, you can place the `.npmrc` file directly inside `supabase/functions/function-name` directory.)
|
||||
|
||||
Add your registry details in the `.npmrc` file. Follow [this guide](https://docs.npmjs.com/cli/v10/configuring-npm/npmrc) to learn more about the syntax of npmrc files.
|
||||
|
||||
```
|
||||
@myorg:registry=https://npm.registryhost.com
|
||||
//npm.registryhost.com/:_authToken=VALID_AUTH_TOKEN
|
||||
```
|
||||
|
||||
After that, you can import the package directly in your function code or add it to the import_map.json (/docs/guides/functions/import-maps#using-import-maps).
|
||||
|
||||
```ts
|
||||
import MyPackage from 'npm:@myorg/private-package@v1.0.1'
|
||||
|
||||
// use MyPackage
|
||||
```
|
||||
|
||||
## Importing types
|
||||
|
||||
@@ -47,68 +171,3 @@ To include types for built-in Node APIs, add the following line to the top of yo
|
||||
```ts
|
||||
/// <reference types="npm:@types/node" />
|
||||
```
|
||||
|
||||
## Using import maps
|
||||
|
||||
An [Import Map](https://github.com/WICG/import-maps#the-import-map) is similar to a `package.json` file. They are a way to manage your dependencies. Consider this code:
|
||||
|
||||
```ts
|
||||
// without import maps:
|
||||
import lodash from 'https://cdn.skypack.dev/lodash'
|
||||
|
||||
// with import maps:
|
||||
import lodash from 'lodash'
|
||||
```
|
||||
|
||||
You can accomplish this using an Import Map, which is a JSON file with the following:
|
||||
|
||||
```json supabase/functions/import_map.json
|
||||
{
|
||||
"imports": {
|
||||
"lodash": "https://cdn.skypack.dev/lodash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We recommend creating one `import_map.json` within the `/supabase/functions` folder, similar to a `package.json` file, to define imports that can be used across all of your project's functions.
|
||||
|
||||
```bash
|
||||
└── supabase
|
||||
├── functions
|
||||
│ ├── import_map.json # A top-level import map to use across functions..
|
||||
│ ├── function-one
|
||||
│ │ └── index.ts
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
Alternatively, you can create one `import_map.json` file in each function folder, which will take priority over a top-level file.
|
||||
|
||||
You can override this default behavior by providing the `--import-map <string>` flag to the `serve` and `deploy` commands.
|
||||
|
||||
### Configuring VSCode
|
||||
|
||||
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": [
|
||||
"bare-node-builtins",
|
||||
"byonm",
|
||||
"sloppy-imports",
|
||||
"unsafe-proto",
|
||||
"webgpu",
|
||||
"broadcast-channel",
|
||||
"worker-options",
|
||||
"cron",
|
||||
"kv",
|
||||
"ffi",
|
||||
"fs",
|
||||
"http",
|
||||
"net"
|
||||
],
|
||||
"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).
|
||||
@@ -14,8 +14,6 @@ subtitle: "Limits applied Edge Functions in Supabase's hosted platform."
|
||||
- Maximum Function Size (after bundling via CLI): 10MB
|
||||
- Maximum log message length: 10,000 characters
|
||||
- Log event threshold: 100 events per 10 seconds
|
||||
- Max Request: 5GB
|
||||
- Max Response: No limit
|
||||
|
||||
## Other limits & restrictions
|
||||
|
||||
|
||||
@@ -186,6 +186,26 @@ verify_jwt = false
|
||||
import_map = './import_map.json'
|
||||
```
|
||||
|
||||
### Not using TypeScript
|
||||
|
||||
When you create a new Edge Function, it will use TypeScript by default. However, it is possible to write and deploy Edge Functions using pure JavaScript.
|
||||
|
||||
Save your Function as a JavaScript file (eg: `index.js`) and then update the `supabase/config.toml` as follows:
|
||||
|
||||
<Admonition type="info">
|
||||
|
||||
`entrypoint` is available only in Supabase CLI version 1.215.0 or higher.
|
||||
|
||||
</Admonition>
|
||||
|
||||
```toml supabase/config.toml
|
||||
[functions.hello-world]
|
||||
# other entries
|
||||
entrypoint = './functions/hello-world/index.js' # path must be relative to config.toml
|
||||
```
|
||||
|
||||
You can use any `.ts`, `.js`, `.tsx`, `.jsx` or `.mjs` file as the `entrypoint` for a Function.
|
||||
|
||||
### Error handling
|
||||
|
||||
The `supabase-js` library provides several error types that you can use to handle errors that might occur when invoking Edge Functions:
|
||||
|
||||
@@ -8,7 +8,7 @@ hideToc: true
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
|
||||
|
||||
<StepHikeCompact.Details title="Create a Supabase project">
|
||||
|
||||
Go to [database.new](https://database.new) and create a new Supabase project.
|
||||
@@ -118,7 +118,7 @@ hideToc: true
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
|
||||
export default async function Notes() {
|
||||
const supabase = createClient();
|
||||
const supabase = await createClient();
|
||||
const { data: notes } = await supabase.from("notes").select();
|
||||
|
||||
return <pre>{JSON.stringify(notes, null, 2)}</pre>
|
||||
@@ -129,8 +129,8 @@ hideToc: true
|
||||
import { createServerClient } from '@supabase/ssr'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export function createClient() {
|
||||
const cookieStore = cookies()
|
||||
export async function createClient() {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
return createServerClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
|
||||
@@ -53,18 +53,18 @@ hideToc: true
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
In `src/App.vue`, create a Supabase client using your project URL and public API (anon) key:
|
||||
In `app.vue`, create a Supabase client using your project URL and public API (anon) key:
|
||||
|
||||
<ProjectConfigVariables variable="url" />
|
||||
<ProjectConfigVariables variable="anonKey" />
|
||||
|
||||
Replace the existing content in your `src/App.vue` file with the following code.
|
||||
Replace the existing content in your `app.vue` file with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```vue src/App.vue
|
||||
```vue app.vue
|
||||
<script setup>
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
|
||||
@@ -108,3 +108,9 @@ hideToc: true
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
The community-maintained [@nuxtjs/supabase](https://supabase.nuxtjs.org/) module provides an alternate DX for working with Supabase in Nuxt.
|
||||
|
||||
</Admonition>
|
||||
@@ -106,7 +106,7 @@ hideToc: true
|
||||
|
||||
```svelte src/routes/+page.svelte
|
||||
<script>
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
|
||||
@@ -40,7 +40,7 @@ npx expo install @supabase/supabase-js @react-native-async-storage/async-storage
|
||||
Now let's create a helper file to initialize the Supabase client.
|
||||
We need the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
|
||||
These variables are safe to expose in your Expo app since Supabase has
|
||||
[Row Level Security](/docs/guides/auth#row-level-security) enabled on your Database.
|
||||
[Row Level Security](/docs/guides/database/postgres/row-level-security) enabled on your Database.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
|
||||
@@ -118,8 +118,8 @@ export function createClient() {
|
||||
import { createServerClient } from '@supabase/ssr'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export function createClient() {
|
||||
const cookieStore = cookies()
|
||||
export async function createClient() {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
// Create a server's supabase client with newly configured cookie,
|
||||
// which could be used to maintain user's session
|
||||
@@ -385,7 +385,7 @@ import { redirect } from 'next/navigation'
|
||||
import { createClient } from '@/utils/supabase/server'
|
||||
|
||||
export async function login(formData) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
// type-casting here for convenience
|
||||
// in practice, you should validate your inputs
|
||||
@@ -405,7 +405,7 @@ export async function login(formData) {
|
||||
}
|
||||
|
||||
export async function signup(formData) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
const data = {
|
||||
email: formData.get('email'),
|
||||
@@ -512,7 +512,7 @@ export async function GET(request) {
|
||||
redirectTo.searchParams.delete('type')
|
||||
|
||||
if (token_hash && type) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
type,
|
||||
@@ -720,7 +720,7 @@ import AccountForm from './account-form'
|
||||
import { createClient } from '@/utils/supabase/server'
|
||||
|
||||
export default async function Account() {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
@@ -763,7 +763,7 @@ import { revalidatePath } from 'next/cache'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function POST(req) {
|
||||
const supabase = createClient()
|
||||
const supabase = await createClient()
|
||||
|
||||
// Check if a user's logged in
|
||||
const {
|
||||
|
||||
@@ -147,9 +147,9 @@ postgresql://postgres:[DB-PASSWORD]@db.[REF].supabase.co:5432/postgres
|
||||
|
||||
Note that you cannot retrieve the database password via the management API, so for the user's existing projects you will need to collect their database password in your UI.
|
||||
|
||||
### Create new organizations and projects
|
||||
### Create new projects
|
||||
|
||||
If you don't need access to the user's existing project, we recommend creating a new project under a new organization. Use the [`/v1/organizations` endpoint](https://api.supabase.com/api/v1#/organizations/createOrganization) to create a new organization named after your integration, afterward use the [`/v1/projects` endpoint](https://api.supabase.com/api/v1#/projects/createProject) to create a new project.
|
||||
Use the [`/v1/projects` endpoint](https://api.supabase.com/api/v1#/projects/createProject) to create a new project.
|
||||
|
||||
When creating a new project, you can either ask the user to provide a database password, or you can generate a secure password for them. In any case, make sure to securely store the database password on your end which will allow you to construct the Postgres URI.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ sql_paths = ['./seeds/*.sql']
|
||||
|
||||
The CLI processes seed files in the order they are declared in the `sql_paths` array. If a glob pattern is used and matches multiple files, those files are sorted in lexicographic order to ensure consistent execution. Additionally:
|
||||
|
||||
- The base folder for the pattern matching is `supabase` so `./contries.sql` will search for `supabase/contries.sql`
|
||||
- The base folder for the pattern matching is `supabase` so `./countries.sql` will search for `supabase/countries.sql`
|
||||
- Files matched by multiple patterns will be deduplicated to prevent redundant seeding.
|
||||
- If a pattern does not match any files, a warning will be logged to help you troubleshoot potential configuration issues.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ description: 'Filter logs with regular expressions'
|
||||
|
||||
## Understanding field references
|
||||
|
||||
The log tables are queried with subset of BigQuery SQL syntax. They all have three columns: `event_message`, `timestamp`, and `metadata`.
|
||||
The log tables are queried with a subset of BigQuery SQL syntax. They all have three columns: `event_message`, `timestamp`, and `metadata`.
|
||||
|
||||
| column | description |
|
||||
| ------------- | --------------------------- |
|
||||
|
||||
@@ -148,9 +148,8 @@ Do not log Personal Identifiable Information (PII) within the `User-Agent` heade
|
||||
To enable query logs for other categories of statements:
|
||||
|
||||
1. [Enable the pgAudit extension](https://supabase.com/dashboard/project/_/database/extensions).
|
||||
2. [Restart your project](https://supabase.com/dashboard/project/_/settings/general) using the **Fast database reboot** option.
|
||||
3. Configure `pgaudit.log` (see below). Perform a fast reboot if needed.
|
||||
4. View your query logs under [Logs > Postgres Logs](https://supabase.com/dashboard/project/_/logs/postgres-logs).
|
||||
2. Configure `pgaudit.log` (see below). Perform a fast reboot if needed.
|
||||
3. View your query logs under [Logs > Postgres Logs](https://supabase.com/dashboard/project/_/logs/postgres-logs).
|
||||
|
||||
### Configuring `pgaudit.log`
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ Supabase Grafana only tracks metrics while it's active. For monitoring historica
|
||||
<StepHikeCompact.Step step={3}>
|
||||
|
||||
<StepHikeCompact.Details title="Add your credentials">
|
||||
- To monitor a single project, add your service_role key from the [API Settings](https://supabase.com/dashboard/project/_/settings/api) and Reference ID from the [General Settings](https://supabase.com/dashboard/project/_/settings/general).
|
||||
- To monitor a single project, add your service_role key from the [API Settings](https://supabase.com/dashboard/project/_/settings/api) and Project ID from the [General Settings](https://supabase.com/dashboard/project/_/settings/general).
|
||||
- To monitor multiple projects, create an [account access token](https://supabase.com/dashboard/account/tokens) and add it to your `.env` file.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
@@ -224,7 +224,7 @@ Supabase Grafana only tracks metrics while it's active. For monitoring historica
|
||||
|
||||
<StepHikeCompact.Step step={5} >
|
||||
<StepHikeCompact.Details title="Add your credentials and deploy">
|
||||
Add your service_role key from the [API Settings](https://supabase.com/dashboard/project/_/settings/api) and Reference ID from the [General Settings](https://supabase.com/dashboard/project/_/settings/general) to your Grafana credentials. Then install your dashboard.
|
||||
Add your service_role key from the [API Settings](https://supabase.com/dashboard/project/_/settings/api) and Project ID from the [General Settings](https://supabase.com/dashboard/project/_/settings/general) to your Grafana credentials. Then install your dashboard.
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||

|
||||
|
||||
@@ -3,14 +3,6 @@ title: 'Access Control'
|
||||
description: 'Roles and permissions at the organization and project levels'
|
||||
---
|
||||
|
||||
<Admonition type="warning" title="Breaking Changes to Project Permissions on October 15, 2024" className="mt-3">
|
||||
|
||||
Developer and Read-Only roles will no longer have access to certain resources and actions in your projects beginning October 15.
|
||||
|
||||
Refer to our [GitHub announcement](https://github.com/orgs/supabase/discussions/29494) for details on the exact breaking changes.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Supabase provides granular access controls to manage permissions across your organizations and projects.
|
||||
|
||||
For each organization and project, a member can have one of the following roles:
|
||||
@@ -101,7 +93,7 @@ The table below shows the actions each role can take on the resources belonging
|
||||
| Authorize GitHub | - | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| Add GitHub Repositories | - | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| GitHub Connections | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| Vercel Connections | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
@@ -123,7 +115,7 @@ The table below shows the actions each role can take on the resources belonging
|
||||
|
||||
The table below shows the actions each role can take on the resources belonging to the project.
|
||||
|
||||
| Resource | Action | Owner | Admin | Developer | Read-Only [^3] |
|
||||
| Resource | Action | Owner | Admin | Developer | Read-Only[^3][^5] |
|
||||
| ------------------------------------------------------------------------------------------------------ | ---------------------- | :-------------------------------------: | :-------------------------------------: | :-------------------------------------: | :-------------------------------------------------------------: |
|
||||
| <a href="#project-permissions" id="project-permissions">**Project**</a> | | | | | |
|
||||
| Project Management | Transfer | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
@@ -170,33 +162,33 @@ The table below shows the actions each role can take on the resources belonging
|
||||
| API Keys | Read service key | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Read anon key | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| JWT Secret | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Generate new | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Generate new | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| API settings | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| <a href="#auth-config-permissions" id="auth-config-permissions">**Auth Configuration**</a> | | | | | |
|
||||
| Auth Settings | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| SMTP Settings | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| Advanced Settings | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| <a href="#storage-config-permissions" id="storage-config-permissions">**Storage Configuration**</a> | | | | | |
|
||||
| Upload Limit | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| S3 Access Keys | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| <a href="#edge-config-permissions" id="edge-config-permissions">**Edge Functions Configuration**</a> | | | | | |
|
||||
| Secrets | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck className="inline" size={14} color="#3FCF8E" /> [^4] |
|
||||
| | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| <a href="#sql-editor-permissions" id="sql-editor-permissions">**SQL Editor**</a> | | | | | |
|
||||
| Queries | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | List | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Run | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck className="inline" size={14} color="#3FCF8E" /> [^5] |
|
||||
| | Run | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck className="inline" size={14} color="#3FCF8E" /> [^6] |
|
||||
| <a href="#database-permissions" id="database-permissions">**Database**</a> | | | | | |
|
||||
| Scheduled Backups | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Download | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
@@ -212,13 +204,13 @@ The table below shows the actions each role can take on the resources belonging
|
||||
| | Send magic link | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Remove MFA factors | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| Providers | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| Rate Limits | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| Email Templates | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| URL Configuration | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| Hooks | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
@@ -250,13 +242,13 @@ The table below shows the actions each role can take on the resources belonging
|
||||
| | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | List | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | Run | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| Events Collections | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| Events Collections | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | Update | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | Delete | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | View | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| | List | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> |
|
||||
| Warehouse Access Tokens | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| | Revoke | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| Warehouse Access Tokens | Create | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | Revoke | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
| | List | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> |
|
||||
| <a href="#branching-permissions" id="branching-permissions">**Branching**</a> | | | | | |
|
||||
| Enable branching | - | <IconCheck size={14} color="#3FCF8E" /> | <IconCheck size={14} color="#3FCF8E" /> | <IconX size={14} /> | <IconX size={14} /> |
|
||||
@@ -269,4 +261,5 @@ The table below shows the actions each role can take on the resources belonging
|
||||
[^2]: Invites sent from a SSO account can only be accepted by another SSO account coming from the same identity provider. This is a security measure that prevents accidental invites to accounts not managed by your company's enterprise systems.
|
||||
[^3]: Available on the Enterprise Plan.
|
||||
[^4]: Read-Only role is able to access secrets.
|
||||
[^5]: Limited to executing SELECT queries.
|
||||
[^5]: Listed permissions are for the API and Dashboard.
|
||||
[^6]: Limited to executing SELECT queries. SQL Query Snippets run by the Read-Only role are run against the database using the **supabase_read_only_user**. This role has the [predefined Postgres role pg_read_all_data](https://www.postgresql.org/docs/current/predefined-roles.html).
|
||||
@@ -154,6 +154,10 @@ Any changes made to your billing details will only be reflected in your upcoming
|
||||
|
||||
We accept credit card payments only. If you cannot pay via credit card, we do offer alternatives for larger upfront payments. Please create a [support ticket](https://supabase.help) in case you’re interested.
|
||||
|
||||
#### What credit card brands are supported?
|
||||
|
||||
Visa, Mastercard, American Express, Japan Credit Bureau (JCB), China UnionPay (CUP), Cartes Bancaires
|
||||
|
||||
#### What currency can I pay in?
|
||||
|
||||
All our invoices are issued in USD, but you can pay in any currency as long as your credit card provider allows charging in USD after conversion.
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
---
|
||||
id: 'compute-add-ons'
|
||||
title: 'Compute Add-ons'
|
||||
description: "Learn about your project's instance and additional add-ons."
|
||||
---
|
||||
|
||||
Every project on the Supabase Platform comes with its own dedicated Postgres instance running inside a virtual machine (VM).
|
||||
|
||||
The following table describes the base instances, Nano (free projects) and Micro (paying projects), with additional compute add-ons available if you need extra performance when scaling up Supabase.
|
||||
|
||||
<Admonition type="note" label="Upgrade to Micro Instances at No Additional Charge for Paid Projects">
|
||||
|
||||
If you upgraded your Supabase organization from the Free plan to a paid plan, any projects created on the Free plan will remain on Nano instances due to the upgrade process [incurring downtime](/docs/guides/platform/compute-add-ons#upgrade-downtime).
|
||||
|
||||
Since every project on a paid plan is already paying for a Micro instance you can upgrade to Micro at no additional charge. See [Supabase Pricing](https://supabase.com/pricing) for more information.
|
||||
|
||||
We recommend that you take advantage of the upgrade when your usage requires Micro resources because the upgrade incurs downtime.
|
||||
|
||||
</Admonition>
|
||||
|
||||
| Compute Size | Hourly Price USD | Monthly Price USD | CPU | Memory | Database Max Connections[^1] | Connection Pooler Max Clients | Max DB Size (Recommended)[^2] |
|
||||
| ------------ | ---------------- | ----------------- | ----------------------- | ------------ | ---------------------------- | ----------------------------- | ----------------------------- |
|
||||
| Nano[^3] | $0 | $0 | Shared | Up to 0.5 GB | 60 | 200 | 500MB |
|
||||
| Micro | $0.01344 | ~$10 | 2-core ARM (shared) | 1 GB | 60 | 200 | 10GB |
|
||||
| Small | $0.0206 | ~$15 | 2-core ARM (shared) | 2 GB | 90 | 400 | 50GB |
|
||||
| Medium | $0.0822 | ~$60 | 2-core ARM (shared) | 4 GB | 120 | 600 | 100GB |
|
||||
| Large | $0.1517 | ~$110 | 2-core ARM (dedicated) | 8 GB | 160 | 800 | 200GB |
|
||||
| XL | $0.2877 | ~$210 | 4-core ARM (dedicated) | 16 GB | 240 | 1,000 | 500GB |
|
||||
| 2XL | $0.562 | ~$410 | 8-core ARM (dedicated) | 32 GB | 380 | 1,500 | 1TB |
|
||||
| 4XL | $1.32 | ~$960 | 16-core ARM (dedicated) | 64 GB | 480 | 3,000 | 2TB |
|
||||
| 8XL | $2.562 | ~$1,870 | 32-core ARM (dedicated) | 128 GB | 490 | 6,000 | 4TB |
|
||||
| 12XL | $3.836 | ~$2,800 | 48-core ARM (dedicated) | 192 GB | 500 | 9,000 | 6TB |
|
||||
| 16XL | $5.12 | ~$3,730 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 | 10TB |
|
||||
|
||||
[^1]: Database max connections are recommended values and can be customized depending on your use case.
|
||||
[^2]: Database size for each compute instance is the default recommendation but the actual performance of your database has many contributing factors, including resources available to it and the size of the data contained within it. See the [shared responsibility model](https://supabase.com/docs/guides/platform/shared-responsibility-model) for more information.
|
||||
[^3]: Nano compute resources on the Free plan are subject to change.
|
||||
|
||||
Compute sizes can be changed by first selecting your project in the dashboard [here](https://supabase.com/dashboard/project/_/settings/addons?panel=computeInstance) and the upgrade process will [incur downtime](/docs/guides/platform/compute-add-ons#upgrade-downtime).
|
||||
|
||||
We charge hourly for additional compute based on your usage. Read more about [usage-based billing for compute](/docs/guides/platform/org-based-billing#billing-for-compute-compute-hours).
|
||||
|
||||
## Dedicated vs. shared CPU
|
||||
|
||||
All Postgres instances on Supabase are dedicated applications running inside dedicated virtual machines. However, the underlying hardware resources, for example the physical CPU, may be shared between multiple VMs, but appear to the OS as if it is a dedicated hardware CPU. This is commonly referred to as a vCPU (virtual CPU). Cloud providers use these shared hardware resources to save cost—you can upgrade to a larger compute add-on to guarantee a dedicated physical CPU for your instance.
|
||||
|
||||
## Compute upgrades [#upgrades]
|
||||
|
||||
When considering compute upgrades, assess whether your bottlenecks are hardware-constrained or software-constrained. For example, you may want to look into [optimizing the number of connections](/docs/guides/platform/performance#optimizing-the-number-of-connections) or [examining query performance](/docs/guides/platform/performance#examining-query-performance). When you're happy with your Postgres instance's performance, then you can focus on additional compute resources. For example, you can load test your application in staging to understand your compute requirements. You can also start out on a smaller tier, [create a report](https://supabase.com/dashboard/project/_/reports) in the Dashboard to monitor your CPU utilization, and upgrade later as needed
|
||||
|
||||
## Disk IO
|
||||
|
||||
SSD disks are attached to your servers, and their performance depends on the compute add-on of your instance as well as the attributes set for the disk.
|
||||
|
||||
Disk I/O Metrics:
|
||||
|
||||
- Throughput: Measured in Megabits per Second (Mbps), which indicates the rate of data transfer.
|
||||
- IOPS: Input/Output Operations per Second, which measures the number of read and write operations.
|
||||
|
||||
Performance Limits:
|
||||
|
||||
Disk throughput is capped at 19,000 Mbps and IOPS is capped at 80,000 IOPS for all compute add-ons.
|
||||
|
||||
| Compute add-on | Max Disk Throughput | Baseline Disk Throughput | Max IOPS | Baseline IOPS |
|
||||
| -------------- | ------------------- | ------------------------ | ----------- | ------------- |
|
||||
| Nano (free) | 2,085 Mbps | 43 Mbps | 11,800 IOPS | 250 IOPS |
|
||||
| Micro | 2,085 Mbps | 87 Mbps | 11,800 IOPS | 500 IOPS |
|
||||
| Small | 2,085 Mbps | 174 Mbps | 11,800 IOPS | 1,000 IOPS |
|
||||
| Medium | 2,085 Mbps | 347 Mbps | 11,800 IOPS | 2,000 IOPS |
|
||||
| Large | 4,750 Mbps | 630 Mbps | 20,000 IOPS | 3,600 IOPS |
|
||||
| XL | 4,750 Mbps | 1,188 Mbps | 20,000 IOPS | 6,000 IOPS |
|
||||
| 2XL | 4,750 Mbps | 2,375 Mbps | 20,000 IOPS | 12,000 IOPS |
|
||||
| 4XL | 4,750 Mbps | 4,750 Mbps | 20,000 IOPS | 20,000 IOPS |
|
||||
| 8XL | 9,500 Mbps | 9,500 Mbps | 40,000 IOPS | 40,000 IOPS |
|
||||
| 12XL | 14,250 Mbps | 14,250 Mbps | 50,000 IOPS | 50,000 IOPS |
|
||||
| 16XL | 19,000 Mbps | 19,000 Mbps | 80,000 IOPS | 80,000 IOPS |
|
||||
|
||||
### Self-Serve Configurability
|
||||
|
||||
You can now adjust storage type, IOPS, throughput and disk size parameters yourself through the Dashboard [here](https://supabase.com/dashboard/project/_/settings/database).
|
||||
|
||||

|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Be aware that increasing IOPS or throughput incur additional charges.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Understanding I/O Balance and Bursting (Smaller add-ons)
|
||||
|
||||
For smaller add-ons like Nano, Micro, and Small, you get I/O balance and burst capacity. These add-ons have limited base performance for IOPS and throughput. However, they come with burst credits that let you handle temporary spikes for 30 minutes in a day in demand by briefly exceeding their baseline performance. This means you can achieve higher performance for short bursts before reverting to your baseline limits.
|
||||
|
||||
For example, the micro add-on can burst up to 2,085 Mbps for 30 minutes a day and reverts to the baseline performance of 87 Mbps. Your disk IO budget gets replenished throughout the day.
|
||||
|
||||
### Dedicated I/O Resources (Larger add-ons)
|
||||
|
||||
Larger add-ons, such as 4XL and above, come with dedicated I/O resources and don't rely on I/O balance or burst capacity. These add-ons are designed for sustained, high performance with specific IOPS and throughput limits. They provide consistent performance without needing to burst. If you hit your IOPS or throughput limit, throttling will occur, but you won't have burst credits to exceed these limits.
|
||||
|
||||
### Storage Volume Types: gp3 vs. io2
|
||||
|
||||
When selecting your storage volume, it’s essential to focus on the performance needs of your workload. Below is a comparison between the two main volume types we offer:
|
||||
|
||||
**gp3 (General Purpose SSD) - Default Option**
|
||||
This is our standard, versatile option and offers great value for most use cases. It’s ideal for general workloads that require moderate IOPS and throughput, such as small to medium-sized databases, development environments, and everyday tasks.
|
||||
|
||||
- Volume Size: Up to 16TB
|
||||
- IOPS: 3,000 (default) up to 16,000 (maximum)
|
||||
- Throughput: 125 MB/s (default) to 1,000 MB/s (maximum)
|
||||
|
||||
**io2 (Provisioned IOPS SSD)**
|
||||
This option is designed for high-performance needs where low latency and very high IOPS are crucial. If you’re running large-scale databases or mission-critical applications, io2 provides the performance you need.
|
||||
|
||||
- Volume Size: Up to 60TB
|
||||
- IOPS: Can scale up to 256,000 based on your disk attributes
|
||||
- Throughput: Scales with IOPS
|
||||
|
||||
**Choosing the Right Type**
|
||||
Think about the performance your workload demands. For general, day-to-day operations, gp3 should be more than enough. If you need high throughput and IOPS for critical systems, io2 will provide the performance required.
|
||||
|
||||
### Choosing the Right Compute Add-On for Consistent Disk Performance
|
||||
|
||||
If you need consistent disk performance, choose the 4XL or larger compute add-on which has the same baseline and maximum disk throughput and IOPS.
|
||||
|
||||
If you're unsure of how much throughput or IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://supabase.com/dashboard/project/_/reports). If the `Disk IO % consumed` stat is more than 1%, it indicates that your workload has burst beyond the baseline IO throughput during the day. If this metric goes to 100%, the workload has used up all available disk IO budget and will revert to baseline performance. Projects that use any disk IO budget are good candidates for upgrading to a larger compute add-on with higher baseline throughput.
|
||||
|
||||
<Admonition type="information">
|
||||
|
||||
Compute add-on changes will not cause you to lose your disk attributes.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Constraints
|
||||
|
||||
- After **any** disk attribute change, there is a cooldown period of approximately six hours before you can make further adjustments. During this time, no changes are allowed. If you encounter throttling, you’ll need to wait until the cooldown period concludes before making additional modifications.
|
||||
- You can increase volume size but cannot decrease it.
|
||||
- The performance of EBS volumes is affected by the add-on size to which you are attached.
|
||||
|
||||
## Postgres replication slots and WAL senders
|
||||
|
||||
[Replication Slots](https://postgresqlco.nf/doc/en/param/max_replication_slots) and [WAL Senders](https://postgresqlco.nf/doc/en/param/max_wal_senders/) are used to enable [Postgres Replication](/docs/guides/database/replication).
|
||||
|
||||
The maximum number of replication slots and WAL senders depends on your compute add-on size, as follows:
|
||||
|
||||
| Compute add-on | Max Replication Slots | Max WAL Senders |
|
||||
| -------------- | --------------------- | --------------- |
|
||||
| Nano (free) | 5 | 5 |
|
||||
| Micro | 5 | 5 |
|
||||
| Small | 5 | 5 |
|
||||
| Medium | 5 | 5 |
|
||||
| Large | 8 | 8 |
|
||||
| XL | 24 | 24 |
|
||||
| 2XL | 80 | 80 |
|
||||
| 4XL | 80 | 80 |
|
||||
| 8XL | 80 | 80 |
|
||||
| 12XL | 80 | 80 |
|
||||
| 16XL | 80 | 80 |
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
As mentioned in the Postgres [documentation](https://postgresqlco.nf/doc/en/param/max_replication_slots/), setting `max_replication_slots` to a lower value than the current number of replication slots will prevent the server from starting. If you are downgrading your compute add-on, please ensure that you are using fewer slots than the maximum number of replication slots available for the new compute add-on.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Upgrade downtime
|
||||
|
||||
Compute add-on changes are usually applied with less than 2 minutes of downtime, but can take longer depending on the Cloud Provider.
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
id: 'compute-and-disk'
|
||||
title: 'Compute and Disk'
|
||||
description: "Learn about your project's compute and disk sizing options."
|
||||
---
|
||||
|
||||
## Compute
|
||||
|
||||
Every project on the Supabase Platform comes with its own dedicated Postgres instance.
|
||||
|
||||
The following table describes the base instances, Nano (free plan) and Micro (paid plans), with additional compute instance sizes available if you need extra performance when scaling up.
|
||||
|
||||
<Admonition type="note" label="Upgrade to Micro Instances at No Additional Charge for Paid Projects">
|
||||
|
||||
If you upgraded your Supabase organization from the Free plan to a paid plan, any projects created on the Free plan will remain on Nano instances due to the upgrade process [incurring downtime](/docs/guides/platform/compute-and-disk#upgrade-downtime).
|
||||
|
||||
Since every project on a paid plan is already paying for a Micro instance you can upgrade to Micro at no additional charge. See [Supabase Pricing](https://supabase.com/pricing) for more information.
|
||||
|
||||
</Admonition>
|
||||
|
||||
| Compute Size | Hourly Price USD | Monthly Price USD | CPU | Memory | Max DB Size (Recommended)[^2] |
|
||||
| ------------ | ---------------- | ----------------- | ----------------------- | ------------ | ----------------------------- |
|
||||
| Nano[^3] | $0 | $0 | Shared | Up to 0.5 GB | 500 MB |
|
||||
| Micro | $0.01344 | ~$10 | 2-core ARM (shared) | 1 GB | 10 GB |
|
||||
| Small | $0.0206 | ~$15 | 2-core ARM (shared) | 2 GB | 50 GB |
|
||||
| Medium | $0.0822 | ~$60 | 2-core ARM (shared) | 4 GB | 100 GB |
|
||||
| Large | $0.1517 | ~$110 | 2-core ARM (dedicated) | 8 GB | 200 GB |
|
||||
| XL | $0.2877 | ~$210 | 4-core ARM (dedicated) | 16 GB | 500 GB |
|
||||
| 2XL | $0.562 | ~$410 | 8-core ARM (dedicated) | 32 GB | 1 TB |
|
||||
| 4XL | $1.32 | ~$960 | 16-core ARM (dedicated) | 64 GB | 2 TB |
|
||||
| 8XL | $2.562 | ~$1,870 | 32-core ARM (dedicated) | 128 GB | 4 TB |
|
||||
| 12XL | $3.836 | ~$2,800 | 48-core ARM (dedicated) | 192 GB | 6 TB |
|
||||
| 16XL | $5.12 | ~$3,730 | 64-core ARM (dedicated) | 256 GB | 10 TB |
|
||||
|
||||
[^1]: Database max connections are recommended values and can be customized depending on your use case.
|
||||
[^2]: Database size for each compute instance is the default recommendation but the actual performance of your database has many contributing factors, including resources available to it and the size of the data contained within it. See the [shared responsibility model](https://supabase.com/docs/guides/platform/shared-responsibility-model) for more information.
|
||||
[^3]: Compute resources on the Free plan are subject to change.
|
||||
|
||||
Compute sizes can be changed by first selecting your project in the dashboard [here](https://supabase.com/dashboard/project/_/settings/compute-and-disk) and the upgrade process will [incur downtime](/docs/guides/platform/compute-and-disk#upgrade-downtime).
|
||||
|
||||
We charge hourly for additional compute based on your usage. Read more about [usage-based billing for compute](/docs/guides/platform/org-based-billing#billing-for-compute-compute-hours).
|
||||
|
||||
### Dedicated vs shared CPU
|
||||
|
||||
All Postgres databases on Supabase run in isolated environments. Compute instances smaller than `Large` compute size have CPUs which can burst to higher performance levels for short periods of time. Instances bigger than `Large` have predictable performance levels and do not exhibit the same burst behavior.
|
||||
|
||||
### Compute upgrades [#upgrades]
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Compute instance changes are usually applied with less than 2 minutes of downtime, but can take longer depending on the underlying Cloud Provider.
|
||||
|
||||
</Admonition>
|
||||
|
||||
When considering compute upgrades, assess whether your bottlenecks are hardware-constrained or software-constrained. For example, you may want to look into [optimizing the number of connections](/docs/guides/platform/performance#optimizing-the-number-of-connections) or [examining query performance](/docs/guides/platform/performance#examining-query-performance). When you're happy with your Postgres instance's performance, then you can focus on additional compute resources. For example, you can load test your application in staging to understand your compute requirements. You can also start out on a smaller tier, [create a report](https://supabase.com/dashboard/project/_/reports) in the Dashboard to monitor your CPU utilization, and upgrade as needed.
|
||||
|
||||
## Disk
|
||||
|
||||
Supabase databases are backed by high performance SSD disks. The _effective performance_ depends on a combination of all the following factors:
|
||||
|
||||
- Compute size
|
||||
- Provisioned Disk Throughput
|
||||
- Provisioned Disk IOPS: Input/Output Operations per Second, which measures the number of read and write operations.
|
||||
- Disk type: io2 or gp3
|
||||
- Disk size
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
The disk size and the disk type dictate the maximum IOPS and throughput that can be provisioned. The effective IOPS is the lower of the IOPS supported by the compute size or the provisioned IOPS of the disk. Similarly, the effective throughout is the lower of the throughput supported by the compute size and the provisioned throughput of the disk.
|
||||
|
||||
</Admonition>
|
||||
|
||||
The following sections explain how these attributes affect disk performance.
|
||||
|
||||
### Compute size
|
||||
|
||||
The compute size of your project sets the upper limit for disk throughput and IOPS. The table below shows the limits for each instance size. For instance, an 8XL compute instance has a maximum throughput of 9,500 MiB/s and a maximum IOPS of 40,000.
|
||||
|
||||
| Compute Instance | Disk Throughput | IOPS |
|
||||
| ---------------- | --------------- | ----------- |
|
||||
| Nano (free) | 43 MiB/s | 250 IOPS |
|
||||
| Micro | 87 MiB/s | 500 IOPS |
|
||||
| Small | 174 MiB/s | 1,000 IOPS |
|
||||
| Medium | 347 MiB/s | 2,000 IOPS |
|
||||
| Large | 630 MiB/s | 3,600 IOPS |
|
||||
| XL | 1,188 MiB/s | 6,000 IOPS |
|
||||
| 2XL | 2,375 MiB/s | 12,000 IOPS |
|
||||
| 4XL | 4,750 MiB/s | 20,000 IOPS |
|
||||
| 8XL | 9,500 MiB/s | 40,000 IOPS |
|
||||
| 12XL | 14,250 MiB/s | 50,000 IOPS |
|
||||
| 16XL | 19,000 MiB/s | 80,000 IOPS |
|
||||
|
||||
Smaller compute instances like Nano, Micro, and Small have baseline performance levels that can occasionally be exceeded. Larger compute-instances (4XL and above) are designed for sustained, high performance with specific IOPS and throughput limits. If you hit your IOPS or throughput limit, throttling will occur.
|
||||
|
||||
### Choosing the right compute instance for consistent disk performance
|
||||
|
||||
If you need consistent disk performance, choose the 4XL or larger compute instance. If you're unsure of how much throughput or IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://supabase.com/dashboard/project/_/reports). If the `Disk IO % consumed` stat is more than 1%, it indicates that your workload has exceeded the baseline IO throughput during the day. If this metric goes to 100%, the workload has used up all available disk IO budget. Projects that use any disk IO budget are good candidates for upgrading to a larger compute instance with higher throughput.
|
||||
|
||||
### Provisioned Disk throughput and IOPS
|
||||
|
||||
The default disk type is gp3, which comes with a baseline throughput of 125 MiB/s and a default IOPS of 3,000. You can provision additional IOPS and throughput from the [Database Settings](/dashboard/project/_/settings/database) page, but keep in mind that the effective IOPS and throughput will be limited by the compute instance size.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Be aware that increasing IOPS or throughput incurs additional charges.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Disk types
|
||||
|
||||
When selecting your disk, it's essential to focus on the performance needs of your workload. Here's a comparison of our available disk types:
|
||||
|
||||
| | General Purpose SSD (gp3) | High Performance SSD (io2) |
|
||||
| ----------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| **Use Case** | General workloads, development environments, small to medium databases | High-performance needs, large-scale databases, mission-critical applications |
|
||||
| **Max Disk Size** | 16 TB | 60 TB |
|
||||
| **Max IOPS** | 16,000 IOPS (at 32 GB disk size) | 80,000 IOPS (at 80 GB disk size) |
|
||||
| **Throughput** | 125 MiB/s (default) to 1,000 MiB/s (maximum) | Automatically scales with IOPS |
|
||||
| **Best For** | Great value for most use cases | Low latency and very high IOPS requirements |
|
||||
|
||||
For general, day-to-day operations, gp3 should be more than enough. If you need high throughput and IOPS for critical systems, io2 will provide the performance required.
|
||||
|
||||
<Admonition type="information">
|
||||
|
||||
Compute instance size changes will not change your selected disk type or disk size, but your IO limits may change according to what your selected compute instance size supports.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Disk size
|
||||
|
||||
- General Purpose (gp3) disks come with a baseline of 3,000 IOPS and 125 MiB/s. You can provision additional 500 IOPS for every GB of disk size and additional 0.25 MiB/s throughput per provisioned IOPS.
|
||||
- High Performance (io2) disks can be provisioned with 1,000 IOPS per GB of disk size.
|
||||
|
||||
## Limits and Constraints
|
||||
|
||||
### Postgres replication slots, WAL senders, and connections
|
||||
|
||||
[Replication Slots](https://postgresqlco.nf/doc/en/param/max_replication_slots) and [WAL Senders](https://postgresqlco.nf/doc/en/param/max_wal_senders/) are used to enable [Postgres Replication](/docs/guides/database/replication). Each compute instance also has limits on the maximum number of database connections and connection pooler clients it can handle.
|
||||
|
||||
The maximum number of replication slots, WAL senders, database connections, and pooler clients depends on your compute instance size, as follows:
|
||||
|
||||
| Compute instance | Max Replication Slots | Max WAL Senders | Database Max Connections[^1] | Connection Pooler Max Clients |
|
||||
| ---------------- | --------------------- | --------------- | ---------------------------- | ----------------------------- |
|
||||
| Nano (free) | 5 | 5 | 60 | 200 |
|
||||
| Micro | 5 | 5 | 60 | 200 |
|
||||
| Small | 5 | 5 | 90 | 400 |
|
||||
| Medium | 5 | 5 | 120 | 600 |
|
||||
| Large | 8 | 8 | 160 | 800 |
|
||||
| XL | 24 | 24 | 240 | 1,000 |
|
||||
| 2XL | 80 | 80 | 380 | 1,500 |
|
||||
| 4XL | 80 | 80 | 480 | 3,000 |
|
||||
| 8XL | 80 | 80 | 490 | 6,000 |
|
||||
| 12XL | 80 | 80 | 500 | 9,000 |
|
||||
| 16XL | 80 | 80 | 500 | 12,000 |
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
As mentioned in the Postgres [documentation](https://postgresqlco.nf/doc/en/param/max_replication_slots/), setting `max_replication_slots` to a lower value than the current number of replication slots will prevent the server from starting. If you are downgrading your compute instance, ensure that you are using fewer slots than the maximum number of replication slots available for the new compute instance.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Constraints
|
||||
|
||||
- After **any** disk attribute change, there is a cooldown period of approximately six hours before you can make further adjustments. During this time, no changes are allowed. If you encounter throttling, you’ll need to wait until the cooldown period concludes before making additional modifications.
|
||||
- You can increase disk size but cannot decrease it.
|
||||
@@ -1,19 +1,18 @@
|
||||
---
|
||||
id: 'database-size'
|
||||
title: 'Database size'
|
||||
id: 'disk-metrics'
|
||||
title: 'Understanding Database and Disk Size'
|
||||
description: 'Understanding how database size applies to your subscription.'
|
||||
---
|
||||
|
||||
Database size refers to the _monthly average storage usage_, as reported by Postgres. This metric is reported in your organization's [billing usage](https://supabase.com/dashboard/org/_/usage) and is updated daily. As you read this document we will refer to "database size" and "disk size":
|
||||
Disk metrics refer to the storage usage reported by Postgres. These metrics are updated daily. As you read through this document, we will refer to "database size" and "disk size":
|
||||
|
||||
- "Database size" is the total size of used storage from your database.
|
||||
- "Disk Storage size" describes the size of the underlying available storage.
|
||||
- _Database size_: Displays the actual size of the data within your Postgres database. This can be found on the [Database Reports page](https://supabase.com/dashboard/project/_/reports/database).
|
||||
|
||||
## Database space management
|
||||
- _Disk size_: Shows the overall disk space usage, which includes both the database size and additional files required for Postgres to function like the Write Ahead Log (WAL) and other system log files. You can view this on the [Database Settings page](https://supabase.com/dashboard/project/_/settings/database).
|
||||
|
||||
### Database size
|
||||
## Database size
|
||||
|
||||
This SQL query will show the current size of your Postgres database:
|
||||
This SQL query will show the size of all databases in your Postgres cluster:
|
||||
|
||||
```sql
|
||||
select
|
||||
@@ -23,7 +22,7 @@ from pg_database;
|
||||
|
||||
This value is reported in the [database report page](https://supabase.com/dashboard/project/_/reports/database).
|
||||
|
||||
Database Space is consumed primarily by your data, indexes, and materialized views. You can reduce your disk size by removing any of these and running a Vacuum operation.
|
||||
Database size is consumed primarily by your data, indexes, and materialized views. You can reduce your database size by removing any of these and running a Vacuum operation.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
@@ -69,39 +68,37 @@ Running a manual vacuum after deleting large amounts of data from your DB could
|
||||
|
||||
New Supabase projects have a database size of ~40-60mb. This space includes pre-installed extensions, schemas, and default Postgres data. Additional database size is used when installing extensions, even if those extensions are inactive.
|
||||
|
||||
## Disk management
|
||||
## Disk size
|
||||
|
||||
Supabase uses network-attached storage to balance performance with scalability. The behavior of your disk depends on your billing plan.
|
||||
Supabase uses network-attached storage to balance performance with scalability. The disk scaling behavior depends on your billing plan.
|
||||
|
||||
### Paid plan behavior
|
||||
|
||||
Projects on the Pro Plan and above have auto-scaling Disk Storage.
|
||||
Projects on the Pro Plan and higher have auto-scaling disks.
|
||||
|
||||
Disk Storage expands automatically when the database reaches 90% of the disk size. The disk is expanded to be 50% larger (e.g., 8GB -> 12GB). Auto-scaling can only take place once every 6 hours. If within those 6 hours you reach 95% of the disk space, your project will enter read-only mode.
|
||||
Disk size expands automatically when the database reaches 90% of the allocated disk size. The disk is expanded to be 50% larger (for example, 8 GB -> 12 GB). Auto-scaling can only take place once every 6 hours. If within those 6 hours you reach 95% of the disk space, your project will enter read-only mode.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
The automatic resize operation will add an additional 50% capped to a maximum of 200GB. If 50% of your current usage is more than 200GB then only 200GB will be added to your disk (e.g. a size of 1500GB will resize to 1700GB).
|
||||
The automatic resize operation will add an additional 50% capped to a maximum of 200 GB. If 50% of your current usage is more than 200 GB then only 200 GB will be added to your disk (for example a size of 1500 GB will resize to 1700 GB).
|
||||
|
||||
</Admonition>
|
||||
|
||||
The Disk Storage Size can also be manually expanded on the [Database settings page](https://supabase.com/dashboard/project/_/settings/database). The maximum default you can expand the Disk Storage to is 200GB. If you wish to manually expand it to any more than 200GB, please [contact us](https://supabase.com/dashboard/support/new) to discuss expanding the 200GB limit.
|
||||
Disk size can also be manually expanded on the [Database settings page](https://supabase.com/dashboard/project/_/settings/database). The maximum disk size for the Pro/Team Plan is 60 TB. If you need more than this, [contact us](https://forms.supabase.com/enterprise) to learn more about the Enterprise Plan.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
You may want to import a lot of data into your database which requires multiple disk expansions; for example, uploading more than 1.5x the current size of your database storage will put your database into [read-only mode](#read-only-mode). If so, it is highly recommended you increase the Disk Storage Size manually on the [Database settings page](https://supabase.com/dashboard/project/_/settings/database).
|
||||
You may want to import a lot of data into your database which requires multiple disk expansions. for example, uploading more than 1.5x the current size of your database storage will put your database into [read-only mode](#read-only-mode). If so, it is highly recommended you increase the disk size manually on the [Database settings page](https://supabase.com/dashboard/project/_/settings/database).
|
||||
|
||||
Due to provider restrictions, disk expansions can occur only once every six hours. During the six-hour window, the disk cannot be resized again.
|
||||
Due to restrictions on the underlying cloud provider, disk expansions can occur only once every six hours. During the six hour cool down window, the disk cannot be resized again.
|
||||
|
||||
</Admonition>
|
||||
|
||||
The maximum Disk Storage Size for the Pro/Team Plan is 60TB. If you need more than this, [contact us](https://forms.supabase.com/enterprise) to learn more about the Enterprise Plan.
|
||||
|
||||
### Free Plan behavior
|
||||
|
||||
Free Plan projects enter [read-only](#read-only-mode) mode when you exceed the 500MB limit. Once in read-only mode, you have several options:
|
||||
Free Plan projects enter [read-only](#read-only-mode) mode when you exceed the 500 MB limit. Once in read-only mode, you have these options:
|
||||
|
||||
- [Upgrade to the Pro Plan](https://supabase.com/dashboard/org/_/billing) to increase the limit to 8GB. [Disable the Spend Cap](https://app.supabase.com/org/_/billing?panel=costControl) if you want your Pro instance to auto-scale and expand beyond the 8GB database size limit.
|
||||
- [Upgrade to the Pro Plan](https://supabase.com/dashboard/org/_/billing) to increase the limit to 8 GB. [Disable the Spend Cap](https://app.supabase.com/org/_/billing?panel=costControl) if you want your Pro instance to auto-scale beyond the 8 GB disk size limit.
|
||||
- [Disable read-only mode](#disabling-read-only-mode) and reduce your database size.
|
||||
|
||||
### Read-only mode
|
||||
@@ -134,6 +131,6 @@ set default_transaction_read_only = 'off';
|
||||
|
||||
### Reducing disk size
|
||||
|
||||
Disks don't automatically downsize during normal operation. Once you have [reduced your database space usage](/docs/guides/platform/database-size#database-space-management), they _will_ automatically "right-size" when a Supabase (or Postgres) update is applied. Their final size after the update is 1.2x the size of the database (min 8 GB). For example, if your database is 100GB in size, and you have a 200GB disk, the size after a Supabase update will be 120GB.
|
||||
Disks don't automatically downsize during normal operation. Once you have [reduced your database size](/docs/guides/platform/database-size#database-size), they _will_ automatically "right-size" during a [project upgrade](/docs/guides/platform/upgrading). The final disk size after the upgrade is 1.2x the size of the database with a minimum of 8 GB. For example, if your database size is 100GB, and you have a 200GB disk, the size after a project upgrade will be 120 GB.
|
||||
|
||||
Learn more about [upgrading your project](/docs/guides/platform/migrating-and-upgrading-projects#upgrade-your-project).
|
||||
In the event that your project is already on the latest version of Postgres and cannot be upgraded, a new version of Postgres will be released approximately every week which you can then upgrade to once it becomes available.
|
||||
@@ -11,7 +11,13 @@ Learn how to migrate to Supabase from another database service.
|
||||
<div className="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-6 mb-6 not-prose">
|
||||
{migrationPages.map((page) => (
|
||||
<Link href={`${page.url}`} key={page.url} passHref>
|
||||
<GlassPanel title={page.name} background={false} className="[&>div]:p-4" />
|
||||
<GlassPanel
|
||||
icon={page.icon}
|
||||
title={page.name}
|
||||
hasLightIcon={page.hasLightIcon}
|
||||
background={false}
|
||||
className="[&>div]:p-4"
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: 'Migrate from Neon to Supabase'
|
||||
description: 'Migrate your existing Neon database to Supabase.'
|
||||
subtitle: 'Migrate your existing Neon database to Supabase.'
|
||||
---
|
||||
|
||||
This guide demonstrates how to migrate your Neon database to Supabase to get the most out of Postgres while gaining access to all the features you need to build a project.
|
||||
|
||||
## Retrieve your Neon database credentials [#retrieve-credentials]
|
||||
|
||||
1. Log in to your Neon Console [https://console.neon.tech/login](https://console.neon.tech/login).
|
||||
1. Select **Projects** on the left.
|
||||
1. Click on your project in the list.
|
||||
1. From your Project Dashboard find your **Connection string** and click **Copy snippet** to copy it to the clipboard (do not check "pooled connection").
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
postgresql://neondb_owner:xxxxxxxxxxxxxxx-random-word-yyyyyyyy.us-west-2.aws.neon.tech/neondb?sslmode=require
|
||||
```
|
||||
|
||||
## Set your OLD_DB_URL environment variable
|
||||
|
||||
Set the **OLD_DB_URL** environment variable at the command line using your Neon database credentials from the clipboard.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
export OLD_DB_URL="postgresql://neondb_owner:xxxxxxxxxxxxxxx-random-word-yyyyyyyy.us-west-2.aws.neon.tech/neondb?sslmode=require"
|
||||
```
|
||||
|
||||
## Retrieve your Supabase connection string [#retrieve-supabase-connection-string]
|
||||
|
||||
1. If you're new to Supabase, [create a project](https://supabase.com/dashboard).
|
||||
Make a note of your password, you will need this later. If you forget it, you can [reset it here](https://supabase.com/dashboard/project/_/settings/database).
|
||||
|
||||
1. Go to the [Database settings](https://supabase.com/dashboard/project/_/settings/database) for your project in the Supabase Dashboard.
|
||||
1. Under **Connection string**, select **URI**, make sure **Display connection pooler** is checked, and **Mode: Session** is set.
|
||||
1. Click the **Copy** button to the right of your connection string to copy it to the clipboard.
|
||||
|
||||
## Set your NEW_DB_URL environment variable
|
||||
|
||||
Set the **NEW_DB_URL** environment variable at the command line using your Supabase connection string. You will need to replace `[YOUR-PASSWORD]` with your actual database password.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
export NEW_DB_URL="postgresql://postgres.xxxxxxxxxxxxxxxxxxxx:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres"
|
||||
```
|
||||
|
||||
## Migrate the database
|
||||
|
||||
You will need the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) command line tools, which are included in a full [PostgreSQL installation](https://www.postgresql.org/download).
|
||||
|
||||
1. Export your database to a file in console
|
||||
|
||||
Use `pg_dump` with your Postgres credentials to export your database to a file (e.g., `dump.sql`).
|
||||
|
||||
```bash
|
||||
pg_dump "$OLD_DB_URL" \
|
||||
--clean \
|
||||
--if-exists \
|
||||
--quote-all-identifiers \
|
||||
--no-owner \
|
||||
--no-privileges \
|
||||
> dump.sql
|
||||
```
|
||||
|
||||
2. Import the database to your Supabase project
|
||||
|
||||
Use `psql` to import the Postgres database file to your Supabase project.
|
||||
|
||||
```bash
|
||||
psql -d "$NEW_DB_URL" -f dump.sql
|
||||
```
|
||||
|
||||
Additional options
|
||||
|
||||
- To only migrate a single database schema, add the `--schema=PATTERN` parameter to your `pg_dump` command.
|
||||
- To exclude a schema: `--exclude-schema=PATTERN`.
|
||||
- To only migrate a single table: `--table=PATTERN`.
|
||||
- To exclude a table: `--exclude-table=PATTERN`.
|
||||
|
||||
Run `pg_dump --help` for a full list of options.
|
||||
|
||||
<MigrationWarnings />
|
||||
|
||||
## Enterprise
|
||||
|
||||
[Contact us](https://forms.supabase.com/enterprise) if you need more help migrating your project.
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: 'Migrate from Vercel Postgres to Supabase'
|
||||
description: 'Migrate your existing Vercel Postgres database to Supabase.'
|
||||
subtitle: 'Migrate your existing Vercel Postgres database to Supabase.'
|
||||
---
|
||||
|
||||
This guide demonstrates how to migrate your Vercel Postgres database to Supabase to get the most out of Postgres while gaining access to all the features you need to build a project.
|
||||
|
||||
## Retrieve your Vercel Postgres database credentials [#retrieve-credentials]
|
||||
|
||||
1. Log in to your Vercel Dashboard [https://vercel.com/login](https://vercel.com/login).
|
||||
1. Click on the **Storage** tab.
|
||||
1. Click on your Postgres Database.
|
||||
1. Under the **Quickstart** section, select **psql** then click **Show Secret** to reveal your database password.
|
||||
1. Copy the string after `psql ` to the clipboard.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
psql "postgres://default:xxxxxxxxxxxx@yy-yyyyy-yyyyyy-yyyyyyy.us-west-2.aws.neon.tech:5432/verceldb?sslmode=require"
|
||||
```
|
||||
|
||||
Copy this part to your clipboard:
|
||||
|
||||
```bash
|
||||
"postgres://default:xxxxxxxxxxxx@yy-yyyyy-yyyyyy-yyyyyyy.us-west-2.aws.neon.tech:5432/verceldb?sslmode=require"
|
||||
```
|
||||
|
||||
## Set your OLD_DB_URL environment variable
|
||||
|
||||
Set the **OLD_DB_URL** environment variable at the command line using your Vercel Postgres Database credentials.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
export OLD_DB_URL="postgres://default:xxxxxxxxxxxx@yy-yyyyy-yyyyyy-yyyyyyy.us-west-2.aws.neon.tech:5432/verceldb?sslmode=require"
|
||||
```
|
||||
|
||||
## Retrieve your Supabase connection string [#retrieve-supabase-connection-string]
|
||||
|
||||
1. If you're new to Supabase, [create a project](https://supabase.com/dashboard).
|
||||
Make a note of your password, you will need this later. If you forget it, you can [reset it here](https://supabase.com/dashboard/project/_/settings/database).
|
||||
|
||||
1. Go to the [Database settings](https://supabase.com/dashboard/project/_/settings/database) for your project in the Supabase Dashboard.
|
||||
1. Under **Connection string**, select **URI**, make sure **Display connection pooler** is checked, and **Mode: Session** is set.
|
||||
1. Click the **Copy** button to the right of your connection string to copy it to the clipboard.
|
||||
|
||||
## Set your NEW_DB_URL environment variable
|
||||
|
||||
Set the **NEW_DB_URL** environment variable at the command line using your Supabase connection string. You will need to replace `[YOUR-PASSWORD]` with your actual database password.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
export NEW_DB_URL="postgresql://postgres.xxxxxxxxxxxxxxxxxxxx:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres"
|
||||
```
|
||||
|
||||
## Migrate the database
|
||||
|
||||
You will need the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) command line tools, which are included in a full [PostgreSQL installation](https://www.postgresql.org/download).
|
||||
|
||||
1. Export your database to a file in console
|
||||
|
||||
Use `pg_dump` with your Postgres credentials to export your database to a file (e.g., `dump.sql`).
|
||||
|
||||
```bash
|
||||
pg_dump "$OLD_DB_URL" \
|
||||
--clean \
|
||||
--if-exists \
|
||||
--quote-all-identifiers \
|
||||
--no-owner \
|
||||
--no-privileges \
|
||||
> dump.sql
|
||||
```
|
||||
|
||||
2. Import the database to your Supabase project
|
||||
|
||||
Use `psql` to import the Postgres database file to your Supabase project.
|
||||
|
||||
```bash
|
||||
psql -d "$NEW_DB_URL" -f dump.sql
|
||||
```
|
||||
|
||||
Additional options
|
||||
|
||||
- To only migrate a single database schema, add the `--schema=PATTERN` parameter to your `pg_dump` command.
|
||||
- To exclude a schema: `--exclude-schema=PATTERN`.
|
||||
- To only migrate a single table: `--table=PATTERN`.
|
||||
- To exclude a table: `--exclude-table=PATTERN`.
|
||||
|
||||
Run `pg_dump --help` for a full list of options.
|
||||
|
||||
<MigrationWarnings />
|
||||
|
||||
## Enterprise
|
||||
|
||||
[Contact us](https://forms.supabase.com/enterprise) if you need more help migrating your project.
|
||||
@@ -102,6 +102,65 @@ To call a read-only Postgres function on Read Replicas through the REST API, use
|
||||
|
||||
If you remove all Read Replicas from your project, the load balancer and its endpoint are removed as well. Make sure to redirect requests back to your Primary database before removal.
|
||||
|
||||
#### Experimental routing
|
||||
|
||||
The API load balancer offers an experimental routing mode that builds on top of the existing functionality. It is now possible to use a load balancer endpoint for all Supabase services (Auth, Edge Functions, Realtime, and Storage) meaning there is no need to worry about which endpoint to use in which situations.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Due to the requirements of the Auth service, all Auth requests are handled by the Primary, even when sent over the load balancer endpoint. This is similar to how non-Read requests for the Data API (PostgREST) are exclusively handled by the Primary.
|
||||
|
||||
</Admonition>
|
||||
|
||||
The experimental routing mode can be enabled by sending the header `sb-lb-routing-mode: alpha-all-services` along with requests. This gives the option to opt-in and try out the new routing, but still have the peace of mind that the original behavior is there should it be needed.
|
||||
|
||||
What follows is an example of using the `supabase-js` library to create a new client with the appropriate headers and make an Auth request to create a new user.
|
||||
|
||||
```ts ./create-user.ts
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import * as dotenv from 'dotenv'
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config()
|
||||
|
||||
// Supabase credentials
|
||||
const supabaseUrl = process.env.SUPABASE_URL as string
|
||||
const supabaseKey = process.env.SUPABASE_KEY as string
|
||||
|
||||
// Initialize the Supabase client with custom headers
|
||||
const supabase = createClient(supabaseUrl, supabaseKey, {
|
||||
global: {
|
||||
headers: {
|
||||
'sb-lb-routing-mode': 'alpha-all-services',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* @description simple user creation function
|
||||
*/
|
||||
async function createUser() {
|
||||
// Make the Auth call to create a user
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email: 'my-new-user@example.com',
|
||||
password: 'my--really-strong-password',
|
||||
})
|
||||
|
||||
// Throw on an error
|
||||
if (error) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
|
||||
// Output the response
|
||||
console.dir(data)
|
||||
}
|
||||
|
||||
// Register a random user
|
||||
createUser()
|
||||
```
|
||||
|
||||
The client creation function, with experimental routing header, can be used throughout an application to take advantage of load balancer experimental routing.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
If you use a [custom domain](/docs/guides/platform/custom-domains), requests will not be routed through the load balancer. You should instead use the dedicated endpoints provided in the dashboard.
|
||||
|
||||
@@ -31,14 +31,18 @@ To get started:
|
||||
You can use the `get` subcommand of the CLI to check whether SSL is currently being enforced:
|
||||
|
||||
```bash
|
||||
> supabase ssl-enforcement --project-ref {ref} get --experimental
|
||||
supabase ssl-enforcement --project-ref {ref} get --experimental
|
||||
```
|
||||
|
||||
Response if SSL is being enforced:
|
||||
|
||||
```bash
|
||||
SSL is being enforced.
|
||||
```
|
||||
|
||||
Or similarly, if SSL is not being enforced, you will see:
|
||||
Response if SSL is not being enforced:
|
||||
|
||||
```bash
|
||||
> supabase ssl-enforcement --project-ref {ref} get --experimental
|
||||
SSL is *NOT* being enforced.
|
||||
```
|
||||
|
||||
@@ -47,15 +51,13 @@ SSL is *NOT* being enforced.
|
||||
The `update` subcommand is used to change the SSL enforcement status for your project:
|
||||
|
||||
```bash
|
||||
> supabase ssl-enforcement --project-ref {ref} update --enable-db-ssl-enforcement --experimental
|
||||
SSL is now being enforced.
|
||||
supabase ssl-enforcement --project-ref {ref} update --enable-db-ssl-enforcement --experimental
|
||||
```
|
||||
|
||||
Similarly, to disable SSL enforcement:
|
||||
|
||||
```bash
|
||||
> supabase ssl-enforcement --project-ref {ref} update --disable-db-ssl-enforcement --experimental
|
||||
SSL is *NOT* being enforced.
|
||||
supabase ssl-enforcement --project-ref {ref} update --disable-db-ssl-enforcement --experimental
|
||||
```
|
||||
|
||||
### A note about Postgres SSL modes
|
||||
@@ -67,11 +69,11 @@ The strongest mode offered by Postgres is `verify-full` and this is the mode you
|
||||
Once the CA certificate has been downloaded, add it to the certificate authority list used by Postgres.
|
||||
|
||||
```bash
|
||||
> cat {location of downloaded prod-ca-2021.crt} >> ~/.postgres/root.crt
|
||||
cat {location of downloaded prod-ca-2021.crt} >> ~/.postgres/root.crt
|
||||
```
|
||||
|
||||
With the CA certificate added to the trusted certificate authorities list, use `psql` or your client library to connect to Supabase:
|
||||
|
||||
```bash
|
||||
> psql "postgresql://aws-0-eu-central-1.pooler.supabase.com:6543/postgres?sslmode=verify-full" -U postgres.<user>
|
||||
psql "postgresql://aws-0-eu-central-1.pooler.supabase.com:6543/postgres?sslmode=verify-full" -U postgres.<user>
|
||||
```
|
||||
@@ -99,7 +99,7 @@ If you upgrade to a paid plan while your project is paused, any expired one-clic
|
||||
|
||||
If the 90 day project restore window has expired but you need to access data contained within your project using SQL, you can attempt to restore the project into a local Postgres instance. Supabase publishes tooling that can be used for that purpose. Be aware that this workflow does not produce a complete Supabase environment with REST/Auth/Storage. Instead, it creates a standalone Postgres instance that is maximally compatible with your project's backup file to assist with recovering your data.
|
||||
|
||||
Downloaded backups are plain text SQL files that can be executed in Postgres to restore your project's schema and data. If you need you access data within a backup we recommend restoring into a Postgres instance built using the Supabase open source build tooling to ensure that the local instance is as similar to the original project as possible. Here are the steps to start Supabase Postgres locally and restore the backup:
|
||||
Downloaded backups are plain text SQL files that can be executed in Postgres to restore your project's schema and data. If you need to access data within a backup we recommend restoring into a Postgres instance built using the Supabase open source build tooling to ensure that the local instance is as similar to the original project as possible. Here are the steps to start Supabase Postgres locally and restore the backup:
|
||||
|
||||
Install [nix](https://nixos.org/):
|
||||
|
||||
@@ -123,7 +123,7 @@ nix run 'github:supabase/postgres?ref=15.6.1.115#start-server' 15
|
||||
|
||||
where `15` refers to the backup's Postgres major version.
|
||||
|
||||
Note that the earliest Supabase Postgres version that supports a direct reference is `15.6.1.115`. If you don't know your roject's Postgres version or it is lower than `15.6.1.115` then omit the `ref` option to build our most recent image:
|
||||
Note that the earliest Supabase Postgres version that supports a direct reference is `15.6.1.115`. If you don't know your project's Postgres version or it is lower than `15.6.1.115` then omit the `ref` option to build our most recent image:
|
||||
|
||||
```sh
|
||||
nix run 'github:supabase/postgres#start-server' -- 15
|
||||
|
||||
@@ -10,28 +10,24 @@ There are several ways to host Supabase on your own computer, server, or cloud.
|
||||
## Officially supported
|
||||
|
||||
<div className="grid md:grid-cols-12 gap-4 not-prose">
|
||||
{official.map((x) => (
|
||||
<div className="md:col-span-6 xl:col-span-3" key={x.href}>
|
||||
<Link href={x.href} passHref>
|
||||
<GlassPanel title={x.name}>{x.description}</GlassPanel>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="md:col-span-6 xl:col-span-3 relative" key="/guides/self-hosting/docker">
|
||||
<span className=" absolute left-28 top-[34px] uppercase text-xs whitespace-nowrap text-foreground-lighter font-mono z-10 border rounded-full px-2 py-0.5">
|
||||
Most common
|
||||
</span>
|
||||
<Link href="/guides/self-hosting/docker" passHref>
|
||||
<GlassPanel title="Docker">
|
||||
|
||||
export const official = [
|
||||
{
|
||||
name: 'Docker',
|
||||
description: 'Deploy Supabase within your own infrastructure using Docker Compose.',
|
||||
href: '/guides/self-hosting/docker',
|
||||
},
|
||||
{
|
||||
name: 'BYO Cloud',
|
||||
description:
|
||||
'Contact our Enterprise sales team if you need Supabase managed in your own cloud.',
|
||||
href: '/pricing',
|
||||
},
|
||||
]
|
||||
Deploy Supabase within your own infrastructure using Docker Compose.
|
||||
</GlassPanel>
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
<div className="md:col-span-6 xl:col-span-3" key="/pricing">
|
||||
<Link href="https://supabase.com/pricing" passHref>
|
||||
<GlassPanel title="BYO Cloud">Contact our Enterprise sales team if you need Supabase managed in your own cloud.</GlassPanel>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Supabase is also a hosted platform. If you want to get started for free, visit [supabase.com/dashboard](https://supabase.com/dashboard).
|
||||
|
||||
|
||||
@@ -7,15 +7,23 @@ tocVideo: "FqiQKRKsfZE"
|
||||
|
||||
|
||||
|
||||
Docker is the easiest way to get started with self-hosted Supabase. This guide assumes you are running the command from the machine you intend to host from.
|
||||
Docker is the easiest way to get started with self-hosted Supabase. It should only take you a few minutes to get up and running. This guide assumes you are running the command from the machine you intend to host from.
|
||||
|
||||
## Contents
|
||||
1. [Before you begin](#before-you-begin)
|
||||
2. [Installing and running Supabase](#installing-and-running-supabase)
|
||||
3. [Accessing your services](#accessing-supabase-studio)
|
||||
4. [Updating your services](#updating-your-services)
|
||||
5. [Securing your services](#securing-your-services)
|
||||
|
||||
|
||||
## Before you begin
|
||||
|
||||
You need the following installed in your system: [Git](https://git-scm.com/downloads) and Docker ([Windows](https://docs.docker.com/desktop/install/windows-install/), [MacOS](https://docs.docker.com/desktop/install/mac-install/), or [Linux](https://docs.docker.com/desktop/install/linux-install/)).
|
||||
|
||||
## Running Supabase
|
||||
## Installing and running Supabase
|
||||
|
||||
Follow these steps to start Supabase locally:
|
||||
Follow these steps to start Supabase on your machine:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -67,21 +75,37 @@ docker compose up -d
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
|
||||
<Admonition>
|
||||
|
||||
If you are using rootless docker, edit `.env` and set `DOCKER_SOCKET_LOCATION` to your docker socket location. For example: `/run/user/1000/docker.sock`. Otherwise, you will see an error like `container supabase-vector exited (0)`.
|
||||
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
|
||||
After all the services have started you can see them running in the background:
|
||||
|
||||
```sh
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
All of the services should have a status `running (healthy)`. If you see a status like `created` but not `running`, try starting that service manually with `docker compose start <service-name>`.
|
||||
|
||||
|
||||
<Admonition type="danger">
|
||||
|
||||
Your app is now running with default credentials.
|
||||
Please [secure your services](#securing-your-services) as soon as possible using the instructions below.
|
||||
|
||||
For security reasons, we "pin" the versions of each service in the docker-compose file (these versions are updated ~monthly). If you want to update any services immediately, you can do so by updating the version number in the docker compose file and then running `docker compose pull`. You can find all the latest docker images in the [Supabase Docker Hub](https://hub.docker.com/u/supabase).
|
||||
|
||||
### Accessing Supabase dashboard
|
||||
</Admonition>
|
||||
|
||||
You can access the Supabase Dashboard through the API gateway on port `8000`. For example: `http://<your-ip>:8000`, or [localhost:8000](http://localhost:8000) if you are running Docker locally.
|
||||
|
||||
### Accessing Supabase Studio
|
||||
|
||||
You can access Supabase Studio through the API gateway on port `8000`. For example: `http://<your-ip>:8000`, or [localhost:8000](http://localhost:8000) if you are running Docker locally.
|
||||
|
||||
You will be prompted for a username and password. By default, the credentials are:
|
||||
|
||||
@@ -107,30 +131,56 @@ You can add new Functions as `volumes/functions/<FUNCTION_NAME>/index.ts`. Resta
|
||||
|
||||
### Accessing Postgres
|
||||
|
||||
You can connect to the Postgres database locally on port `5432`. For example, if you have `psql` on your local machine you can run:
|
||||
By default, the Supabase stack runs the [Supavisor](https://supabase.github.io/supavisor/development/docs/) connection pooler. Supavisor provides efficient management of database connections.
|
||||
|
||||
You can connect to the Postgres database using the following methods:
|
||||
|
||||
1. For session-based connections (equivalent to direct PostgreSQL connections):
|
||||
|
||||
```bash
|
||||
psql -h 127.0.0.1 -p 5432 -d postgres -U postgres
|
||||
psql 'postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@localhost:5432/postgres'
|
||||
```
|
||||
|
||||
The default password is `your-super-secret-and-long-postgres-password`. You should change this as soon as possible using the [instructions below](#update-secrets). By default the database is not accessible from outside the local machine. You can [change this](#exposing-your-postgres-database) by updating the `docker-compose.yml` file.
|
||||
2. For pooled transactional connections:
|
||||
|
||||
```bash
|
||||
psql 'postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@localhost:6543/postgres'
|
||||
```
|
||||
|
||||
The default tenant ID is `your-tenant-id`, and the default password is `your-super-secret-and-long-postgres-password`. You should change these as soon as possible using the [instructions below](#update-secrets).
|
||||
|
||||
By default, the database is not accessible from outside the local machine but the pooler is. You can [change this](#exposing-your-postgres-database) by updating the `docker-compose.yml` file.
|
||||
|
||||
## Updating your services
|
||||
For security reasons, we "pin" the versions of each service in the docker-compose file (these versions are updated ~monthly). If you want to update any services immediately, you can do so by updating the version number in the docker compose file and then running `docker compose pull`. You can find all the latest docker images in the [Supabase Docker Hub](https://hub.docker.com/u/supabase).
|
||||
|
||||
You should update your services frequently to get the latest features and bug fixes and security patches. Note that you will need to restart the services to pick up the changes, which will result in some downtime for your services.
|
||||
|
||||
**Example**
|
||||
You'll want to update the Studio(Dashboard) frequently to get the latest features and bug fixes. To update the Dashboard:
|
||||
1. Visit the [supabase/studio](https://hub.docker.com/r/supabase/studio/tags) image in the [Supabase Docker Hub](https://hub.docker.com/u/supabase)
|
||||
2. Find the latest version (tag) number. It will look something like `20241029-46e1e40`
|
||||
3. Update the `image` field in the `docker-compose.yml` file to the new version. It should look like this: `image: supabase/studio:20241028-a265374`
|
||||
4. Run `docker compose pull` and then `docker compose up -d` to restart the service with the new version.
|
||||
|
||||
## Securing your services
|
||||
|
||||
While we provided you with some example secrets for getting started, you should NEVER deploy your Supabase setup using the defaults we have provided. Please follow all of the steps in this section to ensure you have a secure setup, and then [restart all services](#restarting-all-services) to pick up the changes.
|
||||
|
||||
|
||||
### Generate API keys
|
||||
We need to generate secure keys for accessing your services. We'll use the `JWT Secret` to generate `anon` and `service` API keys using the form below.
|
||||
|
||||
Create a new `JWT_SECRET` and store it securely.
|
||||
1. **Obtain a Secret**: Use the 40-character secret provided, or create your own. If creating, ensure it's a strong, random string of 40 characters.
|
||||
2. **Store Securely**: Save the secret in a secure location on your local machine. Don't share this secret publicly or commit it to version control.
|
||||
3. **Generate a JWT**: Use the form below to generate a new `JWT` using your secret.
|
||||
|
||||
We can use your JWT Secret to generate new `anon` and `service` API keys using the form below. Update the "JWT Secret" and then run "Generate JWT" once for the `SERVICE_KEY` and once for the `ANON_KEY`:
|
||||
|
||||
<JwtGenerator />
|
||||
|
||||
### Update API keys
|
||||
|
||||
Replace the values in the `.env` file:
|
||||
Run this form twice to generate new `anon` and `service` API keys. Replace the values in the `./docker/.env` file:
|
||||
|
||||
- `ANON_KEY` - replace with an `anon` key
|
||||
- `SERVICE_ROLE_KEY` - replace with a `service` key
|
||||
@@ -139,23 +189,24 @@ You will need to [restart](#restarting-all-services) the services for the change
|
||||
|
||||
### Update secrets
|
||||
|
||||
Update the `.env` file with your own secrets. In particular, these are required:
|
||||
Update the `./docker/.env` file with your own secrets. In particular, these are required:
|
||||
|
||||
- `POSTGRES_PASSWORD`: the password for the `postgres` role.
|
||||
- `JWT_SECRET`: used by PostgREST and GoTrue, among others.
|
||||
- `SITE_URL`: the base URL of your site.
|
||||
- `SMTP_*`: mail server credentials. You can use any SMTP server.
|
||||
- `POOLER_TENANT_ID`: the tenant-id that will be used by supavisor pooler for your connection string
|
||||
|
||||
You will need to [restart](#restarting-all-services) the services for the changes to take effect.
|
||||
|
||||
### Dashboard authentication
|
||||
|
||||
The dashboard is protected with Basic Authentication. The default user and password MUST be updated before using Supabase in production.
|
||||
Update the following values in the `.env` file:
|
||||
The Dashboard is protected with basic authentication. The default user and password MUST be updated before using Supabase in production.
|
||||
Update the following values in the `./docker/.env` file:
|
||||
- `DASHBOARD_USERNAME`: The default username for the Dashboard
|
||||
- `DASHBOARD_PASSWORD`: The default password for the Dashboard
|
||||
|
||||
You can also add more credentials in `./docker/volumes/api/kong.yml`. For example:
|
||||
You can also add more credentials for multiple users in `./docker/volumes/api/kong.yml`. For example:
|
||||
|
||||
```yaml docker/volumes/api/kong.yml
|
||||
basicauth_credentials:
|
||||
@@ -242,7 +293,7 @@ If the tool doesn't exist, we build and open source it ourselves.
|
||||
- [PostgreSQL](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
|
||||
- [Supavisor](https://github.com/supabase/supavisor) is a scalable connection pooler for Postgres, allowing for efficient management of database connections.
|
||||
|
||||
For the system to work cohesively, some services require additional configuration within the Postgres database. For example, the APIs and Auth system require several [default roles](/docs/guides/database/postgres/roles) and the `pgjwt` Postgres extension.
|
||||
For the system to work cohesively, some services require additional configuration within the Postgres database. For example, the APIs and Auth system require several [default roles](/docs/guides/database/postgres/roles) and the `pgjwt` Postgres extension.
|
||||
|
||||
You can find all the default extensions inside the [schema migration scripts repo](https://github.com/supabase/postgres/tree/develop/migrations). These scripts are mounted at `/docker-entrypoint-initdb.d` to run automatically when starting the database container.
|
||||
|
||||
@@ -253,12 +304,12 @@ Each system has a number of configuration options which can be found in the rele
|
||||
- [Postgres](https://hub.docker.com/_/postgres/)
|
||||
- [PostgREST](https://postgrest.org/en/stable/configuration.html)
|
||||
- [Realtime](https://github.com/supabase/realtime#server)
|
||||
- [GoTrue](https://github.com/supabase/gotrue)
|
||||
- [Auth](https://github.com/supabase/auth)
|
||||
- [Storage](https://github.com/supabase/storage-api)
|
||||
- [Kong](https://docs.konghq.com/gateway/latest/install/docker/)
|
||||
- [Supavisor](https://supabase.github.io/supavisor/development/docs/)
|
||||
|
||||
These configuration items are generally added to the `env` section of each service, inside the `docker-compose.yml` section. If these configuration items are sensitive, they should be stored in a [secret manager](/docs/guides/self-hosting#managing-your-secrets) or using an `.env` file and then referenced using the `${}` syntax.
|
||||
These configuration items are generally added to the `env` section of each service, inside the `docker-compose.yml` section. If these configuration items are sensitive, they should be stored in a [secret manager](/docs/guides/self-hosting#managing-your-secrets) or using an `.env` file and then referenced using the `${}` syntax.
|
||||
|
||||
|
||||
<CH.Code>
|
||||
@@ -282,7 +333,7 @@ services:
|
||||
|
||||
Each system can be [configured](../self-hosting#configuration) independently. Some of the most common configuration options are listed below.
|
||||
|
||||
#### Configuring an email server
|
||||
#### Configuring an email server
|
||||
|
||||
You will need to use a production-ready SMTP server for sending emails. You can configure the SMTP server by updating the following environment variables:
|
||||
|
||||
@@ -297,7 +348,7 @@ SMTP_SENDER_NAME=
|
||||
|
||||
We recommend using [AWS SES](https://aws.amazon.com/ses/). It's extremely cheap and reliable. Restart all services to pick up the new configuration.
|
||||
|
||||
#### Configuring S3 Storage
|
||||
#### Configuring S3 Storage
|
||||
|
||||
By default all files are stored locally on the server. You can configure the Storage service to use S3 by updating the following environment variables:
|
||||
|
||||
@@ -329,7 +380,7 @@ If you need direct access to the Postgres database without going through Supavis
|
||||
# Comment or remove the supavisor section of the docker-compose file
|
||||
# supavisor:
|
||||
# ports:
|
||||
# ...
|
||||
# ...
|
||||
db:
|
||||
ports:
|
||||
- ${POSTGRES_PORT}:${POSTGRES_PORT}
|
||||
@@ -383,5 +434,10 @@ A minimal setup working on Ubuntu, hosted on Digital Ocean.
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
### Demo using Digital Ocean
|
||||
1. A Digital Ocean Droplet with 1 GB memory and 25 GB solid-state drive (SSD) is sufficient to start
|
||||
2. To access the Dashboard, use the ipv4 IP address of your Droplet.
|
||||
3. If you're unable to access Dashboard, run `docker compose ps` to see if the Studio service is running and healthy.
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,100 @@ Supabase Storage implements the [TUS protocol](https://tus.io/) to enable resuma
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="react" label="React">
|
||||
|
||||
Here's an example of how to upload a file using `@uppy/tus` with react:
|
||||
|
||||
```javascript
|
||||
import { useEffect, useState } from "react";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import Uppy from "@uppy/core";
|
||||
import Tus from "@uppy/tus";
|
||||
import Dashboard from "@uppy/dashboard";
|
||||
import "@uppy/core/dist/style.min.css";
|
||||
import "@uppy/dashboard/dist/style.min.css";
|
||||
|
||||
function App() {
|
||||
// Initialize Uppy instance with the 'sample' bucket specified for uploads
|
||||
const uppy = useUppyWithSupabase({ bucketName: "sample" });
|
||||
|
||||
useEffect(() => {
|
||||
// Set up Uppy Dashboard to display as an inline component within a specified target
|
||||
uppy.use(Dashboard, {
|
||||
inline: true, // Ensures the dashboard is rendered inline
|
||||
target: "#drag-drop-area", // HTML element where the dashboard renders
|
||||
showProgressDetails: true, // Show progress details for file uploads
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div id="drag-drop-area">
|
||||
</div>
|
||||
{/* Target element for the Uppy Dashboard */}
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
/**
|
||||
* Custom hook for configuring Uppy with Supabase authentication and TUS resumable uploads
|
||||
* @param {Object} options - Configuration options for the Uppy instance.
|
||||
* @param {string} options.bucketName - The bucket name in Supabase where files are stored.
|
||||
* @returns {Object} uppy - Uppy instance with configured upload settings.
|
||||
*/
|
||||
export const useUppyWithSupabase = ({ bucketName }: { bucketName: string }) => {
|
||||
// Initialize Uppy instance only once
|
||||
const [uppy] = useState(() => new Uppy());
|
||||
// Initialize Supabase client with project URL and anon key
|
||||
const supabase = createClient(projectURL, anonKey);
|
||||
|
||||
useEffect(() => {
|
||||
const initializeUppy = async () => {
|
||||
// Retrieve the current user's session for authentication
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
|
||||
uppy.use(Tus, {
|
||||
endpoint: `${projectURL}/storage/v1/upload/resumable`, // Supabase TUS endpoint
|
||||
retryDelays: [0, 3000, 5000, 10000, 20000], // Retry delays for resumable uploads
|
||||
headers: {
|
||||
authorization: `Bearer ${session?.access_token}`, // User session access token
|
||||
apikey: anonKey, // API key for Supabase
|
||||
},
|
||||
uploadDataDuringCreation: true, // Send metadata with file chunks
|
||||
removeFingerprintOnSuccess: true, // Remove fingerprint after successful upload
|
||||
chunkSize: 6 * 1024 * 1024, // Chunk size for TUS uploads (6MB)
|
||||
allowedMetaFields: [
|
||||
"bucketName",
|
||||
"objectName",
|
||||
"contentType",
|
||||
"cacheControl",
|
||||
], // Metadata fields allowed for the upload
|
||||
onError: (error) => console.error("Upload error:", error), // Error handling for uploads
|
||||
}).on("file-added", (file) => {
|
||||
// Attach metadata to each file, including bucket name and content type
|
||||
file.meta = {
|
||||
...file.meta,
|
||||
bucketName, // Bucket specified by the user of the hook
|
||||
objectName: file.name, // Use file name as object name
|
||||
contentType: file.type, // Set content type based on file MIME type
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize Uppy with Supabase settings
|
||||
initializeUppy();
|
||||
}, [uppy, bucketName]);
|
||||
|
||||
// Return the configured Uppy instance
|
||||
return uppy;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="kotlin" label="Kotlin">
|
||||
|
||||
Kotlin supports resumable uploads natively for all targets:
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
---
|
||||
title = "How to monitor Postgres and Supavisor connections"
|
||||
topics = [
|
||||
"database",
|
||||
"supavisor",
|
||||
]
|
||||
keywords = [
|
||||
"Grafana",
|
||||
"connections",
|
||||
"performance",
|
||||
"pooler",
|
||||
]
|
||||
github_url = "https://github.com/orgs/supabase/discussions/27141"
|
||||
topics = [ "database", "supavisor" ]
|
||||
keywords = [ "Grafana", "connections", "performance", "pooler" ]
|
||||
date_created = "2024-06-09"
|
||||
database_id = "bdd39473-ae02-41f5-b75d-877a79f361f6"
|
||||
---
|
||||
|
||||
This guide explains how connections impact your Supabase database's performance and how to optimize them for better resource utilization.
|
||||
|
||||
Loaded 100 of 902 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user