merge master into feat/reports-storage

This commit is contained in:
Francesco Sansalvadore committed 2025-06-30 12:05:22 +02:00
commit b1d8cdc2c2
527 files changed
+31797 -5561

No files matched your search

@@ -0,0 +1,59 @@
# Documentation Embeddings Generation System
## Overview
The documentation embeddings generation system processes various documentation sources and uploads their metadata to a database for semantic search functionality. The system is located in `apps/docs/scripts/search/` and works by:
1. **Discovering content sources** from multiple types of documentation
2. **Processing content** into structured sections with checksums
3. **Generating embeddings** using OpenAI's text-embedding-ada-002 model
4. **Storing in database** with vector embeddings for semantic search
## Architecture
### Main Entry Point
- `generate-embeddings.ts` - Main script that orchestrates the entire process
- Supports `--refresh` flag to force regeneration of all content
### Content Sources (`sources/` directory)
#### Base Classes
- `BaseLoader` - Abstract class for loading content from different sources
- `BaseSource` - Abstract class for processing and formatting content
#### Source Types
1. **Markdown Sources** (`markdown.ts`)
- Processes `.mdx` files from guides and documentation
- Extracts frontmatter metadata and content sections
2. **Reference Documentation** (`reference-doc.ts`)
- **OpenAPI References** - Management API documentation from OpenAPI specs
- **Client Library References** - JavaScript, Dart, Python, C#, Swift, Kotlin SDKs
- **CLI References** - Command-line interface documentation
- Processes YAML/JSON specs and matches with common sections
3. **GitHub Discussions** (`github-discussion.ts`)
- Fetches troubleshooting discussions from GitHub using GraphQL API
- Uses GitHub App authentication for access
4. **Partner Integrations** (`partner-integrations.ts`)
- Fetches approved partner integration documentation from Supabase database
- Technology integrations only (excludes agencies)
### Processing Flow
1. **Content Discovery**: Each source loader discovers and loads content files/data
2. **Content Processing**: Each source processes content into:
- Checksum for change detection
- Metadata (title, subtitle, etc.)
- Sections with headings and content
3. **Change Detection**: Compares checksums against existing database records
4. **Embedding Generation**: Uses OpenAI to generate embeddings for new/changed content
5. **Database Storage**: Stores in `page` and `page_section` tables with embeddings
6. **Cleanup**: Removes outdated pages using version tracking
### Database Schema
- **`page`** table: Stores page metadata, content, checksum, version
- **`page_section`** table: Stores individual sections with embeddings, token counts
+2 -2
View File
@@ -1,6 +1,6 @@
/packages/ui/ @supabase/design
/packages/shared-data/pricing.ts @roryw10 @kevcodez
/packages/shared-data/plans.ts @roryw10 @kevcodez
/packages/shared-data/pricing.ts @roryw10 @supabase/billing
/packages/shared-data/plans.ts @roryw10 @supabase/billing
/packages/common/telemetry-constants.ts @4L3k51 @supabase/growth-eng
/apps/studio/ @supabase/Dashboard
+1 -1
View File
@@ -1 +1 @@
20
22
@@ -84,6 +84,20 @@ type CLICommandReference implements SearchResult {
content: String
}
"""
A reference document containing a description of a Supabase Management API endpoint
"""
type ManagementApiReference implements SearchResult {
"""The title of the document"""
title: String
"""The URL of the document"""
href: String
"""The content of the reference document, as text"""
content: String
}
"""
A reference document containing a description of a function from a Supabase client library
"""
+100 -15
View File
@@ -9,9 +9,59 @@ import { rootGraphQLSchema } from '~/resources/rootSchema'
import { createQueryDepthLimiter } from './validators'
export const runtime = 'edge'
/* To avoid OpenAI errors, restrict to the Vercel Edge Function regions that
overlap with the OpenAI API regions.
Reference for Vercel regions: https://vercel.com/docs/edge-network/regions#region-list
Reference for OpenAI regions: https://help.openai.com/en/articles/5347006-openai-api-supported-countries-and-territories
*/
export const preferredRegion = [
'arn1',
'bom1',
'cdg1',
'cle1',
'cpt1',
'dub1',
'fra1',
'gru1',
'hnd1',
'iad1',
'icn1',
'kix1',
'lhr1',
'pdx1',
'sfo1',
'sin1',
'syd1',
]
const MAX_DEPTH = 5
function isAllowedCorsOrigin(origin: string): boolean {
const exactMatches = IS_DEV
? ['http://localhost:8082', 'https://supabase.com']
: ['https://supabase.com']
if (exactMatches.includes(origin)) {
return true
}
return /^https:\/\/[\w-]+\w-supabase.vercel.app$/.test(origin)
}
function getCorsHeaders(request: Request): Record<string, string> {
const origin = request.headers.get('Origin')
if (origin && isAllowedCorsOrigin(origin)) {
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Accept',
}
}
return {}
}
const validationRules = [
...specifiedRules,
createQueryDepthLimiter(MAX_DEPTH),
@@ -53,13 +103,18 @@ async function handleGraphQLRequest(request: Request): Promise<NextResponse> {
const { query, variables, operationName } = parsedBody.data
const validationErrors = validateGraphQLRequest(query, isDevGraphiQL(request))
if (validationErrors.length > 0) {
return NextResponse.json({
errors: validationErrors.map((error) => ({
message: error.message,
locations: error.locations,
path: error.path,
})),
})
return NextResponse.json(
{
errors: validationErrors.map((error) => ({
message: error.message,
locations: error.locations,
path: error.path,
})),
},
{
headers: getCorsHeaders(request),
}
)
}
const result = await graphql({
@@ -69,7 +124,9 @@ async function handleGraphQLRequest(request: Request): Promise<NextResponse> {
variableValues: variables,
operationName,
})
return NextResponse.json(result)
return NextResponse.json(result, {
headers: getCorsHeaders(request),
})
}
function validateGraphQLRequest(query: string, isDevGraphiQL = false): ReadonlyArray<GraphQLError> {
@@ -87,9 +144,21 @@ function validateGraphQLRequest(query: string, isDevGraphiQL = false): ReadonlyA
return validate(rootGraphQLSchema, documentAST, rules)
}
export async function OPTIONS(request: Request): Promise<NextResponse> {
const corsHeaders = getCorsHeaders(request)
return new NextResponse(null, {
status: 204,
headers: corsHeaders,
})
}
export async function POST(request: Request): Promise<NextResponse> {
try {
return await handleGraphQLRequest(request)
const result = await handleGraphQLRequest(request)
// Do not let Vercel close the process until Sentry has flushed
// https://github.com/getsentry/sentry-javascript/issues/9626
await Sentry.flush(2000)
return result
} catch (error: unknown) {
console.error(error)
@@ -97,16 +166,32 @@ export async function POST(request: Request): Promise<NextResponse> {
if (!error.isUserError()) {
Sentry.captureException(error)
}
// Do not let Vercel close the process until Sentry has flushed
// https://github.com/getsentry/sentry-javascript/issues/9626
await Sentry.flush(2000)
return NextResponse.json({
errors: [{ message: error.isPrivate() ? 'Internal Server Error' : error.message }],
})
return NextResponse.json(
{
errors: [{ message: error.isPrivate() ? 'Internal Server Error' : error.message }],
},
{
headers: getCorsHeaders(request),
}
)
} else {
Sentry.captureException(error)
// Do not let Vercel close the process until Sentry has flushed
// https://github.com/getsentry/sentry-javascript/issues/9626
await Sentry.flush(2000)
return NextResponse.json({
errors: [{ message: 'Internal Server Error' }],
})
return NextResponse.json(
{
errors: [{ message: 'Internal Server Error' }],
},
{
headers: getCorsHeaders(request),
}
)
}
}
}
@@ -204,4 +204,40 @@ describe('prod smoke test: graphql: searchDocs', () => {
expect(guideNode).toHaveProperty('href')
expect(guideNode).toHaveProperty('content')
})
it('searchDocs query includes Management API references', async () => {
const query = `
query SearchDocsQuery($query: String!) {
searchDocs(query: $query) {
nodes {
...on ManagementApiReference {
title
href
content
}
}
}
}
`
const result = await fetch(GRAPHQL_URL, {
method: 'POST',
body: JSON.stringify({ query, variables: { query: 'create SSO provider' } }),
})
expect(result.status).toBe(200)
const { data, errors } = await result.json()
expect(errors).toBeUndefined()
const {
searchDocs: { nodes },
} = data
expect(Array.isArray(nodes)).toBe(true)
expect(nodes.length).toBeGreaterThan(0)
const managementApiNode = nodes.find((node: any) => !!node.title)
expect(managementApiNode).toBeDefined()
expect(managementApiNode).toHaveProperty('title')
expect(managementApiNode).toHaveProperty('href')
expect(managementApiNode).toHaveProperty('content')
})
})
@@ -33,6 +33,16 @@ const rpcSpy = vi.fn().mockImplementation((funcName, params) => {
content: params?.include_full_content ? 'Another content' : null,
subsections: [{ title: 'Getting Started', content: 'Getting Started content' }],
},
{
type: 'reference',
page_title: 'Create a SSO provider',
href: 'https://supabase.com/docs/reference/api/v1-create-a-sso-provider',
content: params?.include_full_content ? 'Creates a new SSO provider for a project' : null,
metadata: {
title: 'Create a SSO provider',
subtitle: 'Management API Reference: Create a SSO provider',
},
},
]
return Promise.resolve({ data: mockResults.slice(0, limit), error: null })
}
@@ -190,4 +200,40 @@ describe('/api/graphql searchDocs', () => {
expect(json.errors).toBeDefined()
expect(json.errors[0].message).toContain('required')
})
it('should return Management API references with proper fields', async () => {
const searchQuery = `
query {
searchDocs(query: "SSO provider", limit: 3) {
nodes {
... on ManagementApiReference {
title
href
content
}
}
}
}
`
const request = new Request('http://localhost/api/graphql', {
method: 'POST',
body: JSON.stringify({ query: searchQuery }),
})
const response = await POST(request)
const json = await response.json()
expect(json.errors).toBeUndefined()
expect(json.data).toBeDefined()
expect(json.data.searchDocs).toBeDefined()
expect(json.data.searchDocs.nodes).toBeInstanceOf(Array)
expect(json.data.searchDocs.nodes).toHaveLength(3)
const managementApiNode = json.data.searchDocs.nodes[2]
expect(managementApiNode).toMatchObject({
title: 'Create a SSO provider',
href: 'https://supabase.com/docs/reference/api/v1-create-a-sso-provider',
content: 'Creates a new SSO provider for a project',
})
})
})
@@ -211,7 +211,20 @@ interface Params {
const WrappersDocs = async (props: { params: Promise<Params> }) => {
const params = await props.params
const { isExternal, meta, ...data } = await getContent(params)
const { isExternal, meta, assetsBaseUrl, ...data } = await getContent(params)
// Create a combined URL transformer that handles both regular URLs and asset URLs
const combinedUrlTransformer: UrlTransformFunction = (url, node) => {
// First try assets URL transformation (starts with ../assets/)
const transformedUrl = assetUrlTransform(url, assetsBaseUrl)
// If URL wasn't changed proceed with regular URL transformation
if (transformedUrl === url) {
return urlTransform(url, node)
}
return transformedUrl
}
const options = isExternal
? ({
@@ -222,7 +235,7 @@ const WrappersDocs = async (props: { params: Promise<Params> }) => {
remarkPyMdownTabs,
[removeTitle, meta.title],
],
rehypePlugins: [[linkTransform, urlTransform], rehypeSlug],
rehypePlugins: [[linkTransform, combinedUrlTransformer], rehypeSlug],
},
} as SerializeOptions)
: undefined
@@ -242,6 +255,7 @@ const getContent = async (params: Params) => {
let meta: any
let content: string
let editLink: string
let assetsBaseUrl: string = ''
if (!federatedPage) {
isExternal = false
@@ -278,6 +292,8 @@ const getContent = async (params: Params) => {
})
const rawContent = await response.text()
assetsBaseUrl = `https://raw.githubusercontent.com/${org}/${repo}/${tag}/docs/assets/`
const { content: contentWithoutFrontmatter } = matter(rawContent)
content = removeRedundantH1(contentWithoutFrontmatter)
}
@@ -289,9 +305,20 @@ const getContent = async (params: Params) => {
editLink: newEditLink(editLink),
meta,
content,
assetsBaseUrl,
}
}
const assetUrlTransform = (url: string, baseUrl: string): string => {
const assetPattern = /(\.\.\/)+assets\//
if (assetPattern.test(url)) {
return url.replace(assetPattern, baseUrl)
}
return url
}
const urlTransform: UrlTransformFunction = (url) => {
try {
const externalSiteUrl = new URL(externalSite)
@@ -663,6 +663,7 @@ export const auth = {
{ name: 'Firebase Auth', url: '/guides/auth/third-party/firebase-auth' },
{ name: 'Auth0', url: '/guides/auth/third-party/auth0' },
{ name: 'AWS Cognito (Amplify)', url: '/guides/auth/third-party/aws-cognito' },
{ name: 'WorkOS', url: '/guides/auth/third-party/workos' },
],
},
{
@@ -714,6 +715,7 @@ export const auth = {
{ name: 'Rate Limits', url: '/guides/auth/rate-limits' },
{ name: 'Bot Detection (CAPTCHA)', url: '/guides/auth/auth-captcha' },
{ name: 'JWTs', url: '/guides/auth/jwts' },
{ name: 'JWT Fields Reference', url: '/guides/auth/jwt-fields' },
{ name: 'Row Level Security', url: '/guides/database/postgres/row-level-security' },
{
name: 'Column Level Security',
@@ -2160,6 +2162,16 @@ export const platform: NavMenuConstant = {
{
name: 'Multi-factor Authentication',
url: '/guides/platform/multi-factor-authentication',
items: [
{
name: 'Enable MFA',
url: '/guides/platform/multi-factor-authentication',
},
{
name: 'Require MFA for organization members',
url: '/guides/platform/org-mfa-enforcement',
},
],
},
{
name: 'Transfer Project',
@@ -1 +1 @@
There is no fixed fee for a Preview branch. You only pay for the usage it incurs. With Compute costs of <Price price="0.01344" /> per hour, a branch running on Micro Compute size starts at <Price price="0.32" /> per day.
There is no fixed fee for a Preview branch. You only pay for the usage it incurs. A branch running on the default Micro Compute size starts at <Price price="0.01344" /> per hour.
@@ -16,15 +16,18 @@
"args": [
"-y",
"@supabase/mcp-server-supabase@latest",
"--access-token",
"<personal-access-token>"
]
"--read-only",
"--project-ref=<project-ref>"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "<personal-access-token>"
}
}
}
}
```
Replace `<personal-access-token>` with your personal access token.
Replace `<project-ref>` with your project ref, and `<personal-access-token>` with your personal access token.
</TabPanel>
@@ -40,15 +43,18 @@
"npx",
"-y",
"@supabase/mcp-server-supabase@latest",
"--access-token",
"<personal-access-token>"
]
"--read-only",
"--project-ref=<project-ref>"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "<personal-access-token>"
}
}
}
}
```
Replace `<personal-access-token>` with your personal access token.
Replace `<project-ref>` with your project ref, and `<personal-access-token>` with your personal access token.
<Admonition type="note">
@@ -83,15 +89,18 @@
"npx",
"-y",
"@supabase/mcp-server-supabase@latest",
"--access-token",
"<personal-access-token>"
]
"--read-only",
"--project-ref=<project-ref>"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "<personal-access-token>"
}
}
}
}
```
Replace `<personal-access-token>` with your personal access token.
Replace `<project-ref>` with your project ref, and `<personal-access-token>` with your personal access token.
This assumes you have Windows Subsystem for Linux (WSL) enabled and `node`/`npx` are installed within the WSL environment.
@@ -107,15 +116,18 @@
"args": [
"-y",
"@supabase/mcp-server-supabase@latest",
"--access-token",
"<personal-access-token>"
]
"--read-only",
"--project-ref=<project-ref>"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "<personal-access-token>"
}
}
}
}
```
Replace `<personal-access-token>` with your personal access token.
Replace `<project-ref>` with your project ref, and `<personal-access-token>` with your personal access token.
</TabPanel>
@@ -21,7 +21,7 @@
"servers": {
"supabase": {
"command": "npx",
"args": ["-y", "@supabase/mcp-server-supabase@latest"],
"args": ["-y", "@supabase/mcp-server-supabase@latest", "--read-only", "--project-ref=<project-ref>"],
"env": {
"SUPABASE_ACCESS_TOKEN": "${input:supabase-access-token}"
}
@@ -30,6 +30,8 @@
}
```
Replace `<project-ref>` with your project ref.
</TabPanel>
<TabPanel id="windows" label="Windows">
@@ -47,7 +49,7 @@
"servers": {
"supabase": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@supabase/mcp-server-supabase@latest"],
"args": ["/c", "npx", "-y", "@supabase/mcp-server-supabase@latest", "--read-only", "--project-ref=<project-ref>"],
"env": {
"SUPABASE_ACCESS_TOKEN": "${input:supabase-access-token}"
}
@@ -56,6 +58,8 @@
}
```
Replace `<project-ref>` with your project ref.
<Admonition type="note">
Make sure that `node` and `npx` are available in your system `PATH`. Assuming `node` is installed, you can get the path by running:
@@ -93,7 +97,7 @@
"servers": {
"supabase": {
"command": "wsl",
"args": ["npx", "-y", "@supabase/mcp-server-supabase@latest"],
"args": ["npx", "-y", "@supabase/mcp-server-supabase@latest", "--read-only", "--project-ref=<project-ref>"],
"env": {
"SUPABASE_ACCESS_TOKEN": "${input:supabase-access-token}"
}
@@ -102,6 +106,8 @@
}
```
Replace `<project-ref>` with your project ref.
This assumes you have Windows Subsystem for Linux (WSL) enabled and `node`/`npx` are installed within the WSL environment.
</TabPanel>
@@ -121,7 +127,7 @@
"servers": {
"supabase": {
"command": "npx",
"args": ["-y", "@supabase/mcp-server-supabase@latest"],
"args": ["-y", "@supabase/mcp-server-supabase@latest", "--read-only", "--project-ref=<project-ref>"],
"env": {
"SUPABASE_ACCESS_TOKEN": "${input:supabase-access-token}"
}
@@ -130,6 +136,8 @@
}
```
Replace `<project-ref>` with your project ref.
</TabPanel>
</Tabs>
@@ -2,6 +2,28 @@
Go to [database.new](https://database.new) and create a new Supabase project.
Alternatively, you can create a project using the Management API:
```bash
# First, get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
# List your organizations to get the organization ID
curl -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
https://api.supabase.com/v1/organizations
# Create a new project (replace <org-id> with your organization ID)
curl -X POST https://api.supabase.com/v1/projects \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"organization_id": "<org-id>",
"name": "My Project",
"region": "us-east-1",
"password": "<your-secure-password>"
}'
```
When your project is up and running, go to the [Table Editor](https://supabase.com/dashboard/project/_/editor), create a new table and insert some data.
Alternatively, you can run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `instruments` table with some sample data.
+182 -49
View File
@@ -1,66 +1,199 @@
---
id: 'api-keys'
title: 'Understanding API Keys'
description: 'Securing your Data API with Postgres Row Level Security.'
title: 'Understanding API keys'
description: "First-layer protection for your project's data"
---
Supabase provides two default keys when you create a project: an `anon` key, and a `service_role` key. You can find both keys in the [API Settings](https://supabase.com/dashboard/project/_/settings/api).
Supabase gives you fine-grained control over which application components are allowed to access your project through API keys.
The data APIs are designed to work with Postgres Row Level Security (RLS). These keys both map to Postgres roles. You can find an `anon` user and a `service_role` user in the [Roles](http://supabase.com/dashboard/project/_/database/roles) section of the dashboard.
API keys provide the first layer of authentication for data access. Auth then builds upon that. This chart covers the differences:
The keys are both long-lived JWTs. If you decode these keys, you will see that they contain the "role", an "issued date", and an "expiry date" ~10 years in the future.
| Responsibility | Question | Answer |
| ---------------------------------- | ---------------------------------- | -------------------------------------------------- |
| API keys | **What** is accessing the project? | Web page, mobile app, server, Edge Function... |
| [Supabase Auth](/docs/guides/auth) | **Who** is accessing the project? | Monica, Jian Yang, Gavin, Dinesh, Laurie, Fiona... |
```json
{
"role": "anon",
"iat": 1625137684,
"exp": 1940713684
}
```
## Overview
## The `anon` key
An API key authenticates an application component to give it access to Supabase services. An application component might be a web page, a mobile app, or a server. The API key _does not_ distinguish between users, only between applications.
The `anon` key has very few privileges. You can use it in your [RLS policies](/docs/guides/database/postgres/row-level-security) to allow unauthenticated access. For example, this policy will allow unauthenticated access to the `profiles` table:
There are 4 types of API keys that can be used with Supabase:
```sql
create policy "Allow public access" on profiles to anon for
select
using (true);
```
| Type | Format | Privileges | Availability | Use |
| ---------------------------------------------------------- | ---------------------------------------------------------------- | ---------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Publishable&nbsp;key | <span className="!whitespace-nowrap">`sb_publishable_...`</span> | Low | Platform | Safe to expose online: web page, mobile or desktop app, GitHub actions, CLIs, source code. |
| Secret&nbsp;keys | <span className="!whitespace-nowrap">`sb_secret_...`</span> | Elevated | Platform | **Only use in backend components of your app:** servers, already secured APIs (admin panels), [Edge Functions](/docs/guides/functions), microservices, etc. They provide _full access_ to your project's data, bypassing [Row Level Security](/docs/guides/database/postgres/row-level-security). |
| <span className="!whitespace-nowrap">`anon`</span> | JWT (long lived) | Low | <span className="!whitespace-nowrap">Platform, CLI</span> | Exactly like the publishable key. |
| <span className="!whitespace-nowrap">`service_role`</span> | JWT (long lived) | Elevated | <span className="!whitespace-nowrap">Platform, CLI</span> | Exactly like secret keys. |
And similarly for disallowing access:
<Admonition type="note">
```sql
create policy "Disallow public access" on profiles to anon for
select
using (false);
```
If you are using [Supabase Auth](/docs/guides/auth/overview), then the `anon` role will automatically update to `authenticated` once a user is logged in:
```sql
create policy "Allow access to authenticated users" on profiles to authenticated for
select
using (true);
```
## The `service_role` key
The "service_role" is a predefined Postgres role with elevated privileges, designed to perform various administrative and service-related tasks. It can bypass Row Level Security, so it should only be used on a private server.
<Admonition type="caution">
Never expose the `service_role` key in a browser or anywhere where a user can see it.
`anon` and `service_role` keys are based on the project's JWT secret. They are generated when your project is created and can only be changed when you rotate the JWT secret. This can cause significant issues in production applications. Use the publishable and secret keys instead.
</Admonition>
A common use case for the `service_role` key is running data analytics jobs on the backend. To support joins on user id, it is often useful to grant the service role read access to `auth.users` table.
## `anon` and publishable keys
```sql
grant
select
on table auth.users to service_role;
```
The `anon` and publishable keys secure the public components of your application. Public components run in environments where it is impossible to secure any secrets. These include:
We have [partnered with GitHub](https://github.blog/changelog/2022-03-28-supabase-is-now-a-github-secret-scanning-partner/) to scan for Supabase `service_role` keys pushed to public repositories.
If they detect any keys with service_role privileges being pushed to GitHub, they will forward the API key to us, so that we can automatically revoke the detected secrets and notify you, protecting your data against malicious actors.
- Web pages, where the key is bundled in source code.
- Mobile or desktop applications, where the key is bundled inside the compiled packages or executables.
- CLI, scripts, tools, or other pre-built executables.
- Other publicly available APIs that return the key without prior additional authorization.
These environments are always considered public because anyone can retrieve the key from the source code or build artifacts. Obfuscation can increase the difficulty, but never eliminate the possibility. (In general, obfuscation, Turing test challenges, and specialized knowledge do not count as authorization for the purpose of securing secrets.)
### Interaction with Supabase Auth
Using the `anon` or publishable key does not mean that your user is anonymous. (Thinking of both these keys as publishable rather than `anon` makes the mental model clearer.)
Your application can be authenticated with the publishable key, while your user is authenticated (via Supabase Auth) with their personal JWT:
| Key | User logged in via Supabase Auth | Postgres role used for RLS, etc. |
| --------------- | -------------------------------- | -------------------------------- |
| Publishable key | No | `anon` |
| `anon` | No | `anon` |
| Publishable key | Yes | `authenticated` |
| `anon` | Yes | `authenticated` |
### Protection
These keys provide first-layer protection to your project's data, performance and bill, such as:
- Providing basic Denial-of-Service protection, by requiring a minimal threshold of knowledge.
- Protecting your bill by ignoring bots, scrapers, automated vulnerability scanners and other well meaning or random Internet activity.
### Security considerations
The publishable and `anon` keys are not intended to protect from the following, since key retrieval is always possible from a public component:
- Static or dynamic code analysis and reverse engineering attempts.
- Use of the Network inspector in the browser.
- Cross-site request forgery, cross-site scripting, phishing attacks.
- Man-in-the-middle attacks.
When using the publishable or `anon` key, access to your project's data is guarded by Postgres via the built-in `anon` and `authenticated` roles. For full protection make sure:
- You have enabled Row Level Security on all tables.
- You regularly review your Row Level Security policies for permissions granted to the `anon` and `authenticated` roles.
- You do not modify the role's attributes without understanding the changes you are making.
Your project's [Security Advisor](/dashboard/project/_/advisors/security) constantly checks for common security problems with the built-in Postgres roles. Make sure you carefully review each finding before dismissing it.
## `service_role` and secret keys
Unlike the `anon` and publishable key, the `service_role` and secret keys allow elevated access to your project's data. It is meant to be used only in secure, developer-controlled components of your application, such as:
- Servers that implement prior authorization themselves, such as Edge Functions, microservices, traditional or specialized web servers.
- Periodic jobs, queue processors, topic subscribers.
- Admin and back-office tools, with prior authorization checks only.
- Data processing pipelines, such as for analytics, reports, backups, or database synchronization.
<Admonition type="caution">
Never expose your `service_role` and secret keys publicly. Your data is at risk. **Do not:**
- Add in web pages, public documents, source code, bundle in executables or packages for mobile, desktop or CLI apps.
- Send over chat applications, email or SMS to your peers.
- Never use in a browser, even on `localhost`!
- Do not pass in URLs or query params, as these are often logged.
- Be careful passing them in request headers without prior log sanitization.
- Take extra care logging even potentially **invalid API keys**. Simple typos might reveal the real key in the future.
- Reveal, copy, use or manipulate on hardware devices without full disk encryption and which you do not directly own or control (such as public computers, friend's laptop, etc.)
Ensure you handle them with care and using [secure coding practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/stable-en/).
</Admonition>
Secret keys and the `service_role` JWT-based API key authorize access to your project's data via the built-in `service_role` Postgres role. By design, this role has full access to your project's data. It also uses the [`BYPASSRLS` attribute](https://www.postgresql.org/docs/current/ddl-rowsecurity.html#:~:text=BYPASSRLS), skipping any and all Row Level Security policies you attach.
The secret key is an improvement over the old JWT-based `service_role` key, and we recommend using it where possible. It adds more checks to prevent misuse, specifically:
- You cannot use a secret key in the browser (matches on the `User-Agent` header) and it will always reply with HTTP 401 Unauthorized.
- You don't need to have any secret keys if you are not using them.
### Best practices for handling secret keys
Below are some starting guidelines on how to securely work with secret keys:
- Always work with secret keys on computers you fully own or control.
- Use secure & encrypted send tools to share API keys with others (often provided by good password managers), but prefer the [API Keys](/dashboard/project/_/settings/api-keys/new) dashboard instead.
- Prefer encrypting them when stored in files or environment variables.
- Do not add in source control, especially for CI scripts and tools. Prefer using the tool's native secrets capability instead.
- Prefer using a separate secret key for each separate backend component of your application, so that if one is found to be vulnerable or to have leaked the key you will only need to change it and not all.
- Even though a secret key will always return HTTP 401 Unauthorized error when used in a browser, it does not mean that attackers will not use it with other tools. Delete immediately!
- If you must include them in logs, log the first few random characters (but never more than 6).
- If you wish to log or store which valid API key was used, store it as a SHA256 hash.
### What to do if a secret key or `service_role` has been leaked or compromised?
Don't rush if this has happened, or you are suspecting it has. Make sure you have fully considered the situation and have remediated the root cause of the suspicion or vulnerability **first**. Consider using the [OWASP Risk Rating Methodology](https://owasp.org/www-community/OWASP_Risk_Rating_Methodology) as an easy way to identify the severity of the incident and to plan your next steps.
Rotating a secret key (`sb_secret_...`) is easy and painless. Use the [API Keys](/dashboard/project/_/settings/api-keys/new) dashboard to create a new secret API key, then replace it with the compromised key. Once all components are using the new key, delete the compromised one.
**Deleting a secret key is irreversible and once done it will be gone forever.**
If you are still using the JWT-based `service_role` key, there are two options.
1. **Strongly recommended:** Replace the `service_role` key with a new secret key instead. Follow the guide from above as if you are rotating an existing secret key.
2. [Rotate your project's JWT secret.](/dashboard/project/_/settings/api) This operation is only recommended if you suspect that the JWT secret has leaked itself. Consider switching your `anon` JWT-based key to the publishable key, and all `service_role` JWT-based keys to secret keys. Only then rotate the JWT secret. Check the FAQ below if you use the JWT-based keys in mobile, desktop or CLI applications!
## Known limitations and compatibility differences
As the publishable and secret keys are no longer JWT-based, there are some known limitations and compatibility differences that you may need to plan for:
- You cannot send a publishable or secret key in the `Authorization: Bearer ...` header, except if the value exactly equals the `apikey` header. In this case, your request will be forwarded down to your project's database, but will be rejected as the value is not a JWT.
- Edge Functions **only support JWT verification** via the `anon` and `service_role` JWT-based API keys. You will need to use the `--no-verify-jwt` option when using publishable and secret keys. The Supabase platform does not verify the `apikey` header when using Edge Functions in this way. Implement your own `apikey`-header authorization logic inside the Edge Function code itself.
- Public Realtime connections are limited to 24 hours in duration, unless the connection is upgraded and further maintained with user-level authentication via Supabase Auth or a supported Third-Party Auth provider.
## Frequently asked questions
{/* supa-mdx-lint-disable Rule004ExcludeWords */}
### I am using JWT-based `anon` key in a mobile, desktop, or CLI application and need to rotate my `service_role` JWT secret?
If you know or suspect that the JWT secret itself is leaked, refer to the section on [rotating the JWT](#what-to-do-if-a-secret-key-or-servicerole-has-been-leaked-or-compromised).
If the JWT secret is secure, prefer substituting the `service_role` JWT-based key with a new secret key which you can create in the [API Keys](/dashboard/project/_/settings/api-keys/new) dashboard. This will prevent downtime for your application.
### How do I deactivate the `anon` and `service_role` JWT-based API keys after moving to publishable and secret keys?
You can do this in the [API Keys](/dashboard/project/_/settings/api-keys/new) dashboard. To prevent downtime in your application's components, use the last used indicators on the page to confirm that these are no longer used before deactivating.
You can re-activate them should you need to.
### Why are `anon` and `service_role` JWT-based keys no longer recommended?
Since the start of Supabase, the JWT-based `anon` and `service_role` keys were the right trade-off against simplicity and relative security for your project. Unfortunately they pose some real challenges in live applications, especially around rotation and security best practices.
The main reasons for preferring the publishable and secret keys (`sb_publishable_...` and `sb_secret_...`) are:
- Tight coupling between the JWT secret (which itself can be compromised, if you mint your own JWTs), the `anon` (low privilege) and `service_role` (high privilege) and `authenticated` (issued by Supabase Auth) Postgres roles.
- Inability to independently rotate each aspect of the keys, without downtime.
- Inability to roll-back an unnecessary or problematic JWT secret rotation.
- Publishing new versions of mobile applications can take days and often weeks in the app review phase with Apple's App Store and Google's Play Store. A forced rotation can cause weeks of downtime for mobile app users.
- Users may continue using desktop, CLI and mobile apps with very old versions, making rotation impossible without a forced version upgrade.
- JWTs had 10-year expiry duration, giving malicious actors more to work with.
- JWTs were self-referential and full of redundant information not necessary for achieving their primary purpose.
- JWTs are large, hard to parse, verify, and manipulate -- leading to insecure logging or bad security practices.
- They were signed with a symmetric JWT secret.
### Why is there no publishable or secret keys in the CLI / self-hosting?
Publishable and secret keys are only available on the Supabase hosted platform. They are managed by our API Gateway component, which does not currently have a CLI equivalent.
We are looking into providing similar but limited in scope support for publishable or secret keys in the future. For now you can only use the `anon` and `service_role` JWT-based keys there.
For advanced users, see the following question on how these keys are implemented on the hosted platform for an idea on how to provide similar functionality for yourself.
### How are publishable and secret keys implemented on the hosted platform?
When your applications use the Supabase APIs they go through a component called the API Gateway on the Supabase hosted platform. This provides us (and therefore you) with the following features:
- Observability and logging.
- Performance and request routing (such as to read-replicas).
- Security, for blocking malicious patterns or behavior on a global scale.
This API Gateway component is able to verify the API key (sent in the `apikey` request header, or for WebSocket in a query param) against your project's publishable and secret key list. If the match is found, it mints a temporary, short-lived JWT that is then forwarded down to your project's servers.
It may be possible to replicate similar behavior if you self-host by using programmable proxies such as [Kong](https://konghq.com/), [Envoy](https://www.envoyproxy.io/), [NGINX](https://nginx.org/) or similar.
+1 -1
View File
@@ -17,7 +17,7 @@ Authentication and authorization are the core responsibilities of any Auth syste
- **Authentication** means checking that a user is who they say they are.
- **Authorization** means checking what resources a user is allowed to access.
Supabase Auth uses [JSON Web Tokens (JWTs)](/docs/guides/auth/jwts) for authentication. Auth integrates with Supabase's database features, making it easy to use [Row Level Security (RLS)](/docs/guides/database/postgres/row-level-security) for authorization.
Supabase Auth uses [JSON Web Tokens (JWTs)](/docs/guides/auth/jwts) for authentication. For a complete reference of all JWT fields, see the [JWT Fields Reference](/docs/guides/auth/jwt-fields). Auth integrates with Supabase's database features, making it easy to use [Row Level Security (RLS)](/docs/guides/database/postgres/row-level-security) for authorization.
## The Supabase ecosystem
@@ -29,6 +29,36 @@ The templating system provides the following variables for use:
On hosted Supabase projects, edit your email templates on the [Email Templates](/dashboard/project/_/auth/templates) page. On self-hosted projects or in local development, edit your [configuration files](/docs/guides/local-development/customizing-email-templates).
You can also manage email templates using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Get current email templates
curl -X GET "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
| jq 'to_entries | map(select(.key | startswith("mailer_templates"))) | from_entries'
# Update email templates
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mailer_subjects_confirmation": "Confirm your signup",
"mailer_templates_confirmation_content": "<h2>Confirm your signup</h2><p>Follow this link to confirm your user:</p><p><a href=\"{{ .ConfirmationURL }}\">Confirm your email</a></p>",
"mailer_subjects_magic_link": "Your Magic Link",
"mailer_templates_magic_link_content": "<h2>Magic Link</h2><p>Follow this link to login:</p><p><a href=\"{{ .ConfirmationURL }}\">Log In</a></p>",
"mailer_subjects_recovery": "Rest Your Password",
"mailer_templates_recovery_content": "<h2>Reset Password</h2><p>Follow this link to reset the password for your user:</p><p><a href=\"{{ .ConfirmationURL }}\">Reset Password</a></p>",
"mailer_subjects_invite": "You have been invited",
"mailer_templates_invite_content": "<h2>You have been invited</h2><p>You have been invited to create a user on {{ .SiteURL }}. Follow this link to accept the invite:</p><p><a href=\"{{ .ConfirmationURL }}\">Accept the invite</a></p>",
"mailer_subjects_email_change": "Confirm email change",
"mailer_templates_email_change_content": "<h2>Confirm email change</h2><p>Follow this link to confirm the update of your email:</p><p><a href=\"{{ .ConfirmationURL }}\">Change email</a></p>",
}'
```
## Mobile deep linking
For mobile applications, you might need to link or redirect to a specific page within your app. See the [Mobile Deep Linking guide](/docs/guides/auth/native-mobile-deep-linking) to set this up.
@@ -50,6 +50,30 @@ A non-exhaustive list of services that work with Supabase Auth is:
Once you've set up your account with an email sending service, head to the [Authentication settings page](/dashboard/project/_/settings/auth) to enable and configure custom SMTP.
You can also configure custom SMTP using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure custom SMTP
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_email_enabled": true,
"mailer_secure_email_change_enabled": true,
"mailer_autoconfirm": false,
"smtp_admin_email": "no-reply@example.com",
"smtp_host": "smtp.example.com",
"smtp_port": 587,
"smtp_user": "your-smtp-user",
"smtp_pass": "your-smtp-password",
"smtp_sender_name": "Your App Name"
}'
```
Once you save these settings, your project's Auth server will send messages to all addresses. To protect the reputation of your newly set up service a low rate-limit of 30 messages per hour is imposed. To adjust this to an acceptable value for your use case head to the [Rate Limits configuration page](/dashboard/project/_/auth/rate-limits).
## Dealing with abuse: How to maintain the sending reputation of your SMTP server?
@@ -0,0 +1,288 @@
---
id: 'jwt-fields'
title: 'JWT Fields Reference'
subtitle: 'Complete reference for JWT fields in Supabase'
---
This page provides a comprehensive reference for all JWT fields used in Supabase authentication tokens. This information is essential for server-side JWT validation and serialization, especially when implementing authentication in languages like Rust where field names like `ref` are reserved keywords.
## JWT structure overview
Supabase JWTs follow the standard JWT structure with three parts:
- **Header**: Contains algorithm and key information
- **Payload**: Contains the claims (user data and metadata)
- **Signature**: Cryptographic signature for verification
The payload contains various claims that provide user identity, authentication level, and authorization information.
## Required claims
These claims are always present in Supabase JWTs and cannot be removed:
| Field | Type | Description | Example |
| -------------- | -------------------- | ----------------------------------------------------------- | --------------------------------------------- |
| `iss` | `string` | **Issuer** - The entity that issued the JWT | `"https://project-ref.supabase.co/auth/v1"` |
| `aud` | `string \| string[]` | **Audience** - The intended recipient of the JWT | `"authenticated"` or `"anon"` |
| `exp` | `number` | **Expiration Time** - Unix timestamp when the token expires | `1640995200` |
| `iat` | `number` | **Issued At** - Unix timestamp when the token was issued | `1640991600` |
| `sub` | `string` | **Subject** - The user ID (UUID) | `"123e4567-e89b-12d3-a456-426614174000"` |
| `role` | `string` | **Role** - User's role in the system | `"authenticated"`, `"anon"`, `"service_role"` |
| `aal` | `string` | **Authenticator Assurance Level** - Authentication strength | `"aal1"`, `"aal2"` |
| `session_id` | `string` | **Session ID** - Unique session identifier | `"session-uuid"` |
| `email` | `string` | **Email** - User's email address | `"user@example.com"` |
| `phone` | `string` | **Phone** - User's phone number | `"+1234567890"` |
| `is_anonymous` | `boolean` | **Anonymous Flag** - Whether the user is anonymous | `false` |
## Optional claims
These claims may be present depending on the authentication context:
| Field | Type | Description | Example |
| --------------- | -------- | -------------------------------------------------------------------------- | --------------------------------------------------- |
| `jti` | `string` | **JWT ID** - Unique identifier for the JWT | `"jwt-uuid"` |
| `nbf` | `number` | **Not Before** - Unix timestamp before which the token is invalid | `1640991600` |
| `app_metadata` | `object` | **App Metadata** - Application-specific user data | `{"provider": "email"}` |
| `user_metadata` | `object` | **User Metadata** - User-specific data | `{"name": "John Doe"}` |
| `amr` | `array` | **Authentication Methods Reference** - List of authentication methods used | `[{"method": "password", "timestamp": 1640991600}]` |
## Special fields
| Field | Type | Description | Example | Context |
| ----- | -------- | --------------------------------------------------- | ------------------------ | ----------------------------- |
| `ref` | `string` | **Project Reference** - Supabase project identifier | `"abcdefghijklmnopqrst"` | Anon/Service role tokens only |
## Field value constraints
### Authenticator assurance level (`aal`)
| Value | Description |
| -------- | ---------------------------------------------------- |
| `"aal1"` | Single-factor authentication (password, OAuth, etc.) |
| `"aal2"` | Multi-factor authentication (password + TOTP, etc.) |
### Role values (`role`)
| Value | Description | Use Case |
| ----------------- | ------------------ | ----------------------------------- |
| `"anon"` | Anonymous user | Public access with RLS policies |
| `"authenticated"` | Authenticated user | Standard user access |
| `"service_role"` | Service role | Admin privileges (server-side only) |
### Audience values (`aud`)
| Value | Description |
| ----------------- | ----------------------------- |
| `"authenticated"` | For authenticated user tokens |
| `"anon"` | For anonymous user tokens |
### Authentication methods (`amr.method`)
| Value | Description |
| ----------------- | ----------------------------- |
| `"oauth"` | OAuth provider authentication |
| `"password"` | Email/password authentication |
| `"otp"` | One-time password |
| `"totp"` | Time-based one-time password |
| `"recovery"` | Account recovery |
| `"invite"` | Invitation-based signup |
| `"sso/saml"` | SAML single sign-on |
| `"magiclink"` | Magic link authentication |
| `"email/signup"` | Email signup |
| `"email_change"` | Email change |
| `"token_refresh"` | Token refresh |
| `"anonymous"` | Anonymous authentication |
## JWT examples
### Authenticated user token
```json
{
"aal": "aal1",
"amr": [
{
"method": "password",
"timestamp": 1640991600
}
],
"app_metadata": {
"provider": "email",
"providers": ["email"]
},
"aud": "authenticated",
"email": "user@example.com",
"exp": 1640995200,
"iat": 1640991600,
"iss": "https://abcdefghijklmnopqrst.supabase.co/auth/v1",
"phone": "",
"role": "authenticated",
"session_id": "123e4567-e89b-12d3-a456-426614174000",
"sub": "123e4567-e89b-12d3-a456-426614174000",
"user_metadata": {
"name": "John Doe"
},
"is_anonymous": false
}
```
### Anonymous user token
```json
{
"iss": "supabase",
"ref": "abcdefghijklmnopqrst",
"role": "anon",
"iat": 1640991600,
"exp": 1640995200
}
```
### Service role token
```json
{
"iss": "supabase",
"ref": "abcdefghijklmnopqrst",
"role": "service_role",
"iat": 1640991600,
"exp": 1640995200
}
```
## Language-Specific considerations
### Rust
In Rust, the `ref` field is a reserved keyword. When deserializing JWTs, you'll need to handle this:
```rust
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct JwtClaims {
iss: String,
#[serde(rename = "ref")] // Handle reserved keyword
project_ref: Option<String>,
role: String,
iat: i64,
exp: i64,
// ... other fields
}
```
### TypeScript/JavaScript
```typescript
interface JwtClaims {
iss: string
aud: string | string[]
exp: number
iat: number
sub: string
role: string
aal: 'aal1' | 'aal2'
session_id: string
email: string
phone: string
is_anonymous: boolean
jti?: string
nbf?: number
app_metadata?: Record<string, any>
user_metadata?: Record<string, any>
amr?: Array<{
method: string
timestamp: number
}>
ref?: string // Only in anon/service role tokens
}
```
### Python
```python
from typing import Optional, Union, List, Dict, Any
from dataclasses import dataclass
@dataclass
class AmrEntry:
method: str
timestamp: int
@dataclass
class JwtClaims:
iss: str
aud: Union[str, List[str]]
exp: int
iat: int
sub: str
role: str
aal: str
session_id: str
email: str
phone: str
is_anonymous: bool
jti: Optional[str] = None
nbf: Optional[int] = None
app_metadata: Optional[Dict[str, Any]] = None
user_metadata: Optional[Dict[str, Any]] = None
amr: Optional[List[AmrEntry]] = None
ref: Optional[str] = None # Only in anon/service role tokens
```
### Go
```go
type AmrEntry struct {
Method string `json:"method"`
Timestamp int64 `json:"timestamp"`
}
type JwtClaims struct {
Iss string `json:"iss"`
Aud interface{} `json:"aud"` // string or []string
Exp int64 `json:"exp"`
Iat int64 `json:"iat"`
Sub string `json:"sub"`
Role string `json:"role"`
Aal string `json:"aal"`
SessionID string `json:"session_id"`
Email string `json:"email"`
Phone string `json:"phone"`
IsAnonymous bool `json:"is_anonymous"`
Jti *string `json:"jti,omitempty"`
Nbf *int64 `json:"nbf,omitempty"`
AppMetadata map[string]interface{} `json:"app_metadata,omitempty"`
UserMetadata map[string]interface{} `json:"user_metadata,omitempty"`
Amr []AmrEntry `json:"amr,omitempty"`
Ref *string `json:"ref,omitempty"` // Only in anon/service role tokens
}
```
## Validation guidelines
When implementing JWT validation on your server:
1. **Check Required Fields**: Ensure all required claims are present
2. **Validate Types**: Verify field types match expected types
3. **Check Expiration**: Validate `exp` timestamp is in the future
4. **Verify Issuer**: Ensure `iss` matches your Supabase project
5. **Check Audience**: Validate `aud` matches expected audience
6. **Handle Reserved Keywords**: Use field renaming for languages like Rust
## Security considerations
- **Always validate the JWT signature** before trusting any claims
- **Never expose service role tokens** to client-side code
- **Validate all claims** before trusting the JWT
- **Check token expiration** on every request
- **Use HTTPS** for all JWT transmission
- **Rotate JWT secrets** regularly
- **Implement proper error handling** for invalid tokens
## Related documentation
- [JWT Overview](/docs/guides/auth/jwts)
- [Custom Access Token Hooks](/docs/guides/auth/auth-hooks/custom-access-token-hook)
- [Row Level Security](/docs/guides/database/postgres/row-level-security)
- [Server-Side Auth](/docs/guides/auth/server-side)
+1
View File
@@ -177,3 +177,4 @@ Now that you understand what JWTs are and where they're used in Supabase, you ca
## Resources
- JWT debugger: https://jwt.io/
- [JWT Fields Reference](/docs/guides/auth/jwt-fields) - Complete reference for all JWT fields in Supabase
@@ -23,7 +23,7 @@ There are hundreds of millions (and growing!) known passwords out there. Malicio
To help protect your users, Supabase Auth allows you fine-grained control over the strength of the passwords used on your project. You can configure these in your project's [Auth settings](/dashboard/project/_/auth/providers?provider=Email):
- Set a large minimum password length. Anything less than 8 characters is not recommended.
- Set the required characters that must appear at least once in a user's password. Use the strongest option of requiring digits, lowercase and uppercase letters, and symbols.
- Set the required characters that must appear at least once in a user's password. Use the strongest option of requiring digits, lowercase and uppercase letters, and symbols. The allowed symbols are: ``!@#$%^&*()_+-=[]{};'\:"|<>?,./`~``
- Prevent the use of leaked passwords. Supabase Auth uses the open-source [HaveIBeenPwned.org Pwned Passwords API](https://haveibeenpwned.com/Passwords) to reject passwords that have been leaked and are known by malicious actors.
<Admonition type="note">
@@ -5,4 +5,31 @@ subtitle: 'Rate limits protect your services from abuse'
Supabase Auth enforces rate limits on endpoints to prevent abuse. Some rate limits are [customizable](/dashboard/project/_/auth/rate-limits).
You can also manage rate limits using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Get current rate limits
curl -X GET "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
| jq 'to_entries | map(select(.key | startswith("rate_limit_"))) | from_entries'
# Update rate limits
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rate_limit_anonymous_users": 10,
"rate_limit_email_sent": 10,
"rate_limit_sms_sent": 10,
"rate_limit_verify": 10,
"rate_limit_token_refresh": 10,
"rate_limit_otp": 10,
"rate_limit_web3": 10
}'
```
<$Partial path="auth_rate_limits.mdx" />
@@ -321,7 +321,7 @@ Set up a listener for Auth events on the client, to handle session refreshes and
<StepHikeCompact.Details title="Create your first page">
Create your first page. This example page calls Supabase from the server to get a list of countries from the database.
Create your first page. This example page calls Supabase from the server to get a list of colors from the database.
This is an example of a public page that uses publicly readable data.
@@ -67,6 +67,24 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app,
6. Create a signing **Key** in the [Keys](https://developer.apple.com/account/resources/authkeys/list) section of the Apple Developer Console. You can use this key to generate a secret key using the tool below, which is added to your Supabase project's Auth configuration. Make sure you safely store the `AuthKey_XXXXXXXXXX.p8` file. If you ever lose access to it, or make it public accidentally, revoke it from the Apple Developer Console and create a new one immediately. You will have to generate a new secret key using this file every 6 months, so make sure you schedule a recurring meeting in your calendar!
7. Finally, add the information you configured above to the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers).
You can also configure the Apple auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure Apple auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_apple_enabled": true,
"external_apple_client_id": "your-services-id",
"external_apple_secret": "your-generated-secret-key"
}'
```
<Admonition type="tip">
Use this tool to generate a new Apple client secret. No keys leave your browser! Be aware that this tool does not currently work in Safari, so use Firefox or a Chrome-based browser instead.
@@ -192,6 +210,12 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app,
</Admonition>
<Admonition type="note">
When testing with Expo development build with custom `bundleIdentifier`, for example com.example.app , com.example.app.dev , com.example.app.preview. Make sure to add all these variants to the "Client IDs" list in your [Supabase dashboard Apple provider configuration](https://supabase.com/dashboard/project/_/auth/providers)!
</Admonition>
1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._)
2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Client IDs_.
@@ -44,6 +44,25 @@ Setting up OAuth with Azure consists of four broad steps:
![Obtain the client secret](/docs/img/guides/auth-azure/azure-client-secret.png)
You can also configure the Azure auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure Azure auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_azure_enabled": true,
"external_azure_client_id": "your-azure-client-id",
"external_azure_secret": "your-azure-client-secret",
"external_azure_url": "your-azure-url"
}'
```
## Guarding against unverified email domains
Microsoft Entra ID can send out unverified email domains in certain cases. This may open up your project to a vulnerability where a malicious user can impersonate already existing accounts on your project.
@@ -43,6 +43,24 @@ Setting up Discord logins for your application consists of 3 parts:
<$Partial path="social_provider_settings_supabase.mdx" variables={{ "provider": "Discord" }} />
You can also configure the Discord auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure Discord auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_discord_enabled": true,
"external_discord_client_id": "your-discord-client-id",
"external_discord_secret": "your-discord-client-secret"
}'
```
## Add login code to your client app
<Tabs
@@ -60,6 +60,24 @@ Under `Build Your App`, click on `Use Cases` screen. From there, do the followin
<$Partial path="social_provider_settings_supabase.mdx" variables={{ "provider": "Facebook" }} />
You can also configure the Facebook auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure Facebook auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_facebook_enabled": true,
"external_facebook_client_id": "your-facebook-app-id",
"external_facebook_secret": "your-facebook-app-secret"
}'
```
## Add login code to your client app
<Tabs
@@ -38,6 +38,24 @@ Copy your new OAuth credentials
<$Partial path="social_provider_settings_supabase.mdx" variables={{ "provider": "GitHub" }} />
You can also configure the GitHub auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure GitHub auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_github_enabled": true,
"external_github_client_id": "your-github-client-id",
"external_github_secret": "your-github-client-secret"
}'
```
## Add login code to your client app
<Tabs
@@ -53,6 +53,24 @@ To use your own application code:
1. When you finish configuring your credentials, you will be shown your client ID and secret. Add these to the [Google Auth Provider section of the Supabase Dashboard](/dashboard/project/_/auth/providers).
Alternatively, you can configure Google authentication using the Management API:
```bash
# First, get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Update auth config to enable Google provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_google_enabled": true,
"external_google_client_id": "your-google-client-id",
"external_google_secret": "your-google-client-secret"
}'
```
<Admonition type="tip">
In local development, you can add the client ID and secret to your `config.toml` file.
@@ -46,6 +46,24 @@ Ensure that the appropriate scopes have been added under OAuth 2.0 Scopes at the
<$Partial path="social_provider_settings_supabase.mdx" variables={{ "provider": "LinkedIn (OIDC)" }} />
You can also configure the LinkedIn (OIDC) auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure LinkedIn (OIDC) auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_linkedin_oidc_enabled": true,
"external_linkedin_oidc_client_id": "your-linkedin-client-id",
"external_linkedin_oidc_secret": "your-linkedin-client-secret"
}'
```
## Add login code to your client app
<Tabs
@@ -62,6 +62,24 @@ Under `Scopes`:
<$Partial path="social_provider_settings_supabase.mdx" variables={{ "provider": "Slack" }} />
You can also configure the Slack (OIDC) auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure Slack (OIDC) auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_slack_oidc_enabled": true,
"external_slack_oidc_client_id": "your-slack-client-id",
"external_slack_oidc_secret": "your-slack-client-secret"
}'
```
## Add login code to your client app
<Tabs
@@ -48,6 +48,24 @@ Under `Redirect URIs`:
<$Partial path="social_provider_settings_supabase.mdx" variables={{ "provider": "Spotify" }} />
You can also configure the Spotify auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure Spotify auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_spotify_enabled": true,
"external_spotify_client_id": "your-spotify-client-id",
"external_spotify_secret": "your-spotify-client-secret"
}'
```
## Add login code to your client app
The following outlines the steps to sign in using Spotify with Supabase Auth.
@@ -50,6 +50,24 @@ Setting up Twitter logins for your application consists of 3 parts:
<$Partial path="social_provider_settings_supabase.mdx" variables={{ "provider": "Twitter" }} />
You can also configure the Twitter auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure Twitter auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_twitter_enabled": true,
"external_twitter_client_id": "your-twitter-api-key",
"external_twitter_secret": "your-twitter-api-secret-key"
}'
```
## Add login code to your client app
<Tabs
@@ -40,6 +40,25 @@ You must be signed in to see these values.
1. Copy the `Callback URL (for OAuth)` value from the form and save it somewhere handy.
1. Click Save.
You can also configure the WorkOS auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure WorkOS auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_workos_enabled": true,
"external_workos_url": "https://api.workos.com",
"external_workos_client_id": "your-workos-client-id",
"external_workos_secret": "your-workos-client-secret"
}'
```
## Step 4. Set your Supabase redirect URI in the WorkOS Dashboard
Visit the WorkOS dashboard and click the redirects button in the left navigation panel.
@@ -57,6 +57,24 @@ Under `Scopes`
<$Partial path="social_provider_settings_supabase.mdx" variables={{ "provider": "Zoom" }} />
You can also configure the Zoom auth provider using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Configure Zoom auth provider
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_zoom_enabled": true,
"external_zoom_client_id": "your-zoom-client-id",
"external_zoom_secret": "your-zoom-client-secret"
}'
```
## Add login code to your client app
<Tabs
@@ -10,6 +10,7 @@ Supabase has first-class support for these third-party authentication providers:
- [Firebase Auth](/docs/guides/auth/third-party/firebase-auth)
- [Auth0](/docs/guides/auth/third-party/auth0)
- [AWS Cognito (with or without AWS Amplify)](/docs/guides/auth/third-party/aws-cognito)
- [WorkOS](/docs/guides/auth/third-party/workos)
You can use these providers alongside Supabase Auth, or on their own, to access the [Data API (REST and GraphQL)](/docs/guides/database), [Storage](/docs/guides/storage), [Realtime](/docs/guides/storage) and [Functions](/docs/guides/functions) from your existing apps.
+57
View File
@@ -0,0 +1,57 @@
---
id: 'auth-third-party-workos'
title: 'WorkOS'
subtitle: 'Use WorkOS with your Supabase project'
---
WorkOS can be used as a third-party authentication provider alongside Supabase Auth, or standalone, with your Supabase project.
## Getting started
1. First you need to add an integration to connect your Supabase project with your WorkOS tenant. You will need your WorkOS issuer. The issuer is `https://api.workos.com/user_management/<your-client-id>`. Substitute your [custom auth domain](https://workos.com/docs/custom-domains/auth-api) for "api.workos.com" if configured.
2. Add a new Third-party Auth integration in your project's [Authentication settings](/dashboard/project/_/settings/auth).
3. Set up a JWT template to assign the `role: 'authenticated'` claim to your access token.
## Setup the Supabase client library
<Tabs type="underlined" queryGroup="language">
<TabPanel id="ts" label="TypeScript">
```typescript
import { createClient } from '@supabase/supabase-js'
import { createClient as createAuthKitClient } from '@workos-inc/authkit-js'
const authkit = await createAuthKitClient('WORKOS_CLIENT_ID', {
apiHostname: '<WORKOS_AUTH_DOMAIN>',
})
const supabase = createClient('https://<supabase-project>.supabase.co', 'SUPABASE_ANON_KEY', {
accessToken: async () => {
return authkit.getAccessToken()
},
})
```
</TabPanel>
</Tabs>
## Add a new Third-Party Auth integration to your project
In the dashboard navigate to your project's [Authentication settings](/dashboard/project/_/settings/auth) and find the Third-Party Auth section to add a new integration.
## Set up a JWT template to add the authenticated role.
Your Supabase project inspects the `role` claim present in all JWTs sent to it, to assign the correct Postgres role when using the Data API, Storage or Realtime authorization.
WorkOS JWTs already contain a `role` claim that corresponds to the user's role in their organization. It is necessary to adjust the `role` claim to be `"authenticated"` like Supabase expects. This can be done using JWT templates (navigate to Authentication -> Sessions -> JWT Template in the WorkOS Dashboard).
This template overrides the `role` claim to meet Supabase's expectations, and adds the WorkOS role in a new `user_role` claim:
```json
{
"role": "authenticated",
"user_role": {{organization_membership.role}}
}
```
@@ -6,7 +6,7 @@ description: 'Scalable time-series data storage and analysis'
<Admonition type="deprecation">
The `timescaledb` extension is deprecated in projects using Postgres 17. It continues to be supported in projects using Postgres 15, but will need to dropped before those projects are upgraded to Postgres 17. See the [Upgrading to Postgres 17 notes](/guides/platform/upgrading#upgrading-to-postgres-17) for more information.
The `timescaledb` extension is deprecated in projects using Postgres 17. It continues to be supported in projects using Postgres 15, but will need to dropped before those projects are upgraded to Postgres 17. See the [Upgrading to Postgres 17 notes](/docs/guides/platform/upgrading#upgrading-to-postgres-17) for more information.
</Admonition>
@@ -29,7 +29,7 @@ select pg_create_logical_replication_slot('example_slot', 'pgoutput');
This will need a **direct** connection (not a Connection Pooler) to your database and you can find the connection info in the [Dashboard](https://supabase.com/dashboard/project/_/settings/database).
You will also need to ensure that IPv6 is supported by your replication destination (or you can enable the [IPv4 add-on](/guides/platform/ipv4-address))
You will also need to ensure that IPv6 is supported by your replication destination (or you can enable the [IPv4 add-on](/docs/guides/platform/ipv4-address))
If you would prefer not to use the `postgres` user, then you can run `CREATE ROLE <user> WITH REPLICATION;` using the `postgres` user.
@@ -60,3 +60,10 @@ ALTER PUBLICATION example_pub ADD TABLE example_table;
```sql
select * from pg_stat_replication;
```
<Admonition type="note">
You can add more tables to the initial publication, but you're going to need to do a REFRESH on the subscribing database.
See https://www.postgresql.org/docs/current/sql-alterpublication.html
</Admonition>
@@ -59,7 +59,7 @@ Logical replication is typically output in 2 forms, `pgoutput` and `wal2json`. T
When using logical replication, Postgres is then configured to keep WAL files around for longer than it needs them. If the files are removed too quickly, then your `replication slot` will become inactive and, if the database receives a large number of changes in a short time, then the `replication slot` can become lost as it was not able to keep up.
In order to mitigate this, Postgres has many options and settings that can be [tweaked](/guides/database/custom-postgres-config) to manage the WAL usage effectively. Not all of these settings are user configurable as they can impact the stability of your database. For those that are, these should be considered as advanced configuration and not changed without understanding that they can cause additional disk space and resources to be used, as well as incur additional costs.
In order to mitigate this, Postgres has many options and settings that can be [tweaked](/docs/guides/database/custom-postgres-config) to manage the WAL usage effectively. Not all of these settings are user configurable as they can impact the stability of your database. For those that are, these should be considered as advanced configuration and not changed without understanding that they can cause additional disk space and resources to be used, as well as incur additional costs.
| Setting | Description | User-facing | Default |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------ | ----------- | ------- |
@@ -13,7 +13,7 @@ Connections through a pooler, such as Supavisor, will not work.
# The tool in use does not support IPv6
You can enable the [IPv4 add-on](/guides/platform/ipv4-address) for your project.
You can enable the [IPv4 add-on](/docs/guides/platform/ipv4-address) for your project.
# What is XMIN and should it be used?
@@ -29,7 +29,7 @@ You can view [publications](https://supabase.com/dashboard/project/default/datab
# How to configure database settings for replication?
Yes. Using the Supabase CLI, you can [configure database settings](/guides/database/custom-postgres-config#cli-configurable-settings) to optimize them for your replication needs. These values can vary depending on the activity of your database size and activity.
Yes. Using the Supabase CLI, you can [configure database settings](/docs/guides/database/custom-postgres-config#cli-configurable-settings) to optimize them for your replication needs. These values can vary depending on the activity of your database size and activity.
# What are some important configuration options?
@@ -4,12 +4,12 @@ title: 'Monitoring replication'
Monitoring replication lag is important and there are 3 ways to do this:
1. Dashboard - Under the [Reports](/guides/platform/reports) of the dashboard, you can view the replication lag of your project
1. Dashboard - Under the [Reports](/docs/guides/platform/reports) of the dashboard, you can view the replication lag of your project
2. Database -
- pg_stat_subscription (subscriber) - if PID is null, then the subscription is not active
- pg_stat_subscription_stats - look here for error_count to see if there were issues applying or syncing (if yes, check the logs for why)
- pg_replication_slots - use this to check if the slot is active and you can also calculate the lag from here
3. [Metrics](/guides/telemetry/metrics) - Using the prometheus endpoint for your project
3. [Metrics](/docs/guides/telemetry/metrics) - Using the prometheus endpoint for your project
- replication_slots_max_lag_bytes - this is the more important one
- pg_stat_replication_replay_lag - lag to replay WAL files from the source DB on the target DB (throttled by disk or high activity)
- pg_stat_replication_send_lag - lag in sending WAL files from the source DB (a high lag means that the publisher is not being asked to send new WAL files OR a network issues)
@@ -68,7 +68,7 @@ Materialize has the following [documentation](https://materialize.com/docs/sql/c
You can follow those steps with the following modifications:
1. Follow the steps in our [guide](/guides/database/postgres/setup-replication-external) to create a publication slot
1. Follow the steps in our [guide](/docs/guides/database/postgres/setup-replication-external) to create a publication slot
</TabPanel>
@@ -716,7 +716,7 @@ You can make a similar setup with a distinct project for each environment. Or ju
Branching is available on the Pro Plan and above. The price is:
- Each Preview branch costs <Price price="0.32" /> per day
- Each Preview branch costs <Price price="0.01344" /> per hour
- Each Preview branch is billed until it is removed
## Troubleshooting
@@ -23,6 +23,7 @@ After developing your project and deciding it's Production Ready, you should run
- Ensure that your Supabase Account is protected with multi-factor authentication (MFA).
- If using a GitHub signin, [enable 2FA on GitHub](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication). Since your GitHub account gives you administrative rights to your Supabase org, you should protect it with a strong password and 2FA using a U2F key or a TOTP app.
- If using email+password signin, set up [MFA for your Supabase account](https://supabase.com/docs/guides/platform/multi-factor-authentication#enable-mfa).
- Enable [MFA enforcement on your organization](/docs/guides/platform/network-restrictions). This ensures all users must have a valid MFA backed session to interact with organization and project resources.
- Consider [adding multiple owners on your Supabase org](https://supabase.com/dashboard/org/_/team). This ensures that if one of the owners is unreachable or loses access to their account, you still have Owner access to your org.
- Ensure email confirmations are [enabled](https://supabase.com/dashboard/project/_/auth/providers) in the `Settings > Auth` page.
- Ensure that you've [set the expiry](https://supabase.com/dashboard/project/_/auth/providers) for one-time passwords (OTPs) to a reasonable value that you are comfortable with. We recommend setting this to 3600 seconds (1 hour) or lower.
@@ -92,6 +92,7 @@ You can use Supabase to store and process Protected Health Information (PHI). Yo
- Signing a Business Associate Agreement (BAA) with Supabase. Submit a [HIPAA add-on request](https://forms.supabase.com/hipaa2) to get started. You will need to be at least on the [Team Plan](https://supabase.com/pricing) to sign a BAA with us.
- [Marking specific projects as HIPAA projects](/docs/guides/platform/hipaa-projects) and addressing security issues raised by the advisor.
- Ensuring [MFA is enabled](/docs/guides/platform/multi-factor-authentication) on all Supabase accounts.
- [Enforce MFA](/docs/guides/platform/org-mfa-enforcement) as a requirement to access the organization
- Enabling [Point in Time Recovery](/docs/guides/platform/backups#point-in-time-recovery) which requires at least a [small compute add-on](/docs/guides/platform/compute-add-ons).
- Turning on [SSL Enforcement](/docs/guides/platform/ssl-enforcement).
- Enabling [Network Restrictions](/docs/guides/platform/network-restrictions).
@@ -50,6 +50,8 @@ Deno.serve(async (req) => {
return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })
} catch (e) {
Sentry.captureException(e)
// Flush Sentry before the running process closes
await Sentry.flush(2000)
return new Response(JSON.stringify({ msg: 'error' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
@@ -81,9 +83,4 @@ Find the complete example on [GitHub](https://github.com/supabase/supabase/tree/
## Working with scopes
Sentry Deno SDK currently do not support `Deno.serve` instrumentation, which means that there is no scope separation between requests.
Because of that, when the Edge Functions runtime is reused between multiple requests, all globally captured breadcrumbs and contextual data
will be shared, which is not the desired behavior. To work around this, all default integrations in the example code above are disabled,
and you should be relying on [`withScope`](https://docs.sentry.io/platforms/javascript/enriching-events/scopes/#using-withscope) to encapsulate
all Sentry SDK API calls, or [pass context directly](https://docs.sentry.io/platforms/javascript/enriching-events/context/#passing-context-directly)
to the `captureException` or `captureMessage` calls.
Sentry Deno SDK currently do not support `Deno.serve` instrumentation, which means that there is no scope separation between requests. Because of that, when the Edge Functions runtime is reused between multiple requests, all globally captured breadcrumbs and contextual data will be shared, which is not the desired behavior. To work around this, all default integrations in the example code above are disabled, and you should be relying on [`withScope`](https://docs.sentry.io/platforms/javascript/enriching-events/scopes/#using-withscope) to encapsulate all Sentry SDK API calls, or [pass context directly](https://docs.sentry.io/platforms/javascript/enriching-events/context/#passing-context-directly) to the `captureException` or `captureMessage` calls.
@@ -75,6 +75,35 @@ You will also need to set secrets for your production Edge Functions. You can do
}}
/>
### Using the Management API
You can also manage secrets programmatically using the Management API:
```bash
# First, get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Create a secret
curl -X POST "https://api.supabase.com/v1/projects/$PROJECT_REF/secrets" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '[{
"name": "MY_SECRET_NAME",
"value": "my-secret-value"
}]'
# List all secrets
curl -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
"https://api.supabase.com/v1/projects/$PROJECT_REF/secrets"
# Delete a secret
curl -X DELETE "https://api.supabase.com/v1/projects/$PROJECT_REF/secrets" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '["MY_SECRET_NAME"]'
```
### Using the CLI
Let's create a `.env` to help us deploy our secrets to production. In this case we'll just use the same as our local secrets:
@@ -29,7 +29,7 @@ Each Supabase project consists of several tools:
### Postgres (database)
Postgres is the core of Supabase. We do not abstract the Postgres database—you can access it and use it with full privileges. We provide tools which makes Postgres as easy to use as Firebase.
Postgres is the core of Supabase. We do not abstract the Postgres database—you can access it and use it with full privileges. We provide tools which make Postgres as easy to use as Firebase.
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
@@ -64,7 +64,7 @@ Deploy read-only databases across multiple regions, for lower latency and better
### Log drains
Export Supabase logs at to 3rd party providers and external tooling. [Docs](/docs/guides/platform/log-drains).
Export Supabase logs to 3rd party providers and external tooling. [Docs](/docs/guides/platform/log-drains).
## Studio
@@ -195,53 +195,56 @@ Features in Beta are tested by an external penetration tester for security issue
In addition to the Beta requirements, features in GA are covered by the [uptime SLA](https://supabase.com/sla).
| Product | Feature | Stage | Available on self-hosted |
| -------------- | -------------------------- | --------------- | ------------------------------------------- |
| Database | Postgres | `GA` | ✅ |
| Database | Vector Database | `GA` | ✅ |
| Database | Auto-generated Rest API | `GA` | ✅ |
| Database | Auto-generated GraphQL API | `GA` | ✅ |
| Database | Webhooks | `beta` | ✅ |
| Database | Vault | `public alpha` | ✅ |
| Platform | | `GA` | ✅ |
| Platform | Point-in-Time Recovery | `GA` | 🚧 [wal-g](https://github.com/wal-g/wal-g) |
| Platform | Custom Domains | `GA` | N/A |
| Platform | Network Restrictions | `beta` | N/A |
| Platform | SSL enforcement | `GA` | N/A |
| Platform | Branching | `public alpha` | N/A |
| Platform | Terraform Provider | `public alpha` | N/A |
| Platform | Read Replicas | `private alpha` | N/A |
| Platform | Log Drains | `public alpha` | ✅ |
| Studio | | `GA` | ✅ |
| Studio | SSO | `GA` | ✅ |
| Realtime | Postgres Changes | `GA` | ✅ |
| Realtime | Broadcast | `GA` | ✅ |
| Realtime | Presence | `GA` | ✅ |
| Realtime | Broadcast Authorization | `public beta` | ✅ |
| Realtime | Presence Authorization | `public beta` | ✅ |
| Storage | | `GA` | ✅ |
| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Smart CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Image Transformations | `GA` | ✅ |
| Storage | Resumable Uploads | `GA` | ✅ |
| Storage | S3 compatibility | `public alpha` | ✅ |
| Edge Functions | | `beta` | ✅ |
| Edge Functions | Regional Invocations | `beta` | ✅ |
| Edge Functions | NPM compatibility | `beta` | ✅ |
| Auth | | `GA` | ✅ |
| Auth | Email login | `GA` | ✅ |
| Auth | Social login | `GA` | ✅ |
| Auth | Phone login | `GA` | ✅ |
| Auth | Passwordless login | `GA` | ✅ |
| Auth | SSO with SAML | `GA` | ✅ |
| Auth | Authorization via RLS | `GA` | ✅ |
| Auth | CAPTCHA protection | `GA` | ✅ |
| Auth | Server-side Auth | `beta` | ✅ |
| CLI | | `GA` | ✅ Works with self-hosted |
| Management API | | `GA` | N/A |
| Client Library | JavaScript | `GA` | N/A |
| Client Library | Flutter | `beta` | N/A |
| Client Library | Swift | `beta` | N/A |
| Product | Feature | Stage | Available on self-hosted |
| -------------- | -------------------------- | -------------- | ------------------------------------------- |
| Database | Postgres | `GA` | ✅ |
| Database | Vector Database | `GA` | ✅ |
| Database | Auto-generated Rest API | `GA` | ✅ |
| Database | Auto-generated GraphQL API | `GA` | ✅ |
| Database | Webhooks | `beta` | ✅ |
| Database | Vault | `public alpha` | ✅ |
| Platform | | `GA` | ✅ |
| Platform | Point-in-Time Recovery | `GA` | 🚧 [wal-g](https://github.com/wal-g/wal-g) |
| Platform | Custom Domains | `GA` | N/A |
| Platform | Network Restrictions | `GA` | N/A |
| Platform | SSL enforcement | `GA` | N/A |
| Platform | Branching | `beta` | N/A |
| Platform | Terraform Provider | `public alpha` | N/A |
| Platform | Read Replicas | `GA` | N/A |
| Platform | Log Drains | `public alpha` | ✅ |
| Studio | | `GA` | ✅ |
| Studio | SSO | `GA` | ✅ |
| Realtime | Postgres Changes | `GA` | ✅ |
| Realtime | Broadcast | `GA` | ✅ |
| Realtime | Presence | `GA` | ✅ |
| Realtime | Broadcast Authorization | `public beta` | ✅ |
| Realtime | Presence Authorization | `public beta` | ✅ |
| Realtime | Broadcast from Database | `public beta` | ✅ |
| Storage | | `GA` | ✅ |
| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Smart CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Image Transformations | `GA` | ✅ |
| Storage | Resumable Uploads | `GA` | ✅ |
| Storage | S3 compatibility | `GA` | ✅ |
| Edge Functions | | `GA` | ✅ |
| Edge Functions | Regional Invocations | `GA` | ✅ |
| Edge Functions | NPM compatibility | `GA` | ✅ |
| Auth | | `GA` | ✅ |
| Auth | Email login | `GA` | ✅ |
| Auth | Social login | `GA` | ✅ |
| Auth | Phone login | `GA` | ✅ |
| Auth | Passwordless login | `GA` | ✅ |
| Auth | SSO with SAML | `GA` | ✅ |
| Auth | Authorization via RLS | `GA` | ✅ |
| Auth | CAPTCHA protection | `GA` | ✅ |
| Auth | Server-side Auth | `beta` | ✅ |
| Auth | Third-Party Auth | `GA` | ✅ |
| CLI | | `GA` | ✅ Works with self-hosted |
| Management API | | `GA` | N/A |
| Client Library | JavaScript | `GA` | N/A |
| Client Library | Flutter | `GA` | N/A |
| Client Library | Swift | `GA` | N/A |
| Client Library | Python | `beta` | N/A |
- ✅ = Fully Available
- 🚧 = Available, but requires external tools or configuration
@@ -14,6 +14,7 @@ The [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP)
- [Cline](#cline) (VS Code extension)
- [Claude desktop](#claude-desktop)
- [Claude code](#claude-code)
- [Amp](#amp)
Once connected, your AI assistants can interact with and query your Supabase projects on your behalf.
@@ -23,7 +24,9 @@ First, go to your [Supabase settings](https://supabase.com/dashboard/account/tok
## Step 2: Configure in your AI tool
MCP compatible tools can connect to Supabase using the [Supabase MCP server](https://github.com/supabase-community/supabase-mcp). Below are instructions for connecting to this server using popular AI tools:
MCP compatible tools can connect to Supabase using the [Supabase MCP server](https://github.com/supabase-community/supabase-mcp).
Follow the instructions for your AI tool to connect the Supabase MCP server. The configuration below uses read-only, project-scoped mode by default. We recommend these settings to prevent the agent from making unintended changes to your database. Note that read-only mode applies only to database operations. Write operations on project-management tools, such as `create_project`, are still available.
### Cursor
@@ -53,9 +56,9 @@ MCP compatible tools can connect to Supabase using the [Supabase MCP server](htt
<div className="mb-8 not-prose [&>p]:flex [&>p]:flex-col [&>p]:gap-2">
[![Install with NPX in VS
Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=supabase&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22supabase-access-token%22%2C%22description%22%3A%22Supabase%20personal%20access%20token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40supabase%2Fmcp-server-supabase%40latest%22%5D%2C%22env%22%3A%7B%22SUPABASE_ACCESS_TOKEN%22%3A%22%24%7Binput%3Asupabase-access-token%7D%22%7D%7D)
Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=supabase&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22supabase-access-token%22%2C%22description%22%3A%22Supabase%20personal%20access%20token%22%2C%22password%22%3Atrue%7D%5D%26config%3D%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40supabase%2Fmcp-server-supabase%40latest%22%2C%22--readonly%22%2C%22--project-ref%3D%24SUPABASE_MCP_PROJECT_REF%22%5D%2C%22env%22%3A%7B%22SUPABASE_ACCESS_TOKEN%22%3A%22%24%7Binput%3Asupabase-access-token%7D%22%2C%22SUPABASE_MCP_PROJECT_REF%22%3A%22%24%7Binput%3Asupabase-project-ref%7D%22%7D%7D)
[![Install with NPX in VS Code
Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=supabase&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22supabase-access-token%22%2C%22description%22%3A%22Supabase%20personal%20access%20token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40supabase%2Fmcp-server-supabase%40latest%22%5D%2C%22env%22%3A%7B%22SUPABASE_ACCESS_TOKEN%22%3A%22%24%7Binput%3Asupabase-access-token%7D%22%7D%7D&quality=insiders)
Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=supabase&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22supabase-access-token%22%2C%22description%22%3A%22Supabase%20personal%20access%20token%22%2C%22password%22%3Atrue%7D%5D%26config%3D%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40supabase%2Fmcp-server-supabase%40latest%22%2C%22--readonly%22%2C%22--project-ref%3D%24SUPABASE_MCP_PROJECT_REF%22%5D%2C%22env%22%3A%7B%22SUPABASE_ACCESS_TOKEN%22%3A%22%24%7Binput%3Asupabase-access-token%7D%22%2C%22SUPABASE_MCP_PROJECT_REF%22%3A%22%24%7Binput%3Asupabase-project-ref%7D%22%7D%7D)
</div>
1. Open [VS Code](https://code.visualstudio.com/) and create a `.vscode` directory in your project root if it doesn't exist.
@@ -115,11 +118,84 @@ You can also add the Supabase MCP server as a locally-scoped server, which will
1. Run the following command in your terminal:
```bash
claude mcp add supabase -s local -e SUPABASE_ACCESS_TOKEN=your_token_here npx -y @supabase/mcp-server-supabase@latest
claude mcp add supabase -s local -e SUPABASE_ACCESS_TOKEN=your_token_here -- npx -y @supabase/mcp-server-supabase@latest
```
Locally-scoped servers take precedence over project-scoped servers with the same name and are stored in your project-specific user settings.
### Amp
You can add the Supabase MCP server to Amp in two ways:
#### Option 1: VSCode settings.json
1. Open "Preferences: Open User Settings (JSON)"
2. Add the following configuration:
```json
{
"amp.mcpServers": {
"supabase": {
"command": "npx",
"args": [
"-y",
"@supabase/mcp-server-supabase@latest",
"--read-only",
"--project-ref=<project-ref>"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "<personal-access-token>"
}
}
}
}
```
Replace `project-ref` and `personal-access-token` with your project ref and personal access token.
3. Save the configuration file.
4. Restart VS Code to apply the new configuration.
#### Option 2: Amp CLI
1. Edit `~/.config/amp/settings.json`
1. Add the following configuration:
```json
{
"amp.mcpServers": {
"supabase": {
"command": "npx",
"args": [
"-y",
"@supabase/mcp-server-supabase@latest",
"--read-only",
"--project-ref=<project-ref>"
],
"env": {
"SUPABASE_ACCESS_TOKEN": "<personal-access-token>"
}
}
}
}
```
Replace `project-ref` and `personal-access-token` with your project ref and personal access token.
1. Save the configuration file.
1. Restart Amp to apply the new configuration.
### Qodo Gen
1. Open [Qodo Gen](https://docs.qodo.ai/qodo-documentation/qodo-gen) chat panel in VSCode or IntelliJ.
1. Click `Connect more tools`.
1. Click `+ Add new MCP`.
1. Add the following configuration:
<$Partial path="mcp_supabase_config.mdx" variables={{ "app": "Qodo Gen" }} />
1. Click `Save`.
### Next steps
Your AI tool is now connected to Supabase using MCP. Try asking your AI assistant to create a new project, create a table, or fetch project config.
+12 -1
View File
@@ -18,7 +18,7 @@ Develop locally while running the Supabase stack on your machine.
</TabPanel><TabPanel id="yarn" label="yarn">
```sh
yarn add supabase --dev
NODE_OPTIONS=--no-experimental-fetch yarn add supabase --dev
```
</TabPanel><TabPanel id="pnpm" label="pnpm">
@@ -95,6 +95,17 @@ Develop locally while running the Supabase stack on your machine.
</Tabs>
<Admonition type="note">
As a prerequisite, you must install a container runtime compatible with Docker APIs.
- [Docker Desktop](https://docs.docker.com/desktop/) (macOS, Windows, Linux)
- [Rancher Desktop](https://rancherdesktop.io/) (macOS, Windows, Linux)
- [Podman](https://podman.io/) (macOS, Windows, Linux)
- [OrbStack](https://orbstack.dev/) (macOS)
</Admonition>
4. View your local Supabase instance at [http://localhost:54323](http://localhost:54323).
## Local development
@@ -32,13 +32,49 @@ content_path = "./supabase/templates/invite.html"
## Available email templates
There are several Auth email templates which can be configured:
There are several Auth email templates which can be configured. Each template serves a specific authentication flow:
- `auth.email.template.invite`
- `auth.email.template.confirmation`
- `auth.email.template.recovery`
- `auth.email.template.magic_link`
- `auth.email.template.email_change`
### `auth.email.template.invite`
**Default subject**: "You have been invited"
**When sent**: When a user is invited to join your application via email invitation
**Purpose**: Allows administrators to invite users who don't have accounts yet
**Content**: Contains a link for the invited user to accept the invitation and create their account
### `auth.email.template.confirmation`
**Default subject**: "Confirm Your Signup"
**When sent**: When a user signs up and needs to verify their email address
**Purpose**: Email verification for new user registrations
**Content**: Contains a confirmation link to verify the user's email address
### `auth.email.template.recovery`
**Default subject**: "Reset Your Password"
**When sent**: When a user requests a password reset
**Purpose**: Password recovery flow for users who forgot their password
**Content**: Contains a link to reset the user's password
### `auth.email.template.magic_link`
**Default subject**: "Your Magic Link"
**When sent**: When a user requests a magic link for passwordless authentication
**Purpose**: Passwordless login using email links
**Content**: Contains a secure link that automatically logs the user in when clicked
### `auth.email.template.email_change`
**Default subject**: "Confirm Email Change"
**When sent**: When a user requests to change their email address
**Purpose**: Verification for email address changes
**Content**: Contains a confirmation link to verify the new email address
### `auth.email.template.reauthentication`
**Default subject**: "Confirm Reauthentication"
**When sent**: When a user needs to re-authenticate for sensitive operations
**Purpose**: Additional verification for sensitive actions (like changing password, deleting account)
**Content**: Contains a 6-digit OTP code for verification
## Template variables
@@ -74,7 +74,7 @@ supabase db diff -f create_employees_table
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Start the local database and apply migrations">
Start the local database and then apply the migration file to see your schema changes in the local Dashboard.
Start the local database first. Then, apply the migration manually to see your schema changes in the local Dashboard.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
@@ -82,7 +82,8 @@ supabase db diff -f create_employees_table
<$CodeTabs>
```bash name=Terminal
supabase start && supabase migration up
supabase start
supabase migration up
```
</$CodeTabs>
@@ -97,28 +98,6 @@ supabase start && supabase migration up
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Stop the local database">
Before updating your schema files, stop the local development environment.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
<$CodeTabs>
```bash name=Terminal
supabase stop
```
</$CodeTabs>
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Add a new column">
Edit `supabase/schemas/employees.sql` file to add a new column to `employees` table.
</StepHikeCompact.Details>
@@ -150,7 +129,7 @@ Some entities like views and enums expect columns to be declared in a specific o
<StepHikeCompact>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Generate a new migration">
Diff existing migrations against your declared schema.
</StepHikeCompact.Details>
@@ -172,7 +151,7 @@ supabase db diff -f add_age
<StepHikeCompact>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Review the generated migration">
Verify that the generated migration contain a single incremental change.
</StepHikeCompact.Details>
@@ -193,7 +172,7 @@ alter table "public"."employees" add column "age" smallint not null;
</StepHikeCompact>
<StepHikeCompact>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Apply the pending migration">
Start the database locally and apply the pending migration.
</StepHikeCompact.Details>
@@ -203,7 +182,7 @@ alter table "public"."employees" add column "age" smallint not null;
<$CodeTabs>
```bash name=Terminal
supabase start && supabase migration up
supabase migration up
```
</$CodeTabs>
@@ -56,3 +56,13 @@ client_id = "env(GITHUB_CLIENT_ID)"
secret = "env(GITHUB_SECRET)"
redirect_uri = "" # Overrides the default auth redirectUrl.
```
### Going further
For more advanced secrets management workflows, including:
- **Using dotenvx for encrypted secrets**: Learn how to securely manage environment variables across different branches and environments
- **Branch-specific secrets**: Understand how to manage secrets for different deployment environments
- **Encrypted configuration values**: Use encrypted values directly in your `config.toml`
See the [Managing secrets for branches](/docs/guides/deployment/branching#managing-secrets-for-branches) section in our branching documentation, or check out the [dotenvx example repository](https://github.com/supabase/supabase/blob/master/examples/slack-clone/nextjs-slack-clone-dotenvx/README.md) for a complete implementation.
@@ -3,7 +3,7 @@ title: Restoring a downloaded backup locally
subtitle: Restore a backup of a remote database on a local instance to inspect and extract data
---
You can restore a downloaded backup to a local Supabase instance. This might be useful if your paused project has exceeded its [restoring time limit](/docs/guides/platform/upgrading#time-limits). You can download the latest backup, then load it locally to inspect and extract your data.
If your paused project has exceeded its [restoring time limit](/docs/guides/platform/upgrading#time-limits), you can download a backup from the dashboard and restore it to your local development environment. This might be useful for inspecting and extracting data from your paused project.
<Admonition type="caution">
@@ -32,6 +32,20 @@ This is a security measure to prevent accidental invites to accounts not managed
</Admonition>
### Viewing organization members using the Management API
You can also view organization members using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export ORG_ID="your-organization-id"
# List organization members
curl "https://api.supabase.com/v1/organizations/$ORG_ID/members" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
```
### Transferring ownership of an organization
Each Supabase organization must have at least one owner. If your organization has other owners then you can relinquish ownership and leave the organization by clicking **Leave team** in your organization's team [settings](/dashboard/org/_/team).
@@ -71,6 +71,26 @@ The Postgres utility [pg_dumpall](https://www.postgresql.org/docs/current/app-pg
You can access daily backups in the [Scheduled backups](https://supabase.com/dashboard/project/_/database/backups/scheduled) settings in the Dashboard. Pro Plan projects can access the last 7 days' worth of daily backups. Team Plan projects can access the last 14 days' worth of daily backups, while Enterprise Plan projects can access up to 30 days' worth of daily backups. Users can restore their project to any one of the backups. If you wish to generate a logical backup on your own, you can do so through the [Supabase CLI](/docs/reference/cli/supabase-db-dump).
You can also manage backups programmatically using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# List all available backups
curl -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
"https://api.supabase.com/v1/projects/$PROJECT_REF/database/backups"
# Restore from a PITR (not logical) backup (replace ISO timestamp with desired restore point)
curl -X POST "https://api.supabase.com/v1/projects/$PROJECT_REF/database/backups/restore-pitr" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"recovery_time_target_unix": "1735689600"
}'
```
#### Backup process for large databases
Databases larger than 15GB[^1], if they're on a recent build[^2] of the Supabase platform, get automatically transitioned[^3] to use daily physical backups. Physical backups are a more performant backup mechanism that lowers the overhead and impact on the database being backed up, and also avoids holding locks on objects in your database for a long period of time. While restores are unaffected, the backups created using this method cannot be downloaded from the Backups section of the dashboard.
@@ -28,6 +28,30 @@ IPv4 addresses are guaranteed to be static for ingress traffic. If your database
You can enable the IPv4 add-on in your project's [add-ons settings](/dashboard/project/_/settings/addons).
You can also manage the IPv4 add-on using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Get current IPv4 add-on status
curl -X GET "https://api.supabase.com/v1/projects/$PROJECT_REF/billing/addons" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
# Enable IPv4 add-on
curl -X POST "https://api.supabase.com/v1/projects/$PROJECT_REF/addons" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"addon_type": "ipv4"
}'
# Disable IPv4 add-on
curl -X DELETE "https://api.supabase.com/v1/projects/$PROJECT_REF/billing/addons/ipv4" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
```
<Admonition type="caution">
Note that direct database connections can experience a short amount of downtime when toggling the
add-on due to DNS reconfiguration and propagation. Generally, this should be less than a minute.
@@ -36,6 +36,8 @@ For security reasons, we will not be able to restore access to your account if y
Once you've enabled MFA for your Supabase user account, you will be prompted to enter your second factor challenge code as seen in your preferred TOTP app.
If you are an organization owner and on the Pro, Team or Enterprise plan, you can enforce that all organization members [must have MFA enabled](/docs/guides/platform/org-mfa-enforcement).
## Disable MFA
You can disable MFA for your user account under your [Supabase account settings](/dashboard/account/security). On subsequent login attempts, you will not be prompted to enter a MFA code.
@@ -18,6 +18,30 @@ If direct connections to your database [resolve to a IPv6 address](https://supab
Network restrictions can be configured in the [Database Settings](https://supabase.com/dashboard/project/_/settings/database) page. Ensure that you have [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project that you are enabling network restrictions.
## To get started via the Management API:
You can also manage network restrictions using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Get current network restrictions
curl -X GET "https://api.supabase.com/v1/projects/$PROJECT_REF/network-restrictions" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
# Update network restrictions
curl -X POST "https://api.supabase.com/v1/projects/$PROJECT_REF/network-restrictions/apply" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"db_allowed_cidrs": [
"192.168.0.1/24",
]
}'
```
## To get started via the CLI:
1. [Install](/docs/guides/cli) the Supabase CLI 1.22.0+.
@@ -0,0 +1,32 @@
---
title: 'Enforce MFA on Organization'
description: 'All users in an organization must have a valid MFA session to interact with organization resources'
---
Supabase provides multi-factor authentication (MFA) enforcement on the organization level. With MFA enforcement, you can ensure that all organization members use MFA. Members cannot interact with your organization or your organization's projects without a valid MFA-backed session.
<Admonition type="note">
MFA enforcement is only available on the [Pro, Team and Enterprise plans](https://supabase.com/pricing).
This feature is currently in limited preview. If you would like to opt-in to try it, contact support.
</Admonition>
## Manage MFA enforcement
To enable MFA on an organization, visit the [security settings](/dashboard/org/_/security) page and toggle `Require MFA to access organization` on.
- Only organization **owners** can modify this setting
- The owner must have [MFA on their own account](/docs/guides/platform/multi-factor-authentication)
- Supabase recommends creating two distinct MFA apps on your user account
<Admonition type="caution">
When MFA enforcement is enabled, users without MFA will immediately lose access all resources in the organization. The users will still be members of the organization and will regain their original permissions once they enable MFA on their account.
</Admonition>
## Personal access tokens
Personal access tokens are not affected by MFA enforcement. Personal access tokens are designed for programmatic access and issuing of these require a valid Supabase session backed by MFA, if enabled on the account.
@@ -51,6 +51,30 @@ Projects must meet these requirements to use Read Replicas:
To add a Read Replica, go to the [Infrastructure Settings page](/dashboard/project/_/settings/infrastructure) in your dashboard.
You can also manage Read Replicas using the Management API (beta functionality):
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Create a new Read Replica
curl -X POST "https://api.supabase.com/v1/projects/$PROJECT_REF/read-replicas/setup" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"region": "us-east-1"
}'
# Delete a Read Replica
curl -X POST "https://api.supabase.com/v1/projects/$PROJECT_REF/read-replicas/remove" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"database_identifier": "abcdefghijklmnopqrst"
}'
```
Projects on an XL compute add-on or larger can create up to five Read Replicas. Projects on compute add-ons smaller than XL can create up to two Read Replicas. All Read Replicas inherit the compute size of their Primary database.
### Deploying a Read Replica
@@ -18,6 +18,40 @@ Projects need to be at least on Postgres 13.3.0 to enable SSL enforcement. You c
SSL enforcement can be configured via the "Enforce SSL on incoming connections" setting under the SSL Configuration section in [Database Settings page](https://supabase.com/dashboard/project/_/settings/database) of the dashboard.
## Manage SSL enforcement via the Management API
You can also manage SSL enforcement using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Get current SSL enforcement status
curl -X GET "https://api.supabase.com/v1/projects/$PROJECT_REF/ssl-enforceemnt" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"
# Enable SSL enforcement
curl -X PUT "https://api.supabase.com/v1/projects/$PROJECT_REF/ssl-enforcement" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"requestedConfig": {
"database": true
}
}'
# Disable SSL enforcement
curl -X PUT "https://api.supabase.com/v1/projects/$PROJECT_REF/ssl-enforcement" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"requestedConfig": {
"database": false
}
}'
```
## Manage SSL enforcement via the CLI
To get started:
@@ -8,6 +8,12 @@ The [Health Insurance Portability and Accountability Act (HIPAA)](https://www.hh
Under HIPAA, both covered entities and business associates have distinct responsibilities to ensure the protection of PHI. Supabase acts as a business associate for customers (the covered entity) who wish to provide healthcare related services. As a business associate, Supabase has a number of obligations and has undergone auditing of the security and privacy controls that are in place to meet these. Supabase has signed a Business Associate Agreement (BAA) with all of our vendors who would have access to ePHI, such as AWS, and ensure that we follow their terms listed in the agreements. Similarly when a customer signs a BAA with us, they have some responsibilities they agree to when using Supabase to store PHI.
<Admonition type="caution">
The hosted Supabase platform has the necessary controls to meet HIPAA requirements. These controls are not supported out of the box in self-hosted Supabase. HIPAA controls extend further than the Supabase product, encompassing legal agreements (BAAs) with providers, operating controls and policies. Achieving HIPAA compliance with self-hosted Supabase is out of scope for this documentation and you should consult your auditor for further guidance.
</Admonition>
### Customer responsibilities
Covered entities (the customer) are organizations that directly handle PHI, such as health plans, healthcare clearinghouses, and healthcare providers that conduct certain electronic transactions.
+22 -2
View File
@@ -22,10 +22,30 @@ The endpoint discussed in this article is not available on self-hosted.
## Accessing the metrics endpoint
Your project's metrics endpoint is accessible at `https://<project-ref>.supabase.co/customer/v1/privileged/metrics`. Access to the endpoint is secured via HTTP Basic Auth; the username is `service_role`, while the password is the service role JWT available through the Supabase dashboard.
Your project's metrics endpoint is accessible at `https://<project-ref>.supabase.co/customer/v1/privileged/metrics`.
Access to the endpoint is secured via HTTP Basic Auth:
- username: `service_role`
- password: the service role JWT from the [Supabase dashboard](https://supabase.com/dashboard/project/_/settings/api-keys)
You can also retrieve your service role key programmatically using the Management API:
```bash
# Get your access token from https://supabase.com/dashboard/account/tokens
export SUPABASE_ACCESS_TOKEN="your-access-token"
export PROJECT_REF="your-project-ref"
# Get project API keys including service_role key
curl -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
"https://api.supabase.com/v1/projects/$PROJECT_REF/api-keys?reveal=true"
```
<ProjectConfigVariables variable="url" />
```shell
> curl https://<project-ref>.supabase.co/customer/v1/privileged/metrics --user 'service_role:<service-role-jwt>'
curl <project-url>/customer/v1/privileged/metrics \
--user 'service_role:<service-role-jwt>'
```
## Supabase Grafana
+6 -1
View File
@@ -18,7 +18,12 @@ type Database = {
DatabaseGenerated['public']['Functions']['search_content']['Returns'][number],
'subsections' | 'metadata'
> & {
metadata: { language?: string; methodName?: string; platform?: string }
metadata: {
subtitle?: string
language?: string
methodName?: string
platform?: string
}
subsections: Array<{ title?: string; href?: string; content?: string }>
}
>
+1
View File
@@ -25,6 +25,7 @@
"postbuild": "pnpm run build:sitemap && pnpm run build:llms && ./../../scripts/upload-static-assets.sh",
"prebuild": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples",
"predev": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples",
"preembeddings": "pnpm run codegen:references",
"preinstall": "npx only-allow pnpm",
"presync": "pnpm run codegen:graphql",
"pretest": "pnpm run codegen:examples",
+1
View File
@@ -86,6 +86,7 @@ Qiao Han
Raminder Singh
Riccardo Busetti
Rodrigo Mansueli
Ronan Lehane
Rory Wilding
Sam Rose
Sergio Cioban Filho
+8 -2
View File
@@ -6,7 +6,7 @@ import type {
RootQueryTypeErrorsArgs,
Service,
} from '~/__generated__/graphql'
import { ApiError, convertUnknownToApiError } from '~/app/api/utils'
import { ApiError, convertUnknownToApiError, extractMessageFromAnyError } from '~/app/api/utils'
import { Result } from '~/features/helpers.fn'
import {
createCollectionType,
@@ -77,7 +77,13 @@ async function resolveErrors(
code: args[0].code ?? undefined,
},
})
return result.mapError((error) => new ApiError('Failed to resolve error codes', error))
return result.mapError(
(error) =>
new ApiError(
`Failed to resolve error codes: ${extractMessageFromAnyError(error)}`,
error
)
)
}
return await GraphQLCollectionBuilder.create<
ErrorModel,
@@ -8,6 +8,7 @@ import {
DB_METADATA_TAG_PLATFORM_CLI,
ReferenceCLICommandModel,
} from '../reference/referenceCLIModel'
import { ReferenceManagementApiModel } from '../reference/referenceManagementApiModel'
import { ReferenceSDKFunctionModel, SDKLanguageValues } from '../reference/referenceSDKModel'
import { TroubleshootingModel } from '../troubleshooting/troubleshootingModel'
import { SearchResultInterface } from './globalSearchInterface'
@@ -74,6 +75,13 @@ function createModelFromMatch({
content,
subsections,
})
// TODO [Charis 2025-06-09] replace with less hacky check
} else if (metadata.subtitle?.startsWith('Management API Reference')) {
return new ReferenceManagementApiModel({
title: page_title,
href,
content,
})
} else {
return null
}
@@ -0,0 +1,13 @@
import { type SearchResultInterface } from '../globalSearch/globalSearchInterface'
export class ReferenceManagementApiModel implements SearchResultInterface {
public title?: string
public href?: string
public content?: string
constructor({ title, href, content }: { title?: string; href?: string; content?: string }) {
this.title = title
this.href = href
this.content = content
}
}
@@ -0,0 +1,25 @@
import { GraphQLObjectType, GraphQLString } from 'graphql'
import { GraphQLInterfaceTypeSearchResult } from '../globalSearch/globalSearchSchema'
import { ReferenceManagementApiModel } from './referenceManagementApiModel'
export const GraphQLObjectTypeReferenceManagementApi = new GraphQLObjectType({
name: 'ManagementApiReference',
interfaces: [GraphQLInterfaceTypeSearchResult],
isTypeOf: (value: unknown) => value instanceof ReferenceManagementApiModel,
description:
'A reference document containing a description of a Supabase Management API endpoint',
fields: {
title: {
type: GraphQLString,
description: 'The title of the document',
},
href: {
type: GraphQLString,
description: 'The URL of the document',
},
content: {
type: GraphQLString,
description: 'The content of the reference document, as text',
},
},
})
+2
View File
@@ -10,6 +10,7 @@ import { errorRoot, errorsRoot } from './error/errorResolver'
import { searchRoot } from './globalSearch/globalSearchResolver'
import { GraphQLObjectTypeGuide } from './guide/guideSchema'
import { GraphQLObjectTypeReferenceCLICommand } from './reference/referenceCLISchema'
import { GraphQLObjectTypeReferenceManagementApi } from './reference/referenceManagementApiSchema'
import { GraphQLObjectTypeReferenceSDKFunction } from './reference/referenceSDKSchema'
import { GraphQLObjectTypeTroubleshooting } from './troubleshooting/troubleshootingSchema'
@@ -43,6 +44,7 @@ export const rootGraphQLSchema = new GraphQLSchema({
types: [
GraphQLObjectTypeGuide,
GraphQLObjectTypeReferenceCLICommand,
GraphQLObjectTypeReferenceManagementApi,
GraphQLObjectTypeReferenceSDKFunction,
GraphQLObjectTypeTroubleshooting,
],
+103 -28
View File
@@ -8,6 +8,7 @@ import type {
IFunctionDefinition,
ISpec,
} from '../../../components/reference/Reference.types.js'
import { getApiEndpointById } from '../../../features/docs/Reference.generated.singleton.js'
import type { CliCommand, CliSpec } from '../../../generator/types/CliSpec.js'
import { flattenSections } from '../../../lib/helpers.js'
import { enrichedOperation, gen_v3 } from '../../../lib/refGenerator/helpers.js'
@@ -39,30 +40,35 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
const specSections = this.getSpecSections(specContents)
const sections = flattenedRefSections
.map((refSection) => {
const specSection = this.matchSpecSection(specSections, refSection.id)
const sections = (
await Promise.all(
flattenedRefSections.map(async (refSection) => {
const specSection = await this.matchSpecSection(specSections, refSection.id)
if (!specSection) {
return
}
if (!specSection) {
return
}
return this.sourceConstructor(
this.source,
`${this.path}/${refSection.slug}`,
refSection,
specSection,
this.enhanceMeta(specSection)
)
})
.filter((item): item is ReferenceSource<SpecSection> => item !== undefined)
return this.sourceConstructor(
this.source,
`${this.path}/${refSection.slug}`,
refSection,
specSection,
this.enhanceMeta(specSection)
)
})
)
).filter((item): item is ReferenceSource<SpecSection> => item !== undefined)
return sections as BaseSource[]
}
abstract getSpecSections(specContents: string): SpecSection[]
abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection | undefined
enhanceMeta(section: SpecSection): Json {
abstract matchSpecSection(
specSections: SpecSection[],
id: string
): SpecSection | undefined | Promise<SpecSection | undefined>
enhanceMeta(_section: SpecSection): Json {
return this.meta
}
}
@@ -115,7 +121,7 @@ export abstract class ReferenceSource<SpecSection> extends BaseSource {
abstract extractSubtitle(): string
}
export class OpenApiReferenceLoader extends ReferenceLoader<enrichedOperation> {
export class OpenApiReferenceLoader extends ReferenceLoader<Partial<enrichedOperation>> {
constructor(
source: string,
path: string,
@@ -136,39 +142,108 @@ export class OpenApiReferenceLoader extends ReferenceLoader<enrichedOperation> {
return generatedSpec.operations
}
matchSpecSection(operations: enrichedOperation[], id: string): enrichedOperation | undefined {
return operations.find((operation) => operation.operationId === id)
async matchSpecSection(
_operations: enrichedOperation[],
id: string
): Promise<Partial<enrichedOperation> | undefined> {
const apiEndpoint = await getApiEndpointById(id)
if (!apiEndpoint) return undefined
const enrichedOp: Partial<enrichedOperation> = {
operationId: apiEndpoint.id,
operation: apiEndpoint.method,
path: apiEndpoint.path,
summary: apiEndpoint.summary,
description: apiEndpoint.description,
deprecated: apiEndpoint.deprecated,
parameters: apiEndpoint.parameters as any,
requestBody: apiEndpoint.requestBody as any,
responses: apiEndpoint.responses as any,
}
return enrichedOp
}
}
export class OpenApiReferenceSource extends ReferenceSource<enrichedOperation> {
export class OpenApiReferenceSource extends ReferenceSource<Partial<enrichedOperation>> {
formatSection(specOperation: enrichedOperation, _: ICommonItem) {
const { summary, description, operation, path, tags } = specOperation
const { summary, description, operation, path, tags, parameters, responses, operationId } =
specOperation
return JSON.stringify({
summary,
description,
operation,
path,
tags,
parameters,
responses,
operationId,
})
}
extractSubtitle() {
return `${this.meta.title}: ${this.specSection.description}`
return `${this.meta.title}: ${this.specSection.description || this.specSection.operationId || ''}`
}
extractTitle() {
return (
this.specSection.summary ||
(typeof this.meta.title === 'string' ? this.meta.title : this.specSection.operation)
(typeof this.meta.title === 'string' ? this.meta.title : this.specSection.operation) ||
''
)
}
extractIndexedContent(): string {
const { summary, description, operation, tags } = this.specSection
return `# ${this.meta.title ?? ''}\n\n${summary ?? ''}\n\n${description ?? ''}\n\n${operation ?? ''}\n\n${
tags?.join(', ') ?? ''
}`
const { summary, description, operation, tags, path, parameters, responses } = this.specSection
const sections: string[] = []
// Title
sections.push(`# ${this.meta.title ?? ''}`)
// Summary
if (summary) {
sections.push(summary)
}
// Description
if (description) {
sections.push(`Description: ${description}`)
}
// Path and Method
if (path) {
sections.push(`Path: ${operation?.toUpperCase() || 'GET'} ${path}`)
}
// Parameters
if (parameters && parameters.length > 0) {
const paramList = parameters
.map((param: any) => {
const required = param.required ? 'required' : 'optional'
return `- ${param.name} (${param.schema?.type || 'string'}, ${required}): ${param.description || ''}`
})
.join('\n')
sections.push(`Parameters:\n${paramList}`)
}
// Response Types
if (responses) {
const responseList = Object.entries(responses)
.map(([code, response]: [string, any]) => {
const desc = response.description || 'No description'
return `- ${code}: ${desc}`
})
.join('\n')
sections.push(`Responses:\n${responseList}`)
}
// Tags
if (tags && tags.length > 0) {
sections.push(`Tags: ${tags.join(', ')}`)
}
return sections.filter(Boolean).join('\n\n')
}
}
+69 -1
View File
@@ -1,7 +1,7 @@
clispec: '001'
info:
id: cli
version: 2.24.3
version: 2.26.9
title: Supabase CLI
language: sh
source: https://github.com/supabase/cli
@@ -1366,6 +1366,7 @@ commands:
- local-dev
links: []
subcommands:
- supabase-migration-down
- supabase-migration-fetch
- supabase-migration-list
- supabase-migration-new
@@ -1644,6 +1645,31 @@ commands:
name: --local
description: Fetches migration history from the local database.
default_value: 'false'
- id: supabase-migration-down
title: supabase migration down
summary: Resets applied migrations up to the last n versions
tags: []
links: []
usage: supabase migration down [flags]
subcommands: []
flags:
- id: db-url
name: --db-url <string>
description: |
Resets applied migrations on the database specified by the connection string (must be percent-encoded).
default_value: ''
- id: last
name: --last <uint>
description: Reset up to the last n migration versions.
default_value: '1'
- id: linked
name: --linked
description: Resets applied migrations on the linked project.
default_value: 'false'
- id: local
name: --local
description: Resets applied migrations on the local database.
default_value: 'true'
- id: supabase-logout
title: supabase logout
summary: Log out and delete access tokens locally
@@ -3016,6 +3042,10 @@ commands:
description: |
Resets the database specified by the connection string (must be percent-encoded).
default_value: ''
- id: last
name: --last <uint>
description: Reset up to the last n migration versions.
default_value: '0'
- id: linked
name: --linked
description: Resets the linked project with local migrations.
@@ -3876,3 +3906,41 @@ commands:
name: -p, --password <string>
description: Password to your remote Postgres database.
default_value: ''
- id: supabase-backups
title: supabase backups
summary: Manage Supabase physical backups
tags:
- management-api
links: []
subcommands:
- supabase-backups-list
- supabase-backups-restore
flags: []
- id: supabase-backups-restore
title: supabase backups restore
summary: Restore to a specific timestamp using PITR
tags: []
links: []
usage: supabase backups restore [flags]
subcommands: []
flags:
- id: timestamp
name: -t, --timestamp <int>
description: The recovery time target in seconds since epoch.
default_value: '0'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ''
- id: supabase-backups-list
title: supabase backups list
summary: Lists available physical backups
tags: []
links: []
usage: supabase backups list
subcommands: []
flags:
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ''
+4 -4
View File
@@ -895,14 +895,14 @@ parameters:
- name: 'Auth Server configuration'
link: 'https://supabase.com/docs/reference/auth'
- id: 'auth.email.otp_exp'
title: 'auth.email.otp_exp'
- id: 'auth.email.otp_expiry'
title: 'auth.email.otp_expiry'
tags: ['auth']
required: false
default: '300'
default: '3600'
description: |
The expiry time for an OTP code in seconds.
Default is 300 seconds (5 minutes).
Default is 3600 seconds (1 hour).
links:
- name: 'Auth Server configuration'
link: 'https://supabase.com/docs/reference/auth'
+1 -1
View File
@@ -4406,7 +4406,7 @@ functions:
name: Call a read-only Postgres function
code: |
```ts
const { data, error } = await supabase.rpc('hello_world', { get: true })
const { data, error } = await supabase.rpc('hello_world', undefined, { get: true })
```
data:
sql: |
+1 -1
View File
@@ -8,7 +8,7 @@
# docker builder prune
# docker build . -f apps/studio/Dockerfile --target production -t studio:latest --no-cache
FROM node:20-slim AS base
FROM node:22-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
@@ -79,7 +79,7 @@ export function ApiKeyPill({
async function onCopy() {
// If key is already revealed, use that value
if (data?.api_key) return data?.api_key
if (data?.api_key) return data?.api_key ?? ''
try {
// Fetch full key and immediately clear from cache after copying
@@ -89,13 +89,15 @@ export function ApiKeyPill({
exact: true,
})
if (result.isSuccess) return result.data.api_key
if (result.isSuccess) return result.data.api_key ?? ''
if (error) {
toast.error('Failed to copy secret API key')
return ''
}
} catch (error) {
console.error('Failed to fetch API key:', error)
return ''
}
// Fallback to the masked version if fetch fails
@@ -1,4 +1,4 @@
import { ExternalLink, Github, Mail } from 'lucide-react'
import { ExternalLink, Github } from 'lucide-react'
import { LOCAL_STORAGE_KEYS } from 'common'
import { FeatureBanner } from 'components/ui/FeatureBanner'
@@ -119,7 +119,13 @@ export const ApiKeysComingSoonBanner = () => {
</p>
<div className="mt-4">
<Button type="default" icon={<Github />}>
Learn more
<a
href="https://github.com/orgs/supabase/discussions/29260"
target="_blank"
rel="noreferrer"
>
Learn more
</a>
</Button>
</div>
</div>
@@ -188,7 +194,7 @@ export const ApiKeysFeedbackBanner = () => {
<p className="text-sm text-foreground-lighter">
Having trouble with the new API keys?{' '}
<a
href="/support/new?category=Problem&subject=Help%20with%20API%20keys&message=I'm%20experiencing%20problems%20with%20the%20new%20API%20keys%20feature.%20Please%20describe%20your%20specific%20issue%20here."
href="/dashboard/support/new?category=Problem&subject=Help%20with%20API%20keys&message=I'm%20experiencing%20problems%20with%20the%20new%20API%20keys%20feature.%20Please%20describe%20your%20specific%20issue%20here."
className="inline-flex items-center gap-1 text-foreground"
>
Contact support
@@ -4,7 +4,6 @@ import {
useQueryTableCommands,
useSnippetCommands,
} from 'components/layouts/SQLEditorLayout/SqlEditor.Commands'
import { useGenerateSqlCommand } from 'components/interfaces/SqlGenerator/SqlGenerator.Commands'
import { useProjectLevelTableEditorCommands } from 'components/layouts/TableEditorLayout/TableEditor.Commands'
import { useLayoutNavCommands } from 'components/layouts/useLayoutNavCommands'
import { CommandHeader, CommandInput, CommandList, CommandMenu } from 'ui-patterns/CommandMenu'
@@ -19,7 +18,6 @@ import { useSupportCommands } from './Support'
import { orderCommandSectionsByPriority } from './ordering'
export default function StudioCommandMenu() {
useGenerateSqlCommand()
useApiKeysCommands()
useApiUrlCommand()
useProjectLevelTableEditorCommands()
@@ -8,20 +8,6 @@ export const FEATURE_PREVIEWS = [
isNew: true,
isPlatformOnly: false,
},
{
key: LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS,
name: 'Table Editor Tabs',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/35636',
isNew: true,
isPlatformOnly: false,
},
{
key: LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS,
name: 'SQL Editor Tabs',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/35636',
isNew: true,
isPlatformOnly: true,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL,
name: 'Project API documentation',
@@ -2,8 +2,6 @@ import { noop } from 'lodash'
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'
import { FeatureFlagContext, LOCAL_STORAGE_KEYS } from 'common'
import { useFlag } from 'hooks/ui/useFlag'
import { IS_PLATFORM } from 'lib/constants'
import { EMPTY_OBJ } from 'lib/void'
import { FEATURE_PREVIEWS } from './FeaturePreview.constants'
@@ -21,15 +19,10 @@ export const useFeaturePreviewContext = () => useContext(FeaturePreviewContext)
export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren<{}>) => {
const { hasLoaded } = useContext(FeatureFlagContext)
const enableTabsInterface = useFlag('tabsInterface')
// [Joshen] Similar logic to feature flagging previews, we can use flags to default opt in previews
const isDefaultOptIn = (feature: (typeof FEATURE_PREVIEWS)[number]) => {
switch (feature.key) {
case LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS:
return enableTabsInterface
case LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS:
return enableTabsInterface
default:
return false
}
@@ -86,14 +79,3 @@ export const useIsInlineEditorEnabled = () => {
const { flags } = useFeaturePreviewContext()
return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_INLINE_EDITOR]
}
export const useIsTableEditorTabsEnabled = () => {
const { flags } = useFeaturePreviewContext()
return flags[LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS]
}
export const useIsSQLEditorTabsEnabled = () => {
const { flags } = useFeaturePreviewContext()
if (!IS_PLATFORM) return false
return flags[LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS]
}
@@ -7,22 +7,17 @@ import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import { removeTabsByEditor } from 'state/tabs'
import { Badge, Button, Modal, ScrollArea, cn } from 'ui'
import { APISidePanelPreview } from './APISidePanelPreview'
import { CLSPreview } from './CLSPreview'
import { FEATURE_PREVIEWS } from './FeaturePreview.constants'
import { useFeaturePreviewContext } from './FeaturePreviewContext'
import { InlineEditorPreview } from './InlineEditorPreview'
import { SqlEditorTabsPreview } from './SqlEditorTabs'
import { TableEditorTabsPreview } from './TableEditorTabs'
const FEATURE_PREVIEW_KEY_TO_CONTENT: {
[key: string]: ReactNode
} = {
[LOCAL_STORAGE_KEYS.UI_PREVIEW_INLINE_EDITOR]: <InlineEditorPreview />,
[LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS]: <TableEditorTabsPreview />,
[LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS]: <SqlEditorTabsPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL]: <APISidePanelPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_CLS]: <CLSPreview />,
}
@@ -62,13 +57,6 @@ const FeaturePreviewModal = () => {
properties: { feature: selectedFeatureKey },
groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
})
if (ref && selectedFeatureKey === LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS) {
removeTabsByEditor(ref, 'table')
}
if (ref && selectedFeatureKey === LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS) {
removeTabsByEditor(ref, 'sql')
}
}
function handleCloseFeaturePreviewModal() {
@@ -1,7 +1,6 @@
import Image from 'next/image'
import { BASE_PATH } from 'lib/constants'
import { Admonition } from 'ui-patterns'
export const InlineEditorPreview = () => {
return (
@@ -1,28 +0,0 @@
import { BASE_PATH } from 'lib/constants'
import Image from 'next/image'
export const SqlEditorTabsPreview = () => {
return (
<div className="space-y-2">
<Image
src={`${BASE_PATH}/img/previews/tabs-sql.png`}
width={1860}
height={970}
alt="api-docs-side-panel-preview"
className="rounded border"
/>
<p className="text-foreground-light text-sm">
The SQL Editor now features tabs for improved navigation and organization. Conveniently go
between the queries that you're focused on, and collapse the sidebar for a bigger real
estate while writing your queries.
</p>
<div className="space-y-2 !mt-4">
<p className="text-sm">Enabling this preview will:</p>
<ul className="list-disc pl-6 text-sm text-foreground-light space-y-1">
<li>Enable opening of queries in the SQL Editor as tabs</li>
<li>Support closing of the navigation sidebar for a larger code editor space</li>
</ul>
</div>
</div>
)
}
@@ -1,192 +0,0 @@
import { ExternalLink, X } from 'lucide-react'
import Image from 'next/image'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useEditorType } from 'components/layouts/editors/EditorsLayout.hooks'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { InlineLinkClassName } from 'components/ui/InlineLink'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { BASE_PATH } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import {
Alert_Shadcn_,
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Badge,
Button,
cn,
HoverCard,
HoverCardContent_Shadcn_,
HoverCardTrigger_Shadcn_,
} from 'ui'
import { useIsSQLEditorTabsEnabled, useIsTableEditorTabsEnabled } from './FeaturePreviewContext'
export const TableEditorTabsPreview = () => {
return (
<div className="space-y-2">
<Image
src={`${BASE_PATH}/img/previews/tabs-editor.png`}
width={1860}
height={970}
alt="api-docs-side-panel-preview"
className="rounded border"
/>
<p className="text-foreground-light text-sm">
The Table Editor now features tabs for improved navigation and organization. Have multiple
tables opened across schemas and conveniently go across them without having to switch
schemas. Collapse the sidebar for a bigger real estate while browsing your data.
</p>
<div className="space-y-2 !mt-4">
<p className="text-sm">Enabling this preview will:</p>
<ul className="list-disc pl-6 text-sm text-foreground-light space-y-1">
<li>Enable opening of tables in the Table Editor as tabs</li>
<li>Support closing of the navigation sidebar for a larger data grid space</li>
</ul>
</div>
</div>
)
}
export const TabsUpdateCallout = () => {
const editor = useEditorType()
const appSnap = useAppStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const isTabsEnabled = isTableEditorTabsEnabled || isSQLEditorTabsEnabled
const [tableEditorTabsPreviewState] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS,
''
)
const [sqlEditorTabsPreviewState] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS,
''
)
const [tabsInterfaceAcknowledge, setTabsInterfaceAcknowledge] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.TABS_INTERFACE_ACKNOWLEDGED,
false
)
const isDefaultOptedInTabs =
isTabsEnabled && tableEditorTabsPreviewState === '' && sqlEditorTabsPreviewState === ''
if (!isDefaultOptedInTabs || tabsInterfaceAcknowledge) return null
return (
<Alert_Shadcn_ className="mb-4 relative">
<AlertTitle_Shadcn_>
<Badge variant="brand" className="mr-2">
NEW
</Badge>
Tabs Interface for Editors
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
The Table and SQL Editors now feature tabs for improved navigation and organization! Check
out our{' '}
<span
className={cn(InlineLinkClassName, 'cursor-pointer')}
onClick={() => {
appSnap.setSelectedFeaturePreview(
editor === 'table'
? LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS
: LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS
)
appSnap.setShowFeaturePreviewModal(true)
}}
>
feature previews
</span>{' '}
for more information.
</AlertDescription_Shadcn_>
<AlertDescription_Shadcn_ className="mt-4 flex items-center gap-x-2">
<Button asChild type="default" icon={<ExternalLink />}>
<a
target="_blank"
rel="noreferrer noopener"
href="https://github.com/orgs/supabase/discussions/35636"
>
View announcement
</a>
</Button>
</AlertDescription_Shadcn_>
<ButtonTooltip
type="text"
icon={<X />}
className="absolute top-2 right-2 px-1"
onClick={() => setTabsInterfaceAcknowledge(true)}
tooltip={{ content: { side: 'bottom', text: 'Dismiss' } }}
/>
</Alert_Shadcn_>
)
}
export const TabsUpdateTooltip = () => {
const editor = useEditorType()
const appSnap = useAppStateSnapshot()
const isTableEditorTabsEnabled = useIsTableEditorTabsEnabled()
const isSQLEditorTabsEnabled = useIsSQLEditorTabsEnabled()
const isTabsEnabled = isTableEditorTabsEnabled || isSQLEditorTabsEnabled
const [tableEditorTabsPreviewState] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS,
''
)
const [sqlEditorTabsPreviewState] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS,
''
)
const [tabsInterfaceAcknowledge, setTabsInterfaceAcknowledge] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.TABS_INTERFACE_ACKNOWLEDGED,
false
)
const isDefaultOptedInTabs =
isTabsEnabled && tableEditorTabsPreviewState === '' && sqlEditorTabsPreviewState === ''
if (!isDefaultOptedInTabs || tabsInterfaceAcknowledge) return null
return (
<HoverCard openDelay={150}>
<HoverCardTrigger_Shadcn_ asChild>
<div
className={cn(
'flex items-center justify-center px-2 h-10 shrink-0',
'border-b border-b-default gap-x-2'
)}
>
<Badge variant="brand" className="rounded">
<span>NEW</span>
</Badge>
</div>
</HoverCardTrigger_Shadcn_>
<HoverCardContent_Shadcn_ className="p-3 w-72">
<div className="flex flex-col gap-y-1">
<p className="text-sm">Tabs Interface for Editors</p>
<p className="text-xs text-foreground-light">
The Table and SQL Editors now feature tabs for improved navigation and organization!
Check out our{' '}
<span
className={cn(InlineLinkClassName, 'cursor-pointer')}
onClick={() => {
appSnap.setSelectedFeaturePreview(
editor === 'table'
? LOCAL_STORAGE_KEYS.UI_TABLE_EDITOR_TABS
: LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS
)
appSnap.setShowFeaturePreviewModal(true)
}}
>
feature previews
</span>{' '}
for more information.
</p>
</div>
<Button type="default" className="mt-2" onClick={() => setTabsInterfaceAcknowledge(true)}>
Dismiss
</Button>
</HoverCardContent_Shadcn_>
</HoverCard>
)
}
@@ -2,7 +2,7 @@ import { useRouter } from 'next/router'
import { PropsWithChildren, useEffect } from 'react'
import { toast } from 'sonner'
import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useParams } from 'common'
import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useIsMFAEnabled, useParams } from 'common'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import useLatest from 'hooks/misc/useLatest'
@@ -18,6 +18,7 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
const isLoggedIn = useIsLoggedIn()
const snap = useAppStateSnapshot()
const isUserMFAEnabled = useIsMFAEnabled()
const organization = useSelectedOrganization()
@@ -122,7 +123,17 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
}, [isSuccessStorage, ref])
useEffect(() => {
if (organization) setLastVisitedOrganization(organization.slug)
if (organization) {
setLastVisitedOrganization(organization.slug)
if (
organization.organization_requires_mfa &&
!isUserMFAEnabled &&
router.pathname !== '/org/[slug]'
) {
router.push(`/org/${organization.slug}`)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [organization])
@@ -119,7 +119,7 @@ export const UsersV2 = () => {
}
)
const { data: countData } = useUsersCountQuery({
const { data: countData, refetch: refetchCount } = useUsersCountQuery({
projectRef,
connectionString: project?.connectionString,
keywords: filterKeywords,
@@ -437,7 +437,10 @@ export const UsersV2 = () => {
icon={<RefreshCw />}
type="default"
loading={isRefetching && !isFetchingNextPage}
onClick={() => refetch()}
onClick={() => {
refetch()
refetchCount()
}}
>
Refresh
</Button>
@@ -1,18 +1,44 @@
export const USAGE_APPROACHING_THRESHOLD = 0.8
export const CANCELLATION_REASONS = [
'Pricing',
"My project isn't getting traction",
'Poor customer service',
'Missing feature',
"I didn't see the value",
"Supabase didn't meet my needs",
'Dashboard is too complicated',
'Postgres is too complicated',
'Problem not solved',
'Too many bugs/issues',
'I decided to use something else',
'My work has finished/discontinued',
'I’m migrating to/starting a new project',
'None of the above',
{
value: 'I was just exploring, or it was a hobby/student project.',
},
{
value: 'I was not satisfied with the customer support I received.',
label: 'Could you tell us more about your experience with our support team?',
},
{
value: 'Supabase is missing a specific feature I need.',
label: 'What specific feature(s) are we missing?',
},
{
value: 'I found it difficult to use or build with.',
label: 'What specific parts of Supabase did you find difficult or frustrating?',
},
{
value: 'Performance or reliability insufficient.',
label:
'Could you tell us more about the specific issues you encountered (e.g., UI bugs, API latency, downtime)?',
},
{
value: 'My project was cancelled or put on hold.',
},
{
value: 'Too expensive',
label: 'We appreciate your perspective on our pricing, what aspects of the cost felt too high?',
},
{
value: 'The pricing is unpredictable and hard to budget for.',
label:
'Which aspects of our pricing model made it difficult for you to predict your monthly costs?',
},
{
value: 'My company went out of business or was acquired.',
},
{
value: 'I lost trust in the company or its future direction.',
label:
'Building and maintaining your trust is our highest priority, could you please share the specific event or reason that led to this loss of trust?',
},
]
@@ -11,6 +11,7 @@ import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { STRIPE_PUBLIC_KEY } from 'lib/constants'
import { useIsHCaptchaLoaded } from 'stores/hcaptcha-loaded-store'
import AddPaymentMethodForm from './AddPaymentMethodForm'
import { getStripeElementsAppearanceOptions } from './Payment.utils'
interface AddNewPaymentMethodModalProps {
visible: boolean
@@ -90,7 +91,7 @@ const AddNewPaymentMethodModal = ({
const options = {
clientSecret: intent ? intent.client_secret : '',
appearance: { theme: resolvedTheme?.includes('dark') ? 'night' : 'flat', labels: 'floating' },
appearance: getStripeElementsAppearanceOptions(resolvedTheme),
} as any
const onLocalCancel = () => {
@@ -115,7 +115,12 @@ const AddPaymentMethodForm = ({
<Modal.Content
className={`transition ${isSaving ? 'pointer-events-none opacity-75' : 'opacity-100'}`}
>
<PaymentElement className="[.p-LinkAutofillPrompt]:pt-0" />
<PaymentElement
className="[.p-LinkAutofillPrompt]:pt-0"
options={{
defaultValues: { billingDetails: { email: selectedOrganization?.billing_email ?? '' } },
}}
/>
{showSetDefaultCheckbox && (
<div className="flex items-center gap-x-2 mt-4 mb-2">
<Checkbox_Shadcn_
@@ -0,0 +1,29 @@
import type { Appearance } from '@stripe/stripe-js'
export const getStripeElementsAppearanceOptions = (
resolvedTheme: string | undefined
): Appearance => {
return {
theme: (resolvedTheme?.includes('dark') ? 'night' : 'flat') as 'night' | 'flat',
variables: {
fontSizeBase: '14px',
colorBackground: resolvedTheme?.includes('dark')
? 'hsl(0deg 0% 14.1%)'
: 'hsl(0deg 0% 95.3%)',
fontFamily:
'var(--font-custom, Circular, custom-font, Helvetica Neue, Helvetica, Arial, sans-serif)',
spacingUnit: '4px',
borderRadius: '.375rem',
gridRowSpacing: '4px',
},
rules: {
'.Label': {
// Hide labels - it is obvious enough what the fields are for
fontSize: '0',
},
'.TermsText': {
fontSize: '12px',
},
},
}
}
@@ -7,11 +7,9 @@ export const PaymentConfirmation = ({
paymentIntentSecret,
onPaymentIntentConfirm,
onLoadingChange,
paymentMethodId,
onError,
}: {
paymentIntentSecret: string
paymentMethodId: string
onPaymentIntentConfirm: (response: PaymentIntentResult) => void
onLoadingChange: (loading: boolean) => void
onError?: (error: Error) => void
@@ -22,7 +20,7 @@ export const PaymentConfirmation = ({
if (stripe && paymentIntentSecret) {
onLoadingChange(true)
stripe!
.confirmCardPayment(paymentIntentSecret, { payment_method: paymentMethodId })
.confirmCardPayment(paymentIntentSecret)
.then((res) => {
onPaymentIntentConfirm(res)
onLoadingChange(false)
@@ -1,4 +1,4 @@
import type { OrgSubscription, ProjectSelectedAddon } from 'data/subscriptions/types'
import type { OrgSubscription, PlanId, ProjectSelectedAddon } from 'data/subscriptions/types'
import { IS_PLATFORM } from 'lib/constants'
export const getAddons = (selectedAddons: ProjectSelectedAddon[]) => {
@@ -32,3 +32,43 @@ export const billingPartnerLabel = (billingPartner?: string) => {
return billingPartner
}
}
type PlanChangeType = 'upgrade' | 'downgrade' | 'none'
export const getPlanChangeType = (
fromPlan: PlanId | undefined,
toPlan: PlanId | undefined
): PlanChangeType => {
const planChangeTypes: Record<PlanId, Record<PlanId, PlanChangeType>> = {
free: {
free: 'none',
pro: 'upgrade',
team: 'upgrade',
enterprise: 'upgrade',
},
pro: {
free: 'downgrade',
pro: 'none',
team: 'upgrade',
enterprise: 'upgrade',
},
team: {
free: 'downgrade',
pro: 'downgrade',
team: 'none',
enterprise: 'upgrade',
},
enterprise: {
free: 'downgrade',
pro: 'downgrade',
team: 'downgrade',
enterprise: 'none',
},
}
if (!fromPlan || !toPlan) {
return 'none'
}
return planChangeTypes[fromPlan]?.[toPlan] ?? 'none'
}
@@ -281,7 +281,7 @@ export const CreateBranchModal = ({ visible, onClose }: CreateBranchModalProps)
<DialogFooter className="sm:justify-between gap-2" padding="medium">
<p className="flex items-center gap-2 text-sm text-foreground">
<DollarSign size={16} strokeWidth={1.5} />
Each preview branch costs $0.32 per day
Each preview branch costs $0.01344 per hour
</p>
<div className="flex items-center gap-2">
<Button disabled={isCreating} type="default" onClick={onClose}>
@@ -2,11 +2,12 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { ExternalLink, Plug } from 'lucide-react'
import { parseAsBoolean, useQueryState } from 'nuqs'
import { useState } from 'react'
import { useMemo, useState } from 'react'
import { DatabaseConnectionString } from 'components/interfaces/Connect/DatabaseConnectionString'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import Panel from 'components/ui/Panel'
import { useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import { getAPIKeys, useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
@@ -137,13 +138,21 @@ export const Connect = () => {
return []
}
const protocol = settings?.app_config?.protocol ?? 'https'
const endpoint = settings?.app_config?.endpoint ?? ''
const apiHost = canReadAPIKeys ? `${protocol}://${endpoint ?? '-'}` : ''
const apiUrl = canReadAPIKeys ? apiHost : null
const { anonKey } = canReadAPIKeys ? getAPIKeys(settings) : { anonKey: null }
const projectKeys = { apiUrl, anonKey: anonKey?.api_key ?? null }
const { data: apiKeys } = useAPIKeysQuery({ projectRef, reveal: false })
const projectKeys = useMemo(() => {
const protocol = settings?.app_config?.protocol ?? 'https'
const endpoint = settings?.app_config?.endpoint ?? ''
const apiHost = canReadAPIKeys ? `${protocol}://${endpoint ?? '-'}` : ''
const apiUrl = canReadAPIKeys ? apiHost : null
return {
apiUrl: apiHost ?? null,
anonKey: anonKey?.api_key ?? null,
publishableKey: apiKeys?.find(({ type }) => type === 'publishable')?.api_key ?? null,
}
}, [apiKeys, anonKey, canReadAPIKeys, settings])
const filePath = getContentFilePath({
connectionObject,
@@ -1,12 +1,14 @@
export type projectKeys = {
apiUrl: string | null
anonKey: string | null
publishableKey: string | null
}
export interface ContentFileProps {
projectKeys: {
apiUrl: string
anonKey: string
anonKey?: string
publishableKey?: string
}
connectionStringPooler: {
transactionShared: string
@@ -239,7 +239,7 @@ export const DatabaseConnectionString = () => {
</SelectContent_Shadcn_>
</Select_Shadcn_>
</div>
<DatabaseSelector buttonProps={{ size: 'small' }} />
<DatabaseSelector portal={false} buttonProps={{ size: 'small' }} />
</div>
{isLoading && (
@@ -30,7 +30,7 @@ data class TodoItem(val id: Int, val name: String)
{`
val supabase = createSupabaseClient(
supabaseUrl = "${projectKeys.apiUrl ?? 'your-project-url'}",
supabaseKey = "${projectKeys.anonKey ?? 'your-anon-key'}"
supabaseKey = "${projectKeys.publishableKey ?? '<prefer publishable key instead of anon key for mobile apps>'}"
) {
install(Postgrest)
}
Loaded 100 of 527 files, more files were not shown because too many files have changed in this diff. Show more