- {l.filters ? '✅' : '🚫'}
{l.rls ? '✅' : '🚫'}
{Intl.NumberFormat().format(l.concurrency)}
diff --git a/apps/docs/components/SkipToContent.tsx b/apps/docs/components/SkipToContent.tsx
new file mode 100644
index 00000000000..8b31bcb2243
--- /dev/null
+++ b/apps/docs/components/SkipToContent.tsx
@@ -0,0 +1,22 @@
+import { DOCS_CONTENT_CONTAINER_ID } from '~/features/ui/helpers.constants'
+import Link from 'next/link'
+import { Button, cn } from 'ui'
+
+const SkipToContent = () => {
+ return (
+
+ Skip to content
+
+ )
+}
+
+export { SkipToContent }
diff --git a/apps/docs/components/StepHikeCompact/index.tsx b/apps/docs/components/StepHikeCompact/index.tsx
index a20e5f8d4bb..ef1a7f08af7 100644
--- a/apps/docs/components/StepHikeCompact/index.tsx
+++ b/apps/docs/components/StepHikeCompact/index.tsx
@@ -72,22 +72,46 @@ const Step: FC> = ({ children, title, step }) => {
- {children}
+
+ {children}
+
)
}
const Details: FC> = ({ children, title, fullWidth = false }) => {
return (
-
-
{title}
+
+ {title &&
{title} }
{children}
)
}
const Code: FC
> = ({ children }) => {
- return {children}
+ return (
+
+ {children}
+
+ )
}
StepHikeCompact.Step = Step
diff --git a/apps/docs/content/_partials/ai/quickstart_hf_deployment.mdx b/apps/docs/content/_partials/ai/quickstart_hf_deployment.mdx
index 595b1f8f25b..5cd2f61f7aa 100644
--- a/apps/docs/content/_partials/ai/quickstart_hf_deployment.mdx
+++ b/apps/docs/content/_partials/ai/quickstart_hf_deployment.mdx
@@ -2,4 +2,4 @@
If you have your own infrastructure for deploying Python apps, you can continue to use `vecs` as described in this guide.
-Alternatively if you would like to quickly deploy using Supabase, check out our guide on using the [Hugging Face Inference API](/docs/guides/ai/hugging-face) in Edge Functions using TypeScript.
+Alternatively if you would like to deploy using Supabase, check out our guide on using the [Hugging Face Inference API](/docs/guides/ai/hugging-face) in Edge Functions using TypeScript.
diff --git a/apps/docs/content/_partials/api_settings.mdx b/apps/docs/content/_partials/api_settings.mdx
index b60a575b2e5..15da66146f3 100644
--- a/apps/docs/content/_partials/api_settings.mdx
+++ b/apps/docs/content/_partials/api_settings.mdx
@@ -1,10 +1,12 @@
### Get API details
-Now that you've created some database tables, you are ready to insert data using the auto-generated API.
+To interact with data in database tables, you use the client libraries that wrap [the auto-generated Data API endpoints](/docs/guides/api), authenticating using the Project URL and key from [the project **Connect** dialog](/dashboard/project/\_?showConnect=true&connectTab={{ .tab }}&framework={{ .framework }}).
-To do this, you need to get the Project URL and key from [the project **Connect** dialog](/dashboard/project/\_?showConnect=true&connectTab={{ .tab }}&framework={{ .framework }}).
+
+
-[Read the API keys docs](/docs/guides/getting-started/api-keys) for a full explanation of all key types and their uses.
+
-<$Partial path="api_keys_deprecation.mdx" variables={{ "framework": "{{ .framework }}", "tab": "{{ .tab }}" }}
-/>
+[Read the API keys docs](/docs/guides/getting-started/api-keys) for a full explanation of all key types, their uses, and where to find them.
+
+
diff --git a/apps/docs/content/_partials/api_settings_steps.mdx b/apps/docs/content/_partials/api_settings_steps.mdx
deleted file mode 100644
index 19b199f746b..00000000000
--- a/apps/docs/content/_partials/api_settings_steps.mdx
+++ /dev/null
@@ -1,12 +0,0 @@
-{/* TODO: How to completely consolidate partials? */}
-
-### Get API details
-
-Now that you've created some database tables, you are ready to insert data using the auto-generated API.
-
-To do this, you need to get the Project URL and key from [the project **Connect** dialog](/dashboard/project/\_?showConnect=true&connectTab={{ .tab }}&framework={{ .framework }}).
-
-[Read the API keys docs](/docs/guides/getting-started/api-keys) for a full explanation of all key types and their uses.
-
-<$Partial path="api_keys_deprecation.mdx" variables={{ "framework": "{{ .framework }}", "tab": "{{ .tab }}" }}
-/>
diff --git a/apps/docs/content/_partials/db_pre_request_warning.mdx b/apps/docs/content/_partials/db_pre_request_warning.mdx
index 0b61bc21917..5bf6a4fc56a 100644
--- a/apps/docs/content/_partials/db_pre_request_warning.mdx
+++ b/apps/docs/content/_partials/db_pre_request_warning.mdx
@@ -22,7 +22,7 @@ on todos for select
using ( set_information() AND (select auth.uid()) = user_id );
```
-This ensures the function is called when evaluating RLS policies for all products, not just Data API requests.
+This ensures the function is called when evaluating RLS policies for all products, not only Data API requests.
**Performance consideration:**
diff --git a/apps/docs/content/_partials/kotlin_project_setup.mdx b/apps/docs/content/_partials/kotlin_project_setup.mdx
index 91b95ad86ec..546c23d80bb 100644
--- a/apps/docs/content/_partials/kotlin_project_setup.mdx
+++ b/apps/docs/content/_partials/kotlin_project_setup.mdx
@@ -10,7 +10,7 @@ Before building, you must set up your Database and API with a new Project in Sup
### Set up the database schema
-Now we are going to set up the database schema. You can just copy/paste the SQL from below and run it yourself.
+Now we are going to set up the database schema. You can copy/paste the SQL from below and run it yourself.
What you can do with the Metrics API}
+ header="What you can do with the Metrics API"
id="how-do-i-check-when-a-user-went-through-mfa"
className="border-0 px-2 py-4"
>
diff --git a/apps/docs/content/_partials/postgres_installation.mdx b/apps/docs/content/_partials/postgres_installation.mdx
index a116934ecdd..876b02094f3 100644
--- a/apps/docs/content/_partials/postgres_installation.mdx
+++ b/apps/docs/content/_partials/postgres_installation.mdx
@@ -25,7 +25,7 @@
Add the Postgres binary to your system PATH.
- In Control Panel, under the Advanced tab of System Properties, click Environment Variables. Edit the Path variable by adding the path to the SQL binary you just installed.
+ In Control Panel, under the Advanced tab of System Properties, click Environment Variables. Edit the Path variable by adding the path to the SQL binary you installed.
The path will look something like this, though it may differ slightly depending on your installed version:
diff --git a/apps/docs/content/_partials/quickstart_db_setup.mdx b/apps/docs/content/_partials/quickstart_db_setup.mdx
index eedf4167677..08e577f4446 100644
--- a/apps/docs/content/_partials/quickstart_db_setup.mdx
+++ b/apps/docs/content/_partials/quickstart_db_setup.mdx
@@ -1,84 +1,87 @@
-
+export const sqlSetup = `-- Create the table
+create table instruments (
+ id bigint primary key generated always as identity,
+ name text not null
+);
-Go to [database.new](https://database.new) and create a new Supabase project.
+-- Insert sample data into the table
+insert into instruments (name)
+values
+('violin'),
+('viola'),
+('cello');
-Alternatively, you can create a project using the Management API:
+-- Grant the privileges the role needs, which is read access
+grant select on public.instruments to anon;
-
+-- Enable row level security for the table
+alter table instruments enable row level security;
-
+-- Create a policy to allow the anon role to read from the instruments table
+create policy "public can read instruments"
+on public.instruments
+for select to anon
+using (true);`
-```bash
-# First, get your access token from https://supabase.com/dashboard/account/tokens
-export SUPABASE_ACCESS_TOKEN="your-access-token"
+## 1. Create a Supabase project
-# List your organizations to get the organization ID
-curl -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
- https://api.supabase.com/v1/organizations
+To start, you need a Supabase project.
-# Create a new project (replace 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": "",
- "name": "My Project",
- "region": "us-east-1",
- "db_pass": ""
- }'
+Create a new Supabase project from [the Dashboard of any organization](/dashboard/new/_) you belong to.
+
+
+
+Use [the Management API](/docs/reference/api/v1-create-a-project) or ask [the MCP server](/docs/guides/ai-tools/mcp#account-management) to create a new Supabase project.
+
+
+
+## 2. Set up your database
+
+When your Supabase project is up and running, create an `instruments` table with some sample data. Then set only the privileges each Postgres role needs, add [Row Level Security (RLS)](/docs/guides/database/postgres/row-level-security) for enhanced security for database data by default, and create an RLS policy to make the data in the table publicly readable.
+
+Do these steps within your project's dashboard by copying and running the snippet in your project's [SQL Editor](/dashboard/project/_/sql/new).
+
+
+
+Save some steps by clicking here to prefill the SQL in the SQL Editor, and then clicking **Run**.
+
+
+
+
+
+You can use [the Management API](/docs/reference/api/v1-run-a-query) or ask [the MCP server](/docs/guides/ai-tools/mcp#database) to execute SQL queries.
+
+
+
+```sql SQL_EDITOR
+-- Create the table
+create table instruments (
+ id bigint primary key generated always as identity,
+ name text not null
+);
+
+-- Insert sample data into the table
+insert into instruments (name)
+values
+ ('violin'),
+ ('viola'),
+ ('cello');
+
+-- Grant the privileges the role needs, which is read access
+grant select on public.instruments to anon;
+
+-- Enable row level security for the table
+alter table instruments enable row level security;
+
+-- Create a policy to allow the anon role to read from the instruments table
+create policy "public can read instruments"
+on public.instruments
+for select to anon
+using (true);
```
-
+
-
+If you disabled the Data API during project setup, enable it in the [**Integrations > Data API**](/dashboard/project/_/integrations/data_api/settings) section of the Dashboard and expose the specific tables or functions you want to access. To automatically grant access for new tables and functions in `public`, enable **Automatically expose new tables**.
-When your project is up and running, go to the [**Table Editor**](/dashboard/project/_/editor) section of the Dashboard, create a new table and insert some data. Then in the [**Integrations > Data API**](/dashboard/project/_/integrations/data_api/settings) section of the Dashboard, expose the specific tables or functions you want to access. To automatically grant access for new tables and functions in `public`, enable **Default privileges for new entities**.
-
-Alternatively, you can run the following snippet in your project's [SQL Editor](/dashboard/project/_/sql/new).
-
-This creates an `instruments` table with some sample data, sets a secure baseline by setting only the privileges each Postgres role needs, and adds [Row Level Security (RLS)](/docs/guides/database/postgres/row-level-security) for enhanced security for database data by default.
-
-
-
-
-
- ```sql SQL_EDITOR
- -- Create the table
- create table instruments (
- id bigint primary key generated always as identity,
- name text not null
- );
-
- -- Insert sample data into the table
- insert into instruments (name)
- values
- ('violin'),
- ('viola'),
- ('cello');
-
- -- Grant the privileges the role needs, which is read access
- grant select on public.instruments to anon;
-
- -- Enable row level security for the table
- alter table instruments enable row level security;
- ```
-
-
-
-
-
-Create an RLS policy to make the data in your table publicly readable:
-
-
-
-
-
- ```sql SQL_EDITOR
- -- Create a policy to allow the anon role to read from the instruments table
- create policy "public can read instruments"
- on public.instruments
- for select to anon
- using (true);
- ```
-
-
+
diff --git a/apps/docs/content/guides/ai-tools/mcp.mdx b/apps/docs/content/guides/ai-tools/mcp.mdx
index 33126fc0707..6dbc5d37bfb 100644
--- a/apps/docs/content/guides/ai-tools/mcp.mdx
+++ b/apps/docs/content/guides/ai-tools/mcp.mdx
@@ -112,11 +112,11 @@ The [configuration panel above](#step-2-configure-your-ai-tool) can set these op
| `project_ref=` | Scope to a specific project (disables account tools) | `?project_ref=abc123` |
| `features=` | Enable only specific tool groups (comma-separated) | `?features=database,docs` |
-Parameters can be combined: `https://mcp.supabase.com/mcp?project_ref=abc123&read_only=true`
+Parameters can be combined: remote ?project_ref=abc123&read_only=true
-When using [Supabase CLI](/docs/guides/cli) for local development, the MCP server is available at `http://localhost:54321/mcp`.
+When using [Supabase CLI](/docs/guides/cli) for local development, the MCP server is available at local .
@@ -139,19 +139,7 @@ To authenticate the MCP server in a CI environment, you can create a personal ac
1. Pass the token to the `Authorization` header in your MCP server configuration. For example if you are using [Claude Code](https://docs.claude.com/en/docs/claude-code/github-actions), your MCP server configuration might look like this:
- ```json
- {
- "mcpServers": {
- "supabase": {
- "type": "http",
- "url": "https://mcp.supabase.com/mcp?project_ref=${SUPABASE_PROJECT_REF}",
- "headers": {
- "Authorization": "Bearer ${SUPABASE_ACCESS_TOKEN}"
- }
- }
- }
- }
- ```
+
The above example assumes you have environment variables `SUPABASE_ACCESS_TOKEN` and `SUPABASE_PROJECT_REF` set in your CI environment.
diff --git a/apps/docs/content/guides/ai/concepts.mdx b/apps/docs/content/guides/ai/concepts.mdx
index 357581cc455..73f52fc93d6 100644
--- a/apps/docs/content/guides/ai/concepts.mdx
+++ b/apps/docs/content/guides/ai/concepts.mdx
@@ -41,7 +41,7 @@ Embeddings compress discrete information (words & symbols) into distributed cont
The chart below plots example phrases as points. Phrases with similar meanings sit close together, and unrelated phrases sit far apart.
- {
LangChain supports the concept of a hybrid search, which combines Similarity Search with Full Text Search. Read the official docs to get started: [Supabase Hybrid Search](https://js.langchain.com/docs/modules/indexes/retrievers/supabase-hybrid).
-You can install the LangChain Hybrid Search function though our [database.dev package manager](https://database.dev/langchain/hybrid_search).
+You can install the LangChain Hybrid Search function through our [database.dev package manager](https://database.dev/langchain/hybrid_search).
## Resources
diff --git a/apps/docs/content/guides/ai/python-clients.mdx b/apps/docs/content/guides/ai/python-clients.mdx
index a617f0e11cb..a03e5849960 100644
--- a/apps/docs/content/guides/ai/python-clients.mdx
+++ b/apps/docs/content/guides/ai/python-clients.mdx
@@ -7,7 +7,7 @@ sidebar_label: 'Choosing a Client'
As described in [Structured & Unstructured Embeddings](/docs/guides/ai/structured-unstructured), AI workloads come in many forms.
-For data science or ephemeral workloads, the [Supabase Vecs](https://supabase.github.io/vecs/) client gets you started quickly. All you need is a connection string and vecs handles setting up your database to store and query vectors with associated metadata.
+For data science or ephemeral workloads, the [Supabase Vecs](https://supabase.github.io/vecs/) client gets you started. You need a connection string and vecs handles setting up your database to store and query vectors with associated metadata.
diff --git a/apps/docs/content/guides/ai/rag-with-permissions.mdx b/apps/docs/content/guides/ai/rag-with-permissions.mdx
index da34eb5353d..9ecd87c69ae 100644
--- a/apps/docs/content/guides/ai/rag-with-permissions.mdx
+++ b/apps/docs/content/guides/ai/rag-with-permissions.mdx
@@ -1,12 +1,12 @@
---
id: 'ai-rag-with-permissions'
title: 'RAG with Permissions'
-subtitle: 'Fine-grain access control with Retrieval Augmented Generation.'
-description: 'Implement fine-grain access control with retrieval augmented generation'
+subtitle: 'Fine-grained access control with Retrieval Augmented Generation.'
+description: 'Implement fine-grained access control with retrieval augmented generation'
sidebar_label: 'RAG with Permissions'
---
-Since pgvector is built on top of Postgres, you can implement fine-grain access control on your vector database using [Row Level Security (RLS)](/docs/guides/database/postgres/row-level-security). This means you can restrict which documents are returned during a vector similarity search to users that have access to them. Supabase also supports [Foreign Data Wrappers (FDW)](/docs/guides/database/extensions/wrappers/overview) which means you can use an external database or data source to determine these permissions if your user data doesn't exist in Supabase.
+Since pgvector is built on top of Postgres, you can implement fine-grained access control on your vector database using [Row Level Security (RLS)](/docs/guides/database/postgres/row-level-security). This means you can restrict which documents are returned during a vector similarity search to users that have access to them. Supabase also supports [Foreign Data Wrappers (FDW)](/docs/guides/database/extensions/wrappers/overview) which means you can use an external database or data source to determine these permissions if your user data doesn't exist in Supabase.
Use this guide to learn how to restrict access to documents when performing retrieval augmented generation (RAG).
diff --git a/apps/docs/content/guides/ai/semantic-search.mdx b/apps/docs/content/guides/ai/semantic-search.mdx
index 76f77ac3582..6146b6a5edd 100644
--- a/apps/docs/content/guides/ai/semantic-search.mdx
+++ b/apps/docs/content/guides/ai/semantic-search.mdx
@@ -26,7 +26,7 @@ Embeddings are generated using a language model, and embeddings are compared to
## Embedding models
-There are many embedding models available today. Supabase Edge Functions has [built in support](/docs/guides/functions/examples/semantic-search) for the `gte-small` model. Others can be accessed through third-party APIs like [OpenAI](https://platform.openai.com/docs/guides/embeddings), where you send your text in the request and receive an embedding vector in the response. Others can run locally on your own compute, such as through Transformers.js for JavaScript implementations. For more information on local implementation, see [Generate embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings).
+There are many embedding models available today. Supabase Edge Functions has [built-in support](/docs/guides/functions/examples/semantic-search) for the `gte-small` model. Others can be accessed through third-party APIs like [OpenAI](https://platform.openai.com/docs/guides/embeddings), where you send your text in the request and receive an embedding vector in the response. Others can run locally on your own compute, such as through Transformers.js for JavaScript implementations. For more information on local implementation, see [Generate embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings).
It's crucial to remember that when using embedding models with semantic search, you must use the same model for all embedding comparisons. Comparing embeddings created by different models will yield meaningless results.
@@ -65,7 +65,7 @@ For more details on vector columns, including how to generate embeddings and sto
### Similarity metric
-`pgvector` support 3 operators for computing distance between embeddings:
+`pgvector` supports 3 operators for computing distance between embeddings:
| **Operator** | **Description** |
| ------------ | ---------------------- |
@@ -143,7 +143,7 @@ You can also call this method directly from SQL:
select *
from match_documents(
'[...]'::extensions.vector(512), -- pass the query embedding
- 0.78, -- chose an appropriate threshold for your data
+ 0.78, -- choose an appropriate threshold for your data
10 -- choose the number of matches
);
```
diff --git a/apps/docs/content/guides/ai/vector-columns.mdx b/apps/docs/content/guides/ai/vector-columns.mdx
index 6ca1aa54955..d3ab41c3aa9 100644
--- a/apps/docs/content/guides/ai/vector-columns.mdx
+++ b/apps/docs/content/guides/ai/vector-columns.mdx
@@ -5,7 +5,7 @@ description: 'Learn how to use vectors within your own Postgres tables'
sidebar_label: 'Vector columns'
---
-Supabase offers a number of different ways to store and query vectors within Postgres. The SQL included in this guide is applicable for clients in all programming languages. If you are a Python user see your [Python client options](/docs/guides/ai/python-clients) after reading the `Learn` section.
+Supabase offers a number of different ways to store and query vectors within Postgres. The SQL included in this guide is applicable for clients in all programming languages. If you are a Python user, see your [Python client options](/docs/guides/ai/python-clients) after reading the `Learn` section.
Vectors in Supabase are enabled via [pgvector](https://github.com/pgvector/pgvector/), a Postgres extension for storing and querying vectors in Postgres. It can be used to store [embeddings](/docs/guides/ai/concepts#what-are-embeddings).
@@ -48,7 +48,7 @@ To disable an extension, call `drop extension`.
### Create a table to store vectors
-After enabling the `vector` extension, you will get access to a new data type called `vector`. The size of the vector (indicated in parenthesis) represents the number of dimensions stored in that vector.
+After enabling the `vector` extension, you will get access to a new data type called `vector`. The size of the vector (indicated in parentheses) represents the number of dimensions stored in that vector.
```sql
create table documents (
@@ -59,7 +59,7 @@ create table documents (
);
```
-In the above SQL snippet, we create a `documents` table with a column called `embedding` (note this is just a regular Postgres column - you can name it whatever you like). We give the `embedding` column a `vector` data type with 384 dimensions. Change this to the number of dimensions produced by your embedding model. For example, if you are [generating embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings) using the open source [`gte-small`](https://huggingface.co/Supabase/gte-small) model, you would set this number to 384 since that model produces 384 dimensions.
+In the SQL snippet above, we create a `documents` table with an `embedding` column. This is a standard Postgres column, so you can name it anything you like. The `embedding` column uses the `vector` data type with 384 dimensions. Change this number to match the dimensions your embedding model produces. For example, if you're [generating embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings) using the open source [`gte-small`](https://huggingface.co/Supabase/gte-small) model, set this to 384.
@@ -73,6 +73,7 @@ In this example we'll generate a vector using Transformers.js, then store it in
```js
import { pipeline } from '@huggingface/transformers'
+
const generateEmbedding = await pipeline('feature-extraction', 'Supabase/gte-small')
const title = 'First post!'
@@ -99,7 +100,7 @@ This example uses the JavaScript Supabase client, but you can modify it to work
### Querying a vector / embedding
-Similarity search is the most common use case for vectors. `pgvector` support 3 new operators for computing distance:
+Similarity search is the most common use case for vectors. `pgvector` supports 3 new operators for computing distance:
| Operator | Description |
| -------- | ---------------------- |
diff --git a/apps/docs/content/guides/ai/vector-indexes/hnsw-indexes.mdx b/apps/docs/content/guides/ai/vector-indexes/hnsw-indexes.mdx
index 420b32fd48e..8ddcb7c0ab0 100644
--- a/apps/docs/content/guides/ai/vector-indexes/hnsw-indexes.mdx
+++ b/apps/docs/content/guides/ai/vector-indexes/hnsw-indexes.mdx
@@ -110,7 +110,7 @@ The “navigable” part of NSW specifically refers to the ability to logarithmi
HNSW combines these two concepts. From the hierarchical perspective, the bottom layer consists of a NSW made up of short links between nodes. Each layer above “skips” elements and creates longer links between nodes further away from each other.
-Just like skip lists, search starts at the top layer and works its way down until it finds the target element. However, instead of comparing a scalar value at each layer to determine whether or not to descend to the layer below, a multi-dimensional distance measure (such as Euclidean distance) is used.
+Like skip lists, search starts at the top layer and works its way down until it finds the target element. However, instead of comparing a scalar value at each layer to determine whether or not to descend to the layer below, a multi-dimensional distance measure (such as Euclidean distance) is used.
## When should you create HNSW indexes?
diff --git a/apps/docs/content/guides/api/handling-errors-in-supabase-js.mdx b/apps/docs/content/guides/api/handling-errors-in-supabase-js.mdx
index 3e816f35b86..76c549d3e2c 100644
--- a/apps/docs/content/guides/api/handling-errors-in-supabase-js.mdx
+++ b/apps/docs/content/guides/api/handling-errors-in-supabase-js.mdx
@@ -4,7 +4,7 @@ title: 'Handling errors in `supabase-js`'
subtitle: 'Read `error.hint` first — Postgres often tells you the exact fix. Log the full error so you actually see it.'
---
-Every `supabase-js` call returns a `{ data, error }` pair instead of throwing. When something fails, the single most useful field on `error` is usually `hint` — Postgres returns the _fix_, not just a description of the problem. Logging only `error.message` hides it.
+Every `supabase-js` call returns a `{ data, error }` pair instead of throwing. When something fails, the single most useful field on `error` is usually `hint` — Postgres returns the _fix_, not only a description of the problem. Logging only `error.message` hides it.
## Usage of `message` and `hint` properties
@@ -19,7 +19,7 @@ The `message` exposes the error reason, and `hint` gives you the literal SQL sta
The same pattern shows up across many Postgres errors — missing column? `hint` suggests the column name you probably meant. Type mismatch? `hint` shows the expected type. Whenever Postgres knows the fix, it puts it in `hint`.
-Log the full `error` object, not just `error.message`.
+Log the full `error` object, not only `error.message`.
## The recommended pattern
diff --git a/apps/docs/content/guides/api/securing-your-api.mdx b/apps/docs/content/guides/api/securing-your-api.mdx
index de875e9e599..894b2e8179b 100644
--- a/apps/docs/content/guides/api/securing-your-api.mdx
+++ b/apps/docs/content/guides/api/securing-your-api.mdx
@@ -104,7 +104,7 @@ Any table created through the Supabase Dashboard will have RLS enabled by defaul
>
-1. Go to the [Authentication > Policies](/dashboard/project/_/auth/policies) page in the Dashboard.
+1. Go to the [Database > Policies](/dashboard/project/_/database/policies) page in the Dashboard.
2. Select **Enable RLS** to enable Row Level Security.
diff --git a/apps/docs/content/guides/auth.mdx b/apps/docs/content/guides/auth.mdx
index a500f15609c..fd4071f7e65 100644
--- a/apps/docs/content/guides/auth.mdx
+++ b/apps/docs/content/guides/auth.mdx
@@ -1,7 +1,7 @@
---
id: 'auth'
title: 'Auth'
-description: 'Use Supabase to Authenticate and Authorize your users.'
+description: 'Use Supabase to authenticate and authorize your users.'
subtitle: 'Use Supabase to authenticate and authorize your users.'
tocVideo: '6ow_jW4epf8'
---
@@ -27,19 +27,15 @@ Auth uses your project's Postgres database under the hood, storing user data and
Auth also enables access control to your database's automatically generated [REST API](/docs/guides/api). When using Supabase SDKs, your data requests are automatically sent with the user's Auth Token. The Auth Token scopes database access on a row-by-row level when used along with [RLS policies](/docs/guides/database/postgres/row-level-security).
+
+
<$Show if="authentication:show_providers">
<$Partial path="providers.mdx" />
$Show>
<$Show if="billing:all">
-## Pricing
-
-Charges apply to Monthly Active Users (MAU), Monthly Active Third-Party Users (Third-Party MAU), and Monthly Active SSO Users (SSO MAU) and Advanced MFA Add-ons. For a detailed breakdown of how these charges are calculated, refer to the following pages:
-
-- [Pricing MAU](/docs/guides/platform/manage-your-usage/monthly-active-users)
-- [Pricing Third-Party MAU](/docs/guides/platform/manage-your-usage/monthly-active-users-third-party)
-- [Pricing SSO MAU](/docs/guides/platform/manage-your-usage/monthly-active-users-sso)
-- [Advanced MFA - Phone](/docs/guides/platform/manage-your-usage/advanced-mfa-phone)
-
+
$Show>
+
+
diff --git a/apps/docs/content/guides/auth/auth-anonymous.mdx b/apps/docs/content/guides/auth/auth-anonymous.mdx
index 8d1276750bb..0a284cc4c8e 100644
--- a/apps/docs/content/guides/auth/auth-anonymous.mdx
+++ b/apps/docs/content/guides/auth/auth-anonymous.mdx
@@ -8,7 +8,7 @@ subtitle: 'Create and use anonymous users to authenticate with Supabase'
-Calling `signInAnonymously()` creates an anonymous user. It's just like a permanent user, except the user can't access their account if they sign out, clear browsing data, or use another device.
+Calling `signInAnonymously()` creates an anonymous user. It behaves like a permanent user, except the user can't access their account if they sign out, clear browsing data, or use another device.
Like permanent users, the `authenticated` Postgres role will be used when using the Data APIs to access your project. JWTs for these users will have an `is_anonymous` claim which you can use to distinguish in RLS policies.
@@ -279,7 +279,7 @@ response = supabase.auth.link_identity({'provider': 'google'})
## Access control
-An anonymous user assumes the `authenticated` role just like a permanent user. You can use row-level security (RLS) policies to differentiate between an anonymous user and a permanent user by checking for the `is_anonymous` claim in the JWT returned by `auth.jwt()`:
+An anonymous user assumes the `authenticated` role like a permanent user. You can use row-level security (RLS) policies to differentiate between an anonymous user and a permanent user by checking for the `is_anonymous` claim in the JWT returned by `auth.jwt()`:
```sql
create policy "Only permanent users can post to the news feed"
diff --git a/apps/docs/content/guides/auth/auth-email-passwordless.mdx b/apps/docs/content/guides/auth/auth-email-passwordless.mdx
index 34e48a8b2b4..56532c6dcbf 100644
--- a/apps/docs/content/guides/auth/auth-email-passwordless.mdx
+++ b/apps/docs/content/guides/auth/auth-email-passwordless.mdx
@@ -87,7 +87,7 @@ Read the [Deep Linking Documentation](/docs/guides/auth/native-mobile-deep-linki
```dart
Future signInWithEmail() async {
- final AuthResponse res = await supabase.auth.signInWithOtp(email: 'valid.email@supabase.io');
+ await supabase.auth.signInWithOtp(email: 'valid.email@supabase.io');
}
```
@@ -223,7 +223,7 @@ const { data, error } = await supabase.auth.signInWithOtp({
```dart
Future signInWithEmailOtp() async {
- final AuthResponse res = await supabase.auth.signInWithOtp(email: 'valid.email@supabase.io');
+ await supabase.auth.signInWithOtp(email: 'valid.email@supabase.io');
}
```
diff --git a/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx b/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx
index df2b1199cda..c76f9deadf8 100644
--- a/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx
+++ b/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx
@@ -19,7 +19,7 @@ Supabase Auth will send a payload containing these fields to your hook:
-Because the hook is ran just before the insertion into the database, this user will not be found in Postgres at the time the hook is called.
+Because the hook runs immediately before insertion into the database, this user will not be found in Postgres at the time the hook is called.
@@ -722,8 +722,8 @@ supabase functions new before-user-created-hook
Add the following code to your edge function:
```ts
-import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
import { createClient } from 'https://esm.sh/@supabase/supabase-js'
+import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
const whSecret = Deno.env.get('BEFORE_USER_CREATED_HOOK_SECRET')?.replace('v1,whsec_', '')
const supabaseUrl = Deno.env.get('SUPABASE_URL')
diff --git a/apps/docs/content/guides/auth/auth-mfa.mdx b/apps/docs/content/guides/auth/auth-mfa.mdx
index 542884c5aa8..e7e1744b5c1 100644
--- a/apps/docs/content/guides/auth/auth-mfa.mdx
+++ b/apps/docs/content/guides/auth/auth-mfa.mdx
@@ -271,7 +271,7 @@ It is possible to enforce MFA on the Server-Side Rendering level. However, this
You can use the `supabase.auth.mfa.getAuthenticatorAssuranceLevel()` and `supabase.auth.mfa.listFactors()` APIs to identify the AAL level of the session and any factors that are enabled for a user, similar to how you would use these on the browser.
-However, encountering a different AAL level on the server may not actually be a security problem. Consider these likely scenarios:
+However, encountering a different AAL level on the server may not be a security problem. Consider these likely scenarios:
1. User signed-in with a conventional method but closed their tab on the MFA
flow.
@@ -284,7 +284,7 @@ We thus recommend you redirect users to a page where they can authenticate using
### APIs
-If your application uses the Supabase Database, Storage or Edge Functions, just using Row Level Security policies will give you sufficient protection. In the event that you have other APIs that you wish to protect, follow these general guidelines:
+If your application uses the Supabase Database, Storage or Edge Functions, Row Level Security policies provide sufficient protection. In the event that you have other APIs that you wish to protect, follow these general guidelines:
1. **Use a good JWT verification and parsing library for your language.**
This will let you securely parse JWTs and extract their claims.
@@ -300,7 +300,6 @@ If your application uses the Supabase Database, Storage or Edge Functions, just
How do I check when a user went through MFA?}
+header="How do I check when a user went through MFA?"
id="how-do-i-check-when-a-user-went-through-mfa"
>
diff --git a/apps/docs/content/guides/auth/auth-mfa/phone.mdx b/apps/docs/content/guides/auth/auth-mfa/phone.mdx
index 3a123e4a59e..2bf410157f7 100644
--- a/apps/docs/content/guides/auth/auth-mfa/phone.mdx
+++ b/apps/docs/content/guides/auth/auth-mfa/phone.mdx
@@ -41,7 +41,7 @@ In the **login flow**, the user signs in (upgrading the session to AAL1) and the
An enrollment flow provides a UI for users to set up additional authentication factors. Most applications add the enrollment flow in two places within their app:
1. Right after login or sign up.
- This allows users quickly set up Multi Factor Authentication (MFA) post login or account creation. Where possible, encourage all users to set up MFA. Many applications offer this as an opt-in step in an
+ This allows users to set up Multi Factor Authentication (MFA) post login or account creation. Where possible, encourage all users to set up MFA. Many applications offer this as an opt-in step in an
effort to reduce onboarding friction.
2. From within a settings page.
Allows users to set up, disable or modify their MFA settings.
diff --git a/apps/docs/content/guides/auth/auth-mfa/totp.mdx b/apps/docs/content/guides/auth/auth-mfa/totp.mdx
index dacae7becca..c45751d91e9 100644
--- a/apps/docs/content/guides/auth/auth-mfa/totp.mdx
+++ b/apps/docs/content/guides/auth/auth-mfa/totp.mdx
@@ -47,7 +47,7 @@ In the **login flow**, the user signs in (upgrading the session to AAL1) and the
An enrollment flow provides a UI for users to set up additional authentication factors. Most applications add the enrollment flow in two places within their app:
1. Right after login or sign up.
- This lets users quickly set up MFA immediately after they log in or create an
+ This lets users set up MFA immediately after they log in or create an
account. We recommend encouraging all users to set up MFA if that makes sense
for your application. Many applications offer this as an opt-in step in an
effort to reduce onboarding friction.
diff --git a/apps/docs/content/guides/auth/auth-smtp.mdx b/apps/docs/content/guides/auth/auth-smtp.mdx
index b43e261bda7..7ff2e248828 100644
--- a/apps/docs/content/guides/auth/auth-smtp.mdx
+++ b/apps/docs/content/guides/auth/auth-smtp.mdx
@@ -118,7 +118,7 @@ This includes:
**Have another SMTP service set up on stand-by.**
-In case the primary SMTP service you're using is experiencing difficulty, or your account is under threat of being blocked due to spam, you have another service to quickly turn to.
+In case the primary SMTP service you're using is experiencing difficulty, or your account is under threat of being blocked due to spam, you have another service to turn to.
**Use consistent branding and focused content.**
diff --git a/apps/docs/content/guides/auth/general-configuration.mdx b/apps/docs/content/guides/auth/general-configuration.mdx
index 0e79c0625c7..fcb98df2791 100644
--- a/apps/docs/content/guides/auth/general-configuration.mdx
+++ b/apps/docs/content/guides/auth/general-configuration.mdx
@@ -6,7 +6,7 @@ subtitle: 'General configuration options for Supabase Auth'
This section covers the [general configuration options](/dashboard/project/_/auth) for Supabase Auth. If you are looking for another type of configuration, you may be interested in one of the following sections:
-- [Policies](/dashboard/project/_/auth/policies) to manage Row Level Security policies for your tables.
+- [Policies](/dashboard/project/_/database/policies) to manage Row Level Security policies for your tables.
- [Sign In / Providers](/dashboard/project/_/auth/providers) to configure authentication providers and login methods for your users.
- [Third Party Auth](/dashboard/project/_/auth/third-party) to use third-party authentication (TPA) systems based on JWTs to access your project.
- [Sessions](/dashboard/project/_/auth/sessions) to configure settings for user sessions and refresh tokens.
diff --git a/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx b/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx
index 7b890e169b6..e08ddeb5567 100644
--- a/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx
+++ b/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx
@@ -120,7 +120,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This
- Go to your [auth settings](/dashboard/project/_/auth/url-configuration) page.
- You need to enter your app redirect callback on `Additional Redirect URLs` field.
- The redirect callback URL should have this format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.flutterquickstart://login-callback` is just an example, you can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
+ The redirect callback URL should have this format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.flutterquickstart://login-callback` is an example. You can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.

@@ -314,7 +314,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This
1. Go to your [auth settings](/dashboard/project/_/auth/url-configuration) page.
2. Enter your app redirect URL in the `Additional Redirect URLs` field. This is the URL that the user gets redirected to after clicking a magic link.
- The redirect callback URL should have the format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.user-management://login-callback` is just an example. You can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
+ The redirect callback URL should have the format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.user-management://login-callback` is an example. You can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.

@@ -356,7 +356,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This
1. Go to your [auth settings](/dashboard/project/_/auth/url-configuration) page.
2. Enter your app redirect URL in the `Additional Redirect URLs` field. This is the URL that the user gets redirected to after clicking a magic link.
- The redirect callback URL should have the format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. Here, `io.supabase.user-management://login-callback` is just an example. You can choose whatever you would like for `YOUR_SCHEME` and `YOUR_HOSTNAME` as long as the scheme is unique across the user's device. For this reason, typically a reverse domain of your website is used.
+ The redirect callback URL must have the format `[YOUR_SCHEME]://[YOUR_HOSTNAME]`. For example: `io.supabase.user-management://login-callback`. You can use any values for `YOUR_SCHEME` and `YOUR_HOSTNAME`, but the scheme must be unique across the user's device. For this reason, a reverse domain of your website is typically used.
Now, edit the Android manifest to make sure the app opens when the user clicks on the magic link.
diff --git a/apps/docs/content/guides/auth/oauth-server.mdx b/apps/docs/content/guides/auth/oauth-server.mdx
index 31df7fae5fc..66c7790d320 100644
--- a/apps/docs/content/guides/auth/oauth-server.mdx
+++ b/apps/docs/content/guides/auth/oauth-server.mdx
@@ -3,7 +3,7 @@ title: 'OAuth 2.1 Server'
description: 'Turn your Supabase project into an OAuth 2.1 and OpenID Connect identity provider'
---
-Supabase Auth can act as an OAuth 2.1 and OpenID Connect (OIDC) identity provider. This allows other applications and services to use your Supabase project as their authentication provider, just like "Sign in with Google" or "Sign in with GitHub".
+Supabase Auth can act as an OAuth 2.1 and OpenID Connect (OIDC) identity provider. This allows other applications and services to use your Supabase project as their authentication provider, like "Sign in with Google" or "Sign in with GitHub".
You can use this to build "Sign in with [Your App]" experiences, authenticate AI agents through the Model Context Protocol (MCP), power developer platforms with third-party integrations, or implement standards-compliant enterprise SSO.
diff --git a/apps/docs/content/guides/auth/oauth-server/mcp-authentication.mdx b/apps/docs/content/guides/auth/oauth-server/mcp-authentication.mdx
index 051892f1ad5..7434553409f 100644
--- a/apps/docs/content/guides/auth/oauth-server/mcp-authentication.mdx
+++ b/apps/docs/content/guides/auth/oauth-server/mcp-authentication.mdx
@@ -79,13 +79,13 @@ When building your own MCP server, integrate with Supabase Auth to authenticate
**Looking for an easier way to build MCP servers?**
-[FastMCP](https://gofastmcp.com) provides a streamlined way to build MCP servers with built-in Supabase Auth integration. FastMCP handles OAuth configuration, token management, and authentication flows automatically, letting you focus on building your AI agent's functionality. Check out their [Supabase integration guide](https://gofastmcp.com/integrations/supabase#supabase-fastmcp) to get started quickly.
+[FastMCP](https://gofastmcp.com) provides a streamlined way to build MCP servers with built-in Supabase Auth integration. FastMCP handles OAuth configuration, token management, and authentication flows automatically, letting you focus on building your AI agent's functionality. Check out their [Supabase integration guide](https://gofastmcp.com/integrations/supabase#supabase-fastmcp) to get started.
## Handling MCP tokens in your application
-When your MCP server makes requests to your Supabase APIs on behalf of authenticated users, it will send access tokens issued by Supabase Auth, just like any other OAuth client.
+When your MCP server makes requests to your Supabase APIs on behalf of authenticated users, it will send access tokens issued by Supabase Auth, like any other OAuth client.
### Validating MCP tokens
diff --git a/apps/docs/content/guides/auth/oauth-server/token-security.mdx b/apps/docs/content/guides/auth/oauth-server/token-security.mdx
index b5da836eb94..2fe88b440a2 100644
--- a/apps/docs/content/guides/auth/oauth-server/token-security.mdx
+++ b/apps/docs/content/guides/auth/oauth-server/token-security.mdx
@@ -368,7 +368,7 @@ SELECT * FROM user_data WHERE user_id = 'test-user-uuid';
RESET request.jwt.claims;
```
-Or use the Supabase Dashboard's [RLS policy tester](/dashboard/project/_/auth/policies).
+Or use the Supabase Dashboard's [RLS policy tester](/dashboard/project/_/database/policies).
## Troubleshooting
diff --git a/apps/docs/content/guides/auth/passkeys.mdx b/apps/docs/content/guides/auth/passkeys.mdx
index 1a732f7cfa7..ce5df61be30 100644
--- a/apps/docs/content/guides/auth/passkeys.mdx
+++ b/apps/docs/content/guides/auth/passkeys.mdx
@@ -136,6 +136,14 @@ await Supabase.initialize(
final supabase = Supabase.instance.client;
```
+`supabase_flutter` performs the server side of the WebAuthn ceremony for you and delegates the platform prompt (FaceID/TouchID/security key) to an authenticator you supply, instead of depending on a passkey plugin directly. Add a passkey plugin to your own app and pass its authenticator to `registerPasskey()` and `signInWithPasskey()`. The [`passkeys`](https://pub.dev/packages/passkeys) plugin's `PasskeyAuthenticator` implements the `PasskeyAuthenticatorInterface` these methods expect (since `passkeys` `2.21.0`), but you can pass any implementation of that interface:
+
+```dart
+import 'package:passkeys/authenticator.dart';
+
+final authenticator = PasskeyAuthenticator();
+```
+
Platform setup that the library cannot do for you (Associated Domains on iOS/macOS, Digital Asset Links on Android, and including the [`passkeys`](https://pub.dev/packages/passkeys) web SDK in `index.html` on web) is documented in the `supabase_flutter` package README.
@@ -185,7 +193,7 @@ if (error) {
```dart
try {
- final Passkey passkey = await supabase.auth.registerPasskey();
+ final Passkey passkey = await supabase.auth.registerPasskey(authenticator);
print('Registered passkey ${passkey.id}');
} on AuthException catch (e) {
// The Supabase server rejected the credential.
@@ -261,7 +269,7 @@ if (error) {
```dart
try {
- final AuthResponse res = await supabase.auth.signInWithPasskey();
+ final AuthResponse res = await supabase.auth.signInWithPasskey(authenticator);
// res.session and res.user are set; the client also fires AuthChangeEvent.signedIn
print('Signed in as ${res.user?.email}');
} on AuthException catch (e) {
diff --git a/apps/docs/content/guides/auth/passwords.mdx b/apps/docs/content/guides/auth/passwords.mdx
index 4548d785ba8..ad37b727915 100644
--- a/apps/docs/content/guides/auth/passwords.mdx
+++ b/apps/docs/content/guides/auth/passwords.mdx
@@ -28,9 +28,8 @@ The instructions in this section assume that email confirmations are enabled.
stickyTabList={{
style: {
top: 'var(--header-height)',
- backgroundColor: 'hsl(var(--background-default) / var(--tw-bg-opacity))',
maskImage: 'none',
- borderBottom: '1px solid hsl(var(--border-default) / 1)',
+ borderBottom: '1px solid oklch(from var(--border-default) l c h / 1)',
}
}}
size="large"
@@ -1036,7 +1035,7 @@ final UserResponse res = await supabase.auth.updateUser(
#### Verifying the current password
-If your app requires users to confirm their current password before setting a new one, you can pass `currentPassword` (available in `supabase-js` v2.102.0+ and `supabase-kt` 3.5.0+):
+If your app requires users to confirm their current password before setting a new one, you can pass `current_password` (available in `supabase-js` v2.102.0+ and `supabase-kt` 3.5.0+):
+ <$Partial path="api_settings.mdx" variables={{ "framework": "astro", "tab": "frameworks" }} />
diff --git a/apps/docs/content/guides/auth/quickstarts/nextjs.mdx b/apps/docs/content/guides/auth/quickstarts/nextjs.mdx
index 82116d56820..30912b80e7e 100644
--- a/apps/docs/content/guides/auth/quickstarts/nextjs.mdx
+++ b/apps/docs/content/guides/auth/quickstarts/nextjs.mdx
@@ -67,7 +67,7 @@ hideToc: true
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_... key
```
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} />
+ <$Partial path="api_settings.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} />
@@ -95,5 +95,5 @@ hideToc: true
## Learn more
-- [Setting up Server-Side Auth for Next.js](/docs/guides/auth/server-side/nextjs) for a Next.js deep dive
+- [Setting up Server-Side Auth for Next.js](/docs/guides/auth/server-side/creating-a-client?queryGroups=framework&framework=nextjs) for a Next.js deep dive
- [Supabase Auth docs](/docs/guides/auth#authentication) for more Supabase authentication methods
diff --git a/apps/docs/content/guides/auth/quickstarts/react-native.mdx b/apps/docs/content/guides/auth/quickstarts/react-native.mdx
index 4318c0d3e3b..d8ed113a884 100644
--- a/apps/docs/content/guides/auth/quickstarts/react-native.mdx
+++ b/apps/docs/content/guides/auth/quickstarts/react-native.mdx
@@ -81,7 +81,7 @@ hideToc: true
meta="name=lib/supabase.ts"
/>
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "exporeactnative", "tab": "mobiles" }} />
+ <$Partial path="api_settings.mdx" variables={{ "framework": "exporeactnative", "tab": "mobiles" }} />
diff --git a/apps/docs/content/guides/auth/quickstarts/react.mdx b/apps/docs/content/guides/auth/quickstarts/react.mdx
index 7c99938fecd..63cc239e117 100644
--- a/apps/docs/content/guides/auth/quickstarts/react.mdx
+++ b/apps/docs/content/guides/auth/quickstarts/react.mdx
@@ -65,8 +65,7 @@ hideToc: true
Rename `.env.example` to `.env.local` and populate with your Supabase connection variables:
-
-
+ <$Partial path="api_settings.mdx" variables={{ "framework": "react", "tab": "frameworks" }} />
@@ -77,7 +76,6 @@ hideToc: true
lines={[[1, -1]]}
meta="name=.env.local"
/>
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "react", "tab": "frameworks" }} />
diff --git a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
index 92ce3e4f2d2..41dfa60552b 100644
--- a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
+++ b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
@@ -41,10 +41,7 @@ pnpm add @supabase/supabase-js @supabase/ssr
Create a `.env.local` file in the project root directory. In the file, set the project's Supabase URL and Key:
-
-
-
-<$Partial path="api_settings_steps.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} />
+<$Partial path="api_settings.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} />
@@ -203,7 +200,7 @@ The Proxy is responsible for:
What does the `cookies` object do?}
+ header="What does the `cookies` object do?"
id="utility-cookies"
>
@@ -216,7 +213,7 @@ The Proxy is responsible for:
Do I need to create a new client for every route?}
+ header="Do I need to create a new client for every route?"
id="client-deduplication"
>
diff --git a/apps/docs/content/guides/auth/signing-keys.mdx b/apps/docs/content/guides/auth/signing-keys.mdx
index 771cc541fcc..97518c9618a 100644
--- a/apps/docs/content/guides/auth/signing-keys.mdx
+++ b/apps/docs/content/guides/auth/signing-keys.mdx
@@ -151,7 +151,7 @@ This guarantee provides your application with close alignment with security comp
If you wish to make your own JWTs or have access to the private key or shared secret used by Supabase, you can create a new JWT signing key by importing a private key or setting a shared secret yourself.
-Use the [Supabase CLI](/docs/reference/cli/introduction) to quickly and securely generate a private key ready for import:
+Use the [Supabase CLI](/docs/reference/cli/introduction) to securely generate a private key ready for import:
```sh
supabase gen signing-key --algorithm ES256
@@ -228,7 +228,7 @@ This is to ensure you have the ability, should you need it, to go back to the le
### Why does revoking the legacy JWT secret require disabling of `anon` and `service_role` API keys?
-Unfortunately `anon` and `service_role` are not just API keys, but are also valid JSON Web Tokens, signed by the legacy JWT secret. Revoking the legacy JWT secret means that your application no longer trusts any JWT signed with it. Therefore before you revoke the legacy JWT secret, you must disable the `anon` and `service_role` to ensure a consistent security setup.
+Unfortunately `anon` and `service_role` are not only API keys, but are also valid JSON Web Tokens, signed by the legacy JWT secret. Revoking the legacy JWT secret means that your application no longer trusts any JWT signed with it. Therefore before you revoke the legacy JWT secret, you must disable the `anon` and `service_role` to ensure a consistent security setup.
### Using JWT-based `anon` key in a mobile, desktop, or CLI application and need to rotate a `service_role` JWT secret?
diff --git a/apps/docs/content/guides/auth/social-login/auth-azure.mdx b/apps/docs/content/guides/auth/social-login/auth-azure.mdx
index 0189baaad42..7e2a09b236c 100644
--- a/apps/docs/content/guides/auth/social-login/auth-azure.mdx
+++ b/apps/docs/content/guides/auth/social-login/auth-azure.mdx
@@ -95,7 +95,7 @@ Configure this in the following way:
- Select the _App registrations_ menu in Microsoft Entra ID on the Azure portal.
- Select the OAuth app.
- Select the _Manifest_ menu in the sidebar.
-- Make a backup of the JSON just in case.
+- Make a backup of the JSON in case you need it later.
- Identify the `optionalClaims` key.
- Edit it by specifying the following object:
```json
diff --git a/apps/docs/content/guides/auth/users.mdx b/apps/docs/content/guides/auth/users.mdx
index 52306d68b72..3157bbc88a1 100644
--- a/apps/docs/content/guides/auth/users.mdx
+++ b/apps/docs/content/guides/auth/users.mdx
@@ -21,7 +21,7 @@ See the [Anonymous Signins guide](/docs/guides/auth/auth-anonymous) to learn mor
-Just like permanent users, anonymous users use the **authenticated** role for database access.
+Like permanent users, anonymous users use the **authenticated** role for database access.
The **anon** role is for those who aren't signed in at all and are not tied to any user ID. We refer to these as unauthenticated or public users.
diff --git a/apps/docs/content/guides/cron/quickstart.mdx b/apps/docs/content/guides/cron/quickstart.mdx
index 38cb3cd7cd6..6c6b369c8e3 100644
--- a/apps/docs/content/guides/cron/quickstart.mdx
+++ b/apps/docs/content/guides/cron/quickstart.mdx
@@ -40,7 +40,6 @@ select cron.schedule('permanent-cron-job-name', '30 seconds', 'CALL do_something
-Use the examples below with `supabase --experimental --project-ref postgres-config update`:
+Use the examples below with `supabase postgres-config update --project-ref --experimental`:
| Parameter | Type | Restart | Example |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ------- | --------------------------------------------- |
@@ -153,6 +153,8 @@ Use the examples below with `supabase --experimental --project-ref
Some Postgres settings are configurable through the [Management API](/docs/reference/api/v1-update-postgres-config) but not the CLI. These include logging settings such as `log_connections`. See [Postgres connection logging](/docs/guides/platform/postgres-connection-logging) for details.
+`cron.log_statement` (which logs each [pg_cron](/docs/guides/database/extensions/pg_cron) job statement before it runs) is also Management API only. It is a `postmaster`-context parameter, so updating it **requires a database restart** to take effect.
+
#### Managing Postgres configuration with the CLI
To start:
@@ -163,26 +165,26 @@ To start:
To update Postgres configurations, use the [`postgres config`](/docs/reference/cli/supabase-postgres-config) command:
```bash
-supabase --experimental \
-postgres-config update --config shared_buffers=250MB \
---project-ref
+supabase postgres-config update --config shared_buffers=250MB \
+--project-ref \
+--experimental
```
By default, the CLI will merge any provided config overrides with any existing ones. The `--replace-existing-overrides` flag can be used to instead force all existing overrides to be replaced with the ones being provided:
```bash
-supabase --experimental \
-postgres-config update --config max_parallel_workers=3 \
+supabase postgres-config update --config max_parallel_workers=3 \
--replace-existing-overrides \
---project-ref
+--project-ref \
+--experimental
```
To delete specific configuration overrides, use the `postgres-config delete` command:
```bash
-supabase --experimental \
-postgres-config delete --config shared_buffers,work_mem \
---project-ref
+supabase postgres-config delete --config shared_buffers,work_mem \
+--project-ref \
+--experimental
```
By default, CLI v2 (≥ 2.0.0) checks the parameter’s context and requests the correct action (reload or restart):
@@ -221,9 +223,9 @@ By default, Supabase ensures that this propagation is executed correctly. Howeve
```bash
-supabase --experimental \
+supabase postgres-config delete --config shared_buffers --no-restart \
--project-ref \
-postgres-config delete --config shared_buffers --no-restart
+--experimental
```
### Resetting to default config
diff --git a/apps/docs/content/guides/database/extensions/hypopg.mdx b/apps/docs/content/guides/database/extensions/hypopg.mdx
index 6bea2618227..6863634ff8b 100644
--- a/apps/docs/content/guides/database/extensions/hypopg.mdx
+++ b/apps/docs/content/guides/database/extensions/hypopg.mdx
@@ -6,7 +6,7 @@ description: 'Quickly check if an index can be used without creating it.'
`HypoPG` is Postgres extension for creating hypothetical/virtual indexes. HypoPG allows users to rapidly create hypothetical/virtual indexes that have no resource cost (CPU, disk, memory) that are visible to the Postgres query planner.
-The motivation for HypoPG is to allow users to quickly search for an index to improve a slow query without consuming server resources or waiting for them to build.
+The motivation for HypoPG is to allow users to search for an index to improve a slow query without consuming server resources or waiting for them to build.
## Enable the extension
diff --git a/apps/docs/content/guides/database/extensions/pgaudit.mdx b/apps/docs/content/guides/database/extensions/pgaudit.mdx
index 48c482aa12b..1f6ff73921a 100644
--- a/apps/docs/content/guides/database/extensions/pgaudit.mdx
+++ b/apps/docs/content/guides/database/extensions/pgaudit.mdx
@@ -193,7 +193,7 @@ You can then assign the role to monitor only approved object events, such as `se
grant select on random_table to "some_audit_role";
```
-With this privilege granted, PGAudit will record all select statements that reference the `random_table`, regardless of _who_ or _what_ actually initiated the event. All assignable privileges can be viewed in the [Postgres documentation](https://www.postgresql.org/docs/current/ddl-priv.html).
+With this privilege granted, PGAudit will record all select statements that reference the `random_table`, regardless of _who_ or _what_ initiated the event. All assignable privileges can be viewed in the [Postgres documentation](https://www.postgresql.org/docs/current/ddl-priv.html).
If you would no longer like to use object logging, you will need to unassign the `pgaudit.role` variable:
diff --git a/apps/docs/content/guides/database/extensions/pgroonga.mdx b/apps/docs/content/guides/database/extensions/pgroonga.mdx
index 68bdb78ea77..3e8f135c000 100644
--- a/apps/docs/content/guides/database/extensions/pgroonga.mdx
+++ b/apps/docs/content/guides/database/extensions/pgroonga.mdx
@@ -124,7 +124,7 @@ id | content
### Match all search words
-To find all memos where content contains BOTH of the words `postgres` and `pgroonga`, we can just use space to separate each words:
+To find all memos where content contains BOTH of the words `postgres` and `pgroonga`, we can use space to separate each words:
{/* prettier-ignore */}
```sql
diff --git a/apps/docs/content/guides/database/extensions/plv8.mdx b/apps/docs/content/guides/database/extensions/plv8.mdx
index 190cdcf6d24..219f98acc81 100644
--- a/apps/docs/content/guides/database/extensions/plv8.mdx
+++ b/apps/docs/content/guides/database/extensions/plv8.mdx
@@ -55,7 +55,7 @@ Procedural languages are automatically installed within `pg_catalog`, so you don
## Create `plv8` functions
-Functions written in `plv8` are written just like any other Postgres functions, only
+Functions written in `plv8` are written like any other Postgres functions, only
with the `language` identifier set to `plv8`.
```sql
diff --git a/apps/docs/content/guides/database/extensions/postgis.mdx b/apps/docs/content/guides/database/extensions/postgis.mdx
index 1e45230f2cf..c02e5f9c9ae 100644
--- a/apps/docs/content/guides/database/extensions/postgis.mdx
+++ b/apps/docs/content/guides/database/extensions/postgis.mdx
@@ -192,7 +192,7 @@ val data = supabase.from("restaurants").insert(listOf(
$Show>
-Notice the order in which you pass the latitude and longitude. Longitude comes first, and is because longitude represents the x-axis of the location. Another thing to watch for is when inserting data from the client library, there is no comma between the two values, just a single space.
+Notice the order in which you pass the latitude and longitude. Longitude comes first, and is because longitude represents the x-axis of the location. Another thing to watch for is when inserting data from the client library, there is no comma between the two values, only a single space.
At this point, if you go into your Supabase dashboard and look at the data, you will notice that the value of the `location` column looks something like this.
diff --git a/apps/docs/content/guides/database/extensions/wrappers/overview.mdx b/apps/docs/content/guides/database/extensions/wrappers/overview.mdx
index 5695f1aa007..867129be896 100644
--- a/apps/docs/content/guides/database/extensions/wrappers/overview.mdx
+++ b/apps/docs/content/guides/database/extensions/wrappers/overview.mdx
@@ -65,7 +65,7 @@ where ts > (now() - interval '1 DAY');
This approach provides several benefits:
-1. **Simplicity:** the Wrappers API is just SQL, so data engineers don't need to learn new tools and languages.
+1. **Simplicity:** the Wrappers API is SQL, so data engineers don't need to learn new tools and languages.
1. **Save on time:** avoid setting up additional data pipelines.
1. **Save on Data Engineering costs:** less infrastructure to be managed.
diff --git a/apps/docs/content/guides/database/import-data.mdx b/apps/docs/content/guides/database/import-data.mdx
index ca2e330b508..0b0262bdc7a 100644
--- a/apps/docs/content/guides/database/import-data.mdx
+++ b/apps/docs/content/guides/database/import-data.mdx
@@ -5,7 +5,7 @@ title: 'Import data into Supabase'
You can import data into Supabase in multiple ways. The best method depends on your data size and app requirements.
-If you're working with small datasets in development, you can experiment quickly using CSV import in the Supabase dashboard. If you're working with a large dataset in production, you should plan your data import to minimize app latency and ensure data integrity.
+If you're working with small datasets in development, you can experiment with CSV import in the Supabase dashboard. If you're working with a large dataset in production, you should plan your data import to minimize app latency and ensure data integrity.
## How to import data into Supabase
diff --git a/apps/docs/content/guides/database/inspect.mdx b/apps/docs/content/guides/database/inspect.mdx
index 965e83912a2..8d01413792c 100644
--- a/apps/docs/content/guides/database/inspect.mdx
+++ b/apps/docs/content/guides/database/inspect.mdx
@@ -47,7 +47,7 @@ Most inspection commands are Postgres agnostic. You can run inspection routines
For example you can connect to your local Postgres instance:
```
-supabase --db-url postgresql://postgres:postgres@localhost:5432/postgres inspect db bloat
+supabase inspect db bloat --db-url postgresql://postgres:postgres@localhost:5432/postgres
```
### Connect to a Supabase instance
@@ -250,9 +250,9 @@ Postgres has built in tooling to help you optimize poorly performing queries. Yo
explain analyze ;
```
-When you include `analyze` in the explain statement, the database attempts to execute the query and provides a detailed query plan along with actual execution times. So, be careful using `explain analyze` with `insert`/`update`/`delete` queries, because the query will actually run, and could have unintended side-effects.
+When you include `analyze` in the explain statement, the database attempts to execute the query and provides a detailed query plan along with actual execution times. So, be careful using `explain analyze` with `insert`/`update`/`delete` queries, because the query will run, and could have unintended side-effects.
-If you run just `explain` without the `analyze` keyword, the database will only perform query planning without actually executing the query. This approach can be beneficial when you want to inspect the query plan without affecting the database or if you encounter timeouts in your queries.
+If you run `explain` without the `analyze` keyword, the database will only perform query planning without executing the query. This approach can be beneficial when you want to inspect the query plan without affecting the database or if you encounter timeouts in your queries.
Using the query plan analyzer to optimize your queries is a large topic, with a number of online resources available:
diff --git a/apps/docs/content/guides/database/json.mdx b/apps/docs/content/guides/database/json.mdx
index 9d90a37ab70..e4191087163 100644
--- a/apps/docs/content/guides/database/json.mdx
+++ b/apps/docs/content/guides/database/json.mdx
@@ -23,7 +23,7 @@ Don't go overboard with `json/jsonb` columns. They are a useful tool, but most o
## Create JSONB columns
-`json/jsonb` is just another "data type" for Postgres columns. You can create a `jsonb` column in the same way you would create a `text` or `int` column:
+`json/jsonb` is another "data type" for Postgres columns. You can create a `jsonb` column in the same way you would create a `text` or `int` column:
-
-You can work with a project's database in several ways:
+Work with your project's database in the following ways:
- Visually using the [**Table Editor**](/dashboard/project/_/editor) section of the Dashboard.
- With query syntax using the [**SQL Editor**](/dashboard/project/_/sql) section of the Dashboard.
-- Programmatically using a variety of methods.
+- Programmatically using a variety of different methods.
-Read the [Connect to your database](/docs/guides/database/connecting-to-postgres#how-to-connect-to-your-postgres-databases) guide for details on connection strings and options.
+
-
-
-The database is the foundation that Auth, Storage, Realtime, and Edge Functions are built on, and Supabase manages daily database backups and offers point-in-time recovery on paid plans.
-
-## Get started
-
-If you're new to the database section, these are the pages to read first:
-
-- **[Connect to your database](/docs/guides/database/connecting-to-postgres)**: Connection strings, the Supavisor connection pooler, and when to use direct, transaction, or session mode.
-- **[Tables and data](/docs/guides/database/tables)**: Create tables and relationships, and edit rows from the Dashboard.
-- **[Import data](/docs/guides/database/import-data)**: Load existing data from CSV files, `pg_dump`, or another Postgres database.
-- **[Secure your data](/docs/guides/database/secure-data)**: Row Level Security (RLS) is how Supabase makes the database safe to query directly from the client. Read this before exposing any table to your app.
-- **[Extensions](/docs/guides/database/extensions)**: Enable Postgres extensions — including `pgvector` for embeddings, `PostGIS` for geospatial data, and `pg_cron` for scheduled jobs — from the Dashboard.
-- **[Run SQL commands](/dashboard/project/_/sql)**: Use the Dashboard's SQL Editor for ad-hoc queries and saved snippets.
-
-## Going further
-
-Once you've covered the basics, these guides help with other use cases and features:
-
-- **[Database functions](/docs/guides/database/functions)** and **[triggers](/docs/guides/database/postgres/triggers)**: Run logic inside the database in response to inserts, updates, or deletes.
-- **[Database webhooks](/docs/guides/database/webhooks)**: Send row changes to an external HTTP endpoint.
-- **[Replication and read replicas](/docs/guides/database/replication)**: Stream changes to other systems or read from a geographically closer replica.
-- **[Backups](/docs/guides/platform/backups)**: Daily backups on every project, with point-in-time recovery on paid plans. Backups cover the database itself; objects stored through the Storage API are not included.
-- **[Query performance and optimization](/docs/guides/database/query-optimization)**: Indexes, the query planner, and tools for finding slow queries.
-- **[Roles and permissions](/docs/guides/database/postgres/roles)**: The Postgres roles Supabase ships with and how to add your own.
+
diff --git a/apps/docs/content/guides/database/postgres/cascade-deletes.mdx b/apps/docs/content/guides/database/postgres/cascade-deletes.mdx
index e3a73311ce0..0f63f7f3cf9 100644
--- a/apps/docs/content/guides/database/postgres/cascade-deletes.mdx
+++ b/apps/docs/content/guides/database/postgres/cascade-deletes.mdx
@@ -32,7 +32,7 @@ When a foreign key constraint is defined with the option `RESTRICT`, it means th
When a foreign key constraint is defined with the option `NO ACTION`, it means that if a row in the parent table is deleted, the database will also raise an error and prevent the deletion of the row in the parent table. However unlike `RESTRICT`, `NO ACTION` has the option to defer the check using `INITIALLY DEFERRED`. This will only raise the above error _if_ the referenced rows still exist at the end of the transaction.
-The difference from `RESTRICT` is that a constraint marked as `NO ACTION INITIALLY DEFERRED` is deferred until the end of the transaction, rather than running immediately. If, for example there is another foreign key constraint between the same tables marked as `CASCADE`, the cascade will occur first and delete the referenced rows, and no error will be thrown by the deferred constraint. Otherwise if there are still rows referencing the parent row by the end of the transaction, an error will be raised just like before. Just like `RESTRICT`, the database will not delete, update or set to NULL any rows in the referenced tables.
+The difference from `RESTRICT` is that a constraint marked as `NO ACTION INITIALLY DEFERRED` is deferred until the end of the transaction, rather than running immediately. If, for example there is another foreign key constraint between the same tables marked as `CASCADE`, the cascade will occur first and delete the referenced rows, and no error will be thrown by the deferred constraint. Otherwise if there are still rows referencing the parent row by the end of the transaction, an error will be raised as before. Like `RESTRICT`, the database will not delete, update or set to NULL any rows in the referenced tables.
In practice, you can use either `NO ACTION` or `RESTRICT` depending on your needs. `NO ACTION` is the default behavior if you do not specify anything. If you prefer to defer the check until the end of the transaction, use `NO ACTION INITIALLY DEFERRED`.
diff --git a/apps/docs/content/guides/database/postgres/column-level-security.mdx b/apps/docs/content/guides/database/postgres/column-level-security.mdx
index 13937a5fc64..8a6f7cc87b5 100644
--- a/apps/docs/content/guides/database/postgres/column-level-security.mdx
+++ b/apps/docs/content/guides/database/postgres/column-level-security.mdx
@@ -4,7 +4,7 @@ title: 'Column Level Security'
description: 'Secure your data using Postgres Column Level Security.'
---
-Postgres's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) gives you granular control over who can access rows of data. However, it doesn't give you control over which columns they can access within rows. Sometimes you want to restrict access to specific columns in your database. Column Level Privileges allows you to do just that.
+Postgres's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) gives you granular control over who can access rows of data. However, it doesn't give you control over which columns they can access within rows. Sometimes you want to restrict access to specific columns in your database. Column Level Privileges allows you to do that.
@@ -31,7 +31,7 @@ For example, assume you have a `posts` table with the following columns:
- `created_at`
- `updated_at`
-You can restrict updates to just the user who created it using [RLS](/docs/guides/auth#row-level-security), with the following policy:
+You can restrict updates to the user who created it using [RLS](/docs/guides/auth#row-level-security), with the following policy:
```sql
create policy "Allow update for owners" on posts for
@@ -66,7 +66,7 @@ update
(title, content) on table public.posts to authenticated;
```
-In the above example, we are revoking the table-level `UPDATE` privilege from the `authenticated` role and granting a column-level `UPDATE` privilege on just the `title` and `content` columns.
+In the above example, we are revoking the table-level `UPDATE` privilege from the `authenticated` role and granting a column-level `UPDATE` privilege on the `title` and `content` columns.
If we want to restrict access to updating the `title` column:
diff --git a/apps/docs/content/guides/database/postgres/data-deletion.mdx b/apps/docs/content/guides/database/postgres/data-deletion.mdx
index a27ac37074c..eecfd8f66cb 100644
--- a/apps/docs/content/guides/database/postgres/data-deletion.mdx
+++ b/apps/docs/content/guides/database/postgres/data-deletion.mdx
@@ -47,7 +47,7 @@ delete from logs
where created_at < now() - interval '90 days';
```
-This acquires a `ROW EXCLUSIVE` lock on the table, which still allows other `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements to run concurrently. For small row counts, the operation completes quickly and has minimal impact.
+This acquires a `ROW EXCLUSIVE` lock on the table, which still allows other `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements to run concurrently. For small row counts, the operation completes with minimal impact.
### Large deletes
@@ -188,7 +188,7 @@ If autovacuum is not keeping up, you can trigger a manual vacuum:
vacuum (verbose) logs;
```
-For reclaiming disk space (not just marking tuples as reusable), use `VACUUM FULL` — but be aware this rewrites the entire table and takes an `ACCESS EXCLUSIVE` lock:
+To reclaim disk space rather than only marking tuples as reusable, use `VACUUM FULL`. Note that this rewrites the entire table and takes an `ACCESS EXCLUSIVE` lock:
```sql
-- This locks the table for the duration — use during maintenance windows only
diff --git a/apps/docs/content/guides/database/postgres/event-triggers.mdx b/apps/docs/content/guides/database/postgres/event-triggers.mdx
index 78da36e91e9..682b7d042a5 100644
--- a/apps/docs/content/guides/database/postgres/event-triggers.mdx
+++ b/apps/docs/content/guides/database/postgres/event-triggers.mdx
@@ -61,10 +61,10 @@ See how to [auto enable RLS for new tables](/docs/guides/database/postgres/row-l
Event triggers can be triggered on:
-- `ddl_command_start` - occurs just before a DDL command for almost all objects within a schema
-- `ddl_command_end` - occurs just after a DDL command for almost all objects within a schema
-- `sql_drop` - occurs just before `ddl_command_end` for any DDL commands that `DROP` a database object (note that altering a table can cause it to be dropped)
-- `table_rewrite` - occurs just before a table is rewritten using the `ALTER TABLE` command
+- `ddl_command_start` - occurs before a DDL command for almost all objects within a schema
+- `ddl_command_end` - occurs after a DDL command for almost all objects within a schema
+- `sql_drop` - occurs before `ddl_command_end` for any DDL commands that `DROP` a database object (note that altering a table can cause it to be dropped)
+- `table_rewrite` - occurs before a table is rewritten using the `ALTER TABLE` command
diff --git a/apps/docs/content/guides/database/postgres/indexes.mdx b/apps/docs/content/guides/database/postgres/indexes.mdx
index 3263e5a6e6a..ec1c5a24724 100644
--- a/apps/docs/content/guides/database/postgres/indexes.mdx
+++ b/apps/docs/content/guides/database/postgres/indexes.mdx
@@ -5,7 +5,7 @@ footerHelpType: 'postgres'
tocVideo: 'bBu_V8CfWgM'
---
-An index makes your Postgres queries faster. The index is like a "table of contents" for your data - a reference list which allows queries to quickly locate a row in a given table without needing to scan the entire table (which in large tables can take a long time).
+An index makes your Postgres queries faster. The index is like a "table of contents" for your data - a reference list which allows queries to locate a row in a given table without needing to scan the entire table (which in large tables can take a long time).
Indexes can be structured in a few different ways. The type of index chosen depends on the values you are indexing. By far the most common index type, and the default in Postgres, is the B-Tree. A B-Tree is the generalized form of a binary search tree, where nodes can have more than two children.
@@ -67,7 +67,7 @@ Luckily Postgres provides us with `create index concurrently` which prevents blo
-Here is a simplified diagram of the index we just created (note that in practice, nodes actually have more than two children).
+Here is a simplified diagram of the index we created (note that in practice, nodes have more than two children).
diff --git a/apps/docs/content/guides/database/prisma/prisma-troubleshooting.mdx b/apps/docs/content/guides/database/prisma/prisma-troubleshooting.mdx
index 4918a182eb2..b9e9ff75b14 100644
--- a/apps/docs/content/guides/database/prisma/prisma-troubleshooting.mdx
+++ b/apps/docs/content/guides/database/prisma/prisma-troubleshooting.mdx
@@ -72,7 +72,7 @@ Prisma is unable to allocate connections to pending queries fast enough to meet
#### Possible causes: [#possible-causes-timed-out-fetching-a-new-connection]
- **Overwhelmed server**: The server hosting Prisma is under heavy load, limiting its ability to manage connections. By default, Prisma will create the default `num_cpus * 2 + 1` worth of connections. A common cause for server strain is increasing the `connection_limit` significantly past the default.
-- **Insufficient pool size**: The Supavisor pooler does not have enough connections available to quickly satisfy Prisma's requests.
+- **Insufficient pool size**: The Supavisor pooler does not have enough connections available to satisfy Prisma's requests.
- **Slow queries**: Prisma's queries are taking too long to execute, preventing it from releasing connections for reuse.
#### Solutions: [#solution-timed-out-fetching-a-new-connection]
@@ -124,7 +124,7 @@ Postgres or Supavisor rejected a request for more connections
#### Solutions [#solutions-causes-max-client-connections-reached]
- **Transaction Mode for serverless apps**: If you are using serverless functions (Supabase Edge, Vercel, AWS Lambda), switch to transaction mode (port 6543). It handles more connections than session mode or direct connections.
-- **Reduce the number of Prisma connections**: A single client-server can establish multiple connections with a pooler. Typically, serverless setups do not need many connections. Starting with fewer, like five or three, or even just one, is often sufficient. In serverless setups, begin with `connection_limit=1`, increasing cautiously if needed to avoid maxing out connections.
+- **Reduce the number of Prisma connections**: A single client-server can establish multiple connections with a pooler. Typically, serverless setups do not need many connections. Starting with fewer, like five or three, or even one, is often sufficient. In serverless setups, begin with `connection_limit=1`, increasing cautiously if needed to avoid maxing out connections.
- **Increase pool size**: If you are connecting with Supavisor, try increasing the pool size in the [Database Settings](/dashboard/project/_/database/settings).
- **Disconnect appropriately**: Close Prisma connections when they are no longer needed.
- **Decrease query time**: Reduce query complexity or add [strategic indexes](/docs/guides/database/postgres/indexes) to your tables to speed up queries.
diff --git a/apps/docs/content/guides/database/replication/pipelines-faq.mdx b/apps/docs/content/guides/database/replication/pipelines-faq.mdx
index 2b1ee6b92e8..f8268695983 100644
--- a/apps/docs/content/guides/database/replication/pipelines-faq.mdx
+++ b/apps/docs/content/guides/database/replication/pipelines-faq.mdx
@@ -18,6 +18,14 @@ Pipelines currently supports **BigQuery** as the managed destination. See the [B
We are working on new destinations. Supported destinations can be Supabase-managed or third-party systems as support expands. Availability may continue to vary based on the planned roll-out strategy.
+## Does the destination's region affect performance?
+
+Yes. The further apart your source database, the pipeline, and your destination are, the more network latency is added to replication, which increases replication lag and reduces throughput.
+
+Pipelines run out of **eu-central-1 (Frankfurt)**. For the best performance, place both your source project and your destination as close to this region as possible.
+
+If you can only optimize one side, prioritize placing your **destination** close to the pipeline's region. Replicated data continuously streams out to the destination, so latency on that leg has a bigger impact on overall replication lag than latency between the pipeline and the source.
+
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
## Which plans support Pipelines?
diff --git a/apps/docs/content/guides/database/tables.mdx b/apps/docs/content/guides/database/tables.mdx
index 2a210617936..96c0570f5dc 100644
--- a/apps/docs/content/guides/database/tables.mdx
+++ b/apps/docs/content/guides/database/tables.mdx
@@ -452,6 +452,12 @@ create table private.salaries (
);
```
+
+
+If you want to access a custom schema through the Supabase Data API, you need to expose it and grant the appropriate permissions. See [Using Custom Schemas](/docs/guides/api/using-custom-schemas) for detailed steps. For security best practices around schema exposure, see [Securing your API](/docs/guides/api/securing-your-api).
+
+
+
## Views
A View is a convenient shortcut to a query. Creating a view does not involve new tables or data. When run, an underlying query is executed, returning its results to the user.
@@ -579,7 +585,7 @@ from
where courses.code != 'PG101';
```
-Without a view, we would need to go into every dependent query to add the new rule. This would increase in the likelihood of errors and inconsistencies, as well as introducing a lot of effort for a developer. With views, we can alter just the underlying query in the view **transcripts**. The change will be applied to all applications using this view.
+Without a view, we would need to go into every dependent query to add the new rule. This would increase in the likelihood of errors and inconsistencies, as well as introducing a lot of effort for a developer. With views, we can alter the underlying query in the view **transcripts**. The change will be applied to all applications using this view.
#### Logical organization
diff --git a/apps/docs/content/guides/database/vault.mdx b/apps/docs/content/guides/database/vault.mdx
index 2a6f0788973..8d703a5d509 100644
--- a/apps/docs/content/guides/database/vault.mdx
+++ b/apps/docs/content/guides/database/vault.mdx
@@ -127,7 +127,7 @@ You should ensure that you protect access to this view with the appropriate SQL
### Updating secrets
-A secret can be updated with the `vault.update_secret()` function, this function makes updating secrets easy, just provide the secret UUID as the first argument, and then an updated secret, updated optional unique name, or updated description:
+To update a secret, use the `vault.update_secret()` function. Provide the secret UUID as the first argument, followed by an updated secret, name, or description:
```sql
select
diff --git a/apps/docs/content/guides/database/webhooks.mdx b/apps/docs/content/guides/database/webhooks.mdx
index 3e6a27af497..47496c0c868 100644
--- a/apps/docs/content/guides/database/webhooks.mdx
+++ b/apps/docs/content/guides/database/webhooks.mdx
@@ -12,7 +12,7 @@ You can hook into three table events: `INSERT`, `UPDATE`, and `DELETE`. All even
## Webhooks vs triggers
-Database Webhooks are very similar to triggers, and that's because Database Webhooks are just a convenience wrapper around triggers using the [pg_net](/docs/guides/database/extensions/pgnet) extension. This extension is asynchronous, and therefore will not block your database changes for long-running network requests.
+Database Webhooks are very similar to triggers, and that's because Database Webhooks are a convenience wrapper around triggers using the [pg_net](/docs/guides/database/extensions/pgnet) extension. This extension is asynchronous, and therefore will not block your database changes for long-running network requests.
This video demonstrates how you can create a new customer in Stripe each time a row is inserted into a `profiles` table:
@@ -32,7 +32,7 @@ This video demonstrates how you can create a new customer in Stripe each time a
1. Select the table you want to hook into.
1. Select one or more events (table inserts, updates, or deletes) you want to hook into.
-Since webhooks are just database triggers, you can also create one from SQL statement directly.
+Since webhooks are database triggers, you can also create one from SQL statement directly.
```sql
create trigger "my_webhook" after insert
diff --git a/apps/docs/content/guides/deployment/database-migrations.mdx b/apps/docs/content/guides/deployment/database-migrations.mdx
index cc2333d0cbe..d50618060c2 100644
--- a/apps/docs/content/guides/deployment/database-migrations.mdx
+++ b/apps/docs/content/guides/deployment/database-migrations.mdx
@@ -475,13 +475,13 @@ This creates a new migration file capturing the current remote schema. Commit it
### Step 3: If the migration history table is wrong
-If a migration shows as missing in the remote history table but the schema change is actually already there (for example, it was applied manually), you can mark it as applied without re-running it:
+If a migration shows as missing in the remote history table but the schema change is already there (for example, it was applied manually), you can mark it as applied without re-running it:
```bash name=Terminal
supabase migration repair --status applied
```
-Or if a migration is recorded as applied but was never actually run:
+Or if a migration is recorded as applied but was never run:
```bash name=Terminal
supabase migration repair --status reverted
diff --git a/apps/docs/content/guides/deployment/going-into-prod.mdx b/apps/docs/content/guides/deployment/going-into-prod.mdx
index ce8879923f2..ffa3b028896 100644
--- a/apps/docs/content/guides/deployment/going-into-prod.mdx
+++ b/apps/docs/content/guides/deployment/going-into-prod.mdx
@@ -22,7 +22,7 @@ Check and review issues in your database using [Security Advisor](/dashboard/pro
- Tables that do not have RLS enabled with reasonable policies allow any client to access and modify their data. This is usually not what you want.
- [Learn more about RLS](/docs/guides/database/postgres/row-level-security).
- Enable replication on tables containing sensitive data by enabling RLS and setting row security policies:
- - Go to the [**Authentication > Policies**](/dashboard/project/_/auth/policies) section of the Supabase Dashboard to enable RLS and create security policies.
+ - Go to the [**Database > Policies**](/dashboard/project/_/database/policies) section of the Supabase Dashboard to enable RLS and create security policies.
- Go to the [**Database > Publications**](/dashboard/project/_/database/publications) section of the Supabase Dashboard to manage replication tables.
- Turn on [SSL Enforcement](/docs/guides/platform/ssl-enforcement) from the [**Database > Settings > SSL Configuration**](/dashboard/project/_/database/settings#ssl-configuration) section of the dashboard.
- Enable [Network Restrictions](/docs/guides/platform/network-restrictions) for the database from the [**Database > Settings > Network Restrictions**](/dashboard/project/_/database/settings#network-restrictions) section of the dashboard.
diff --git a/apps/docs/content/guides/functions.mdx b/apps/docs/content/guides/functions.mdx
index 87d1b7f42e4..2982243fa10 100644
--- a/apps/docs/content/guides/functions.mdx
+++ b/apps/docs/content/guides/functions.mdx
@@ -1,8 +1,8 @@
---
id: 'functions'
title: 'Edge Functions'
-description: 'Globally distributed TypeScript functions.'
-subtitle: 'Globally distributed TypeScript functions.'
+description: 'Run TypeScript functions globally at the edge.'
+subtitle: 'Run TypeScript functions globally at the edge.'
sidebar_label: 'Overview'
tocVideo: 'za_loEtS4gs'
---
@@ -41,295 +41,18 @@ Edge Functions are server-side TypeScript functions, distributed globally at the
- Sending transactional emails.
- Building messaging bots for Slack, Discord, etc.
-
+
## Examples
-Check out the [Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in our GitHub repository.
+Check out [Supabase Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in GitHub or try these examples:
-
-
-
-
- Use the Supabase client inside your Edge Function.
-
-
-
-
-
-
- Combining Kysely with Deno Postgres gives you a convenient developer experience for
- interacting directly with your Postgres database.
-
-
-
-
-
-
- Monitor Edge Functions with the Sentry Deno SDK.
-
-
-
-
-
-
- Send CORS headers for invoking from the browser.
-
-
-
-
-
-
- Full example for using Supabase and Stripe, with Expo.
-
-
-
-
-
-
- Full example for using Supabase and Stripe, with Flutter.
-
-
-
-
-
-
- Learn how to use HTTP methods and paths to build a RESTful service for managing tasks.
-
-
-
-
-
-
- An example on reading a file from Supabase Storage.
-
-
-
-
-
-
- Generate Open Graph images with Deno and Supabase Edge Functions.
-
-
-
-
-
-
- Cache generated images with Supabase Storage CDN.
-
-
-
-
-
-
- Get user location data from user's IP address.
-
-
-
-
-
-
- Protecting Forms with Cloudflare Turnstile.
-
-
-
-
-
-
- Connecting to Postgres from Edge Functions.
-
-
-
-
-
-
- Deploying Edge Functions with GitHub Actions.
-
-
-
-
-
-
- Request Routing with Oak server middleware.
-
-
-
-
-
-
- Access 100,000+ Machine Learning models.
-
-
-
-
-
-
- Amazon Bedrock Image Generator
-
-
-
-
-
-
- Using OpenAI in Edge Functions.
-
-
-
-
-
-
- Handling signed Stripe Webhooks with Edge Functions.
-
-
-
-
-
-
- Send emails in Edge Functions with Resend.
-
-
-
-
-
-
- Server-Sent Events in Edge Functions.
-
-
-
-
-
-
- Generate screenshots with Puppeteer.
-
-
-
-
-
-
- Building a Slash Command Discord Bot with Edge Functions.
-
-
-
-
-
-
- Building a Telegram Bot with Edge Functions.
-
-
-
-
-
-
- Process multipart/form-data.
-
-
-
-
-
-
- Build an Edge Functions Counter with Upstash Redis.
-
-
-
-
-
-
- Rate Limiting Edge Functions with Upstash Redis.
-
-
-
-
-
-
- Slack Bot handling Slack mentions in Edge Function
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/apps/docs/content/guides/functions/background-tasks.mdx b/apps/docs/content/guides/functions/background-tasks.mdx
index c893c8227b6..607da02b5b0 100644
--- a/apps/docs/content/guides/functions/background-tasks.mdx
+++ b/apps/docs/content/guides/functions/background-tasks.mdx
@@ -9,7 +9,7 @@ Edge Function instances can process background tasks outside of the request hand
This allows you to:
-- Respond quickly to users while processing continues
+- Respond to users while processing continues
- Handle async operations without blocking the response
---
diff --git a/apps/docs/content/guides/functions/deploy.mdx b/apps/docs/content/guides/functions/deploy.mdx
index e5ccb12e30d..d1a05e34bf1 100644
--- a/apps/docs/content/guides/functions/deploy.mdx
+++ b/apps/docs/content/guides/functions/deploy.mdx
@@ -40,7 +40,7 @@ If you haven't yet created a Supabase project, you can do so by visiting [databa
-[Link](/docs/reference/cli/usage#supabase-link) your local project to your remote Supabase project using the ID you just retrieved:
+[Link](/docs/reference/cli/usage#supabase-link) your local project to your remote Supabase project using the ID you retrieved:
```bash
supabase link --project-ref your-project-id
diff --git a/apps/docs/content/guides/functions/examples/semantic-search.mdx b/apps/docs/content/guides/functions/examples/semantic-search.mdx
index b278fc12503..e2a29dfa470 100644
--- a/apps/docs/content/guides/functions/examples/semantic-search.mdx
+++ b/apps/docs/content/guides/functions/examples/semantic-search.mdx
@@ -136,4 +136,4 @@ export default {
}
```
-You now have AI powered semantic search set up without any external dependencies! Just you, pgvector, and Supabase Edge Functions!
+You now have AI powered semantic search set up without any external dependencies! All you need: you, pgvector, and Supabase Edge Functions!
diff --git a/apps/docs/content/guides/functions/examples/slack-bot-mention.mdx b/apps/docs/content/guides/functions/examples/slack-bot-mention.mdx
index 1e8b854fdbe..ea9007623b6 100644
--- a/apps/docs/content/guides/functions/examples/slack-bot-mention.mdx
+++ b/apps/docs/content/guides/functions/examples/slack-bot-mention.mdx
@@ -20,8 +20,9 @@ For your bot to seamlessly interact with Slack, you'll need to configure Slack A
Deploy the following code as an Edge function using the CLI:
```bash
-supabase --project-ref nacho_slacker secrets \
-set SLACK_TOKEN=
+supabase secrets set \
+ SLACK_TOKEN= \
+ --project-ref nacho_slacker
```
Here's the code of the Edge Function, you can change the response to handle the text received:
diff --git a/apps/docs/content/guides/getting-started.mdx b/apps/docs/content/guides/getting-started.mdx
index 45a0a7892cc..f0ff033351a 100644
--- a/apps/docs/content/guides/getting-started.mdx
+++ b/apps/docs/content/guides/getting-started.mdx
@@ -5,36 +5,7 @@ description: 'Resources for getting started with Supabase.'
hideToc: true
---
-
-
-
-
-
-
-
- Develop with Supabase AI-first using plugins, MCP, and skills.
-
-
-
-
- Learn about the different API keys in Supabase and how to use them.
-
-
-
-
- Use the Supabase CLI to develop locally and collaborate between teams.
-
-
-
-
-
-
-
+
### Use cases
diff --git a/apps/docs/content/guides/getting-started/api-keys.mdx b/apps/docs/content/guides/getting-started/api-keys.mdx
index 3ca3976f52a..b1cff12b380 100644
--- a/apps/docs/content/guides/getting-started/api-keys.mdx
+++ b/apps/docs/content/guides/getting-started/api-keys.mdx
@@ -32,6 +32,12 @@ There are 4 types of API keys that you can use with Supabase:
| `anon` | JWT (long lived) | Low | Platform, CLI | Legacy version of publishable keys. |
| `service_role` | JWT (long lived) | Elevated | Platform, CLI | Legacy version of secret keys. |
+
+
+Both key types work simultaneously. Creating publishable and secret keys adds them _alongside_ your existing `anon` and `service_role` keys without affecting them — your legacy keys keep working. They remain valid until you explicitly disable them in the [**Settings > API Keys**](/dashboard/project/_/settings/api-keys/) section of the Dashboard which is a separate step. See [Migrating to new API keys](/docs/guides/getting-started/migrating-to-new-api-keys) for the full process.
+
+
+
<$Partial path="api_keys_deprecation.mdx" />
## Publishable keys
diff --git a/apps/docs/content/guides/getting-started/quickstarts/astrojs.mdx b/apps/docs/content/guides/getting-started/quickstarts/astrojs.mdx
index 1dd467b0fb3..392227c9fb6 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/astrojs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/astrojs.mdx
@@ -2,191 +2,124 @@
title: 'Use Supabase with Astro'
subtitle: 'Learn how to create a Supabase project, add sample data, and query from an Astro app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: false
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create an Astro app
- <$Partial path="quickstart_db_setup.mdx" />
+Create an Astro app using the `npm create` command.
-
+```bash
+npm create astro@latest my-app
+cd my-app
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- - Create an Astro app using the `npm create` command.
+To install, run the following command in the root of your project:
- <$Partial path="uiLibCta.mdx" />
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install Supabase client library and Node adapter
-
+Install the `supabase-js` client library and the `@astrojs/node` adapter to enable server-side rendering.
- ```bash name=Terminal
- npm create astro@latest my-app
- cd my-app
- ```
+```bash
+npm install @supabase/supabase-js @astrojs/node
+```
-
+## 6. Configure Astro for SSR
-
+Update your `astro.config.mjs`.
-
-
+```js name=astro.config.mjs
+import node from '@astrojs/node'
+import { defineConfig } from 'astro/config'
- Install the `supabase-js` client library and the `@astrojs/node` adapter to enable server-side rendering.
+export default defineConfig({
+ output: 'server',
+ adapter: node({
+ mode: 'standalone',
+ }),
+})
+```
-
+## 7. Declare Supabase environment variables
-
+Create a `.env.local` file and populate with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=astro&tab=frameworks):
- ```bash name=Terminal
- npm install @supabase/supabase-js @astrojs/node
- ```
+
+
+ Open Connect panel
+
+
-
+```text name=.env.local
+PUBLIC_SUPABASE_URL=
+PUBLIC_SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "astro", "tab": "frameworks" }} />
-
-
+## 8. Create a Supabase client helper
- Update your `astro.config.mjs`.
+Create a utility file to initialize the Supabase client:
-
+```ts name=src/lib/supabase.ts
+import { createClient } from '@supabase/supabase-js'
-
+const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL
+const supabasePublishableKey = import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY
- ```js name=astro.config.mjs
- import { defineConfig } from "astro/config";
- import node from "@astrojs/node";
+export function createServerClient() {
+ return createClient(supabaseUrl, supabasePublishableKey)
+}
+```
- export default defineConfig({
- output: "server",
- adapter: node({
- mode: "standalone",
- }),
- });
- ```
+## 9. Query Supabase data from Astro
-
+Create a new file at `src/pages/instruments.astro` and populate with the following.
-
+This queries all rows from the `instruments` table you created earlier and renders them on the page.
-
-
+```astro name=src/pages/instruments.astro
+---
+import { createServerClient } from "../lib/supabase";
- Create a `.env.local` file and populate with your Supabase connection variables:
+const supabase = createServerClient();
+const { data: instruments } = await supabase.from("instruments").select();
+---
-
-
+
+
+ Instruments
+
+
+
+ {instruments?.map((instrument) => (
+ {instrument.name}
+ ))}
+
+
+
+```
-
+## 10. Start the app
-
+Run the development server, go to http://localhost:4321/instruments in your browser of choice to check the list of instruments.
- <$CodeTabs>
-
- ```text name=.env.local
- PUBLIC_SUPABASE_URL=
- PUBLIC_SUPABASE_PUBLISHABLE_KEY=
- ```
-
- $CodeTabs>
-
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "astro", "tab": "frameworks" }} />
-
-
-
-
-
-
-
-
- Create a utility file to initialize the Supabase client:
-
-
-
-
-
- ```ts name=src/lib/supabase.ts
- import { createClient } from "@supabase/supabase-js";
-
- const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL
- const supabasePublishableKey = import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY
-
- export function createServerClient() {
- return createClient(
- supabaseUrl,
- supabasePublishableKey
- );
- }
- ```
-
-
-
-
-
-
-
-
- Create a new file at `src/pages/instruments.astro` and populate with the following.
-
- This queries all rows from the `instruments` table in Supabase and renders them on the page.
-
-
-
-
-
- ```astro name=src/pages/instruments.astro
- ---
- import { createServerClient } from "../lib/supabase";
-
- const supabase = createServerClient();
- const { data: instruments } = await supabase.from("instruments").select();
- ---
-
-
-
- Instruments
-
-
-
- {instruments?.map((instrument) => (
- {instrument.name}
- ))}
-
-
-
- ```
-
-
-
-
-
-
-
-
- Run the development server, go to http://localhost:4321/instruments in your browser of choice to check the list of instruments.
-
-
-
-
-
- ```bash name=Terminal
- npm run dev
- ```
-
-
-
-
-
+```bash
+npm run dev
+```
## Next steps
+- Explore [drop-in UI components](/ui) for your Supabase app
- Set up [Auth](/docs/guides/auth) for your app
- [Insert more data](/docs/guides/database/import-data) into your database
- Upload and serve static files using [Storage](/docs/guides/storage)
diff --git a/apps/docs/content/guides/getting-started/quickstarts/expo-react-native.mdx b/apps/docs/content/guides/getting-started/quickstarts/expo-react-native.mdx
index a4f1b58e0a8..d9b581b5e9f 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/expo-react-native.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/expo-react-native.mdx
@@ -2,191 +2,138 @@
title: 'Use Supabase with Expo React Native'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Expo app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create an Expo app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a minimal Expo app using the `create-expo-app` command with the blank TypeScript template.
-
+```bash
+npx create-expo-app my-app --template blank-typescript
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- Create a minimal Expo app using the `create-expo-app` command with the blank TypeScript template.
+To install, run the following command in the root of your project:
- <$Partial path="uiLibCta.mdx" />
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
-
+The fastest way to get started is to use the `@supabase/supabase-js` client library which provides a convenient interface for working with Supabase from a React Native app.
- ```bash name=Terminal
- npx create-expo-app my-app --template blank-typescript
- ```
+Navigate to the Expo app and install `supabase-js` along with the required dependencies for session storage and URL handling.
-
+```bash
+cd my-app && npx expo install @supabase/supabase-js react-native-url-polyfill expo-sqlite
+```
-
+## 6. Declare Supabase environment variables
-
-
+Create a `.env` file in the root of your project and populate it with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true).
- The fastest way to get started is to use the `@supabase/supabase-js` client library which provides a convenient interface for working with Supabase from a React Native app.
+
+ Open Connect panel
+
- Navigate to the Expo app and install `supabase-js` along with the required dependencies for secure storage and URL handling.
+Expo requires environment variables to be prefixed with `EXPO_PUBLIC_` to be accessible in your app code.
-
+```text name=.env
+EXPO_PUBLIC_SUPABASE_URL=
+EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "", "tab": "" }} />
- ```bash name=Terminal
- cd my-app && npx expo install @supabase/supabase-js react-native-url-polyfill expo-sqlite
- ```
+## 7. Initialize the Supabase client
-
+Create a helper file at `lib/supabase.ts` to initialize the Supabase client using the environment variables.
-
+The code below uses Expo's localStorage polyfill to persist authentication sessions.
-
-
+```ts name=lib/supabase.ts
+import 'react-native-url-polyfill/auto'
- Create a `.env` file in the root of your project and populate it with your Supabase connection variables.
+import { createClient } from '@supabase/supabase-js'
- Expo requires environment variables to be prefixed with `EXPO_PUBLIC_` to be accessible in your app code.
+import 'expo-sqlite/localStorage/install'
-
-
+const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL
+const supabasePublishableKey = process.env.EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY
+export const supabase = createClient(supabaseUrl, supabasePublishableKey, {
+ auth: {
+ storage: localStorage,
+ autoRefreshToken: true,
+ persistSession: true,
+ detectSessionInUrl: false,
+ },
+})
+```
-
+## 8. Query data from the app
-
+Replace the contents of `App.tsx` with the following code to fetch and display the instruments from your database.
- ```text name=.env
- EXPO_PUBLIC_SUPABASE_URL=
- EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
- ```
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "", "tab": "" }} />
+Use `useEffect` to fetch the data when the component mounts and display the query result using React Native components.
-
+```tsx name=App.tsx
+import { useEffect, useState } from 'react'
+import { FlatList, StyleSheet, Text, View } from 'react-native'
-
+import { supabase } from './lib/supabase'
-
-
+export default function App() {
+ const [instruments, setInstruments] = useState([])
- Create a helper file at `lib/supabase.ts` to initialize the Supabase client using the environment variables.
+ useEffect(() => {
+ getInstruments()
+ }, [])
- The code below uses Expo's localStorage polyfill to persist authentication sessions.
+ async function getInstruments() {
+ const { data } = await supabase.from('instruments').select()
+ setInstruments(data)
+ }
-
+ return (
+
+ item.id.toString()}
+ renderItem={({ item }) => {item.name} }
+ />
+
+ )
+}
-
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#fff',
+ paddingTop: 50,
+ paddingHorizontal: 16,
+ },
+ item: {
+ padding: 16,
+ borderBottomWidth: 1,
+ borderBottomColor: '#ccc',
+ },
+})
+```
- ```ts name=lib/supabase.ts
- import 'react-native-url-polyfill/auto'
- import { createClient } from '@supabase/supabase-js'
- import 'expo-sqlite/localStorage/install';
+## 9. Start the app
- const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL
- const supabasePublishableKey = process.env.EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY
+Run the development server and scan the QR code with the Expo Go app on your phone, or press `i` for iOS simulator or `a` for Android emulator.
- export const supabase = createClient(supabaseUrl, supabasePublishableKey, {
- auth: {
- storage: localStorage,
- autoRefreshToken: true,
- persistSession: true,
- detectSessionInUrl: false,
- },
- })
- ```
-
-
-
-
-
-
-
-
- Replace the contents of `App.tsx` with the following code to fetch and display the instruments from your database.
-
- Use `useEffect` to fetch the data when the component mounts and display the query result using React Native components.
-
-
-
-
- ```tsx name=App.tsx
- import { useEffect, useState } from 'react'
- import { StyleSheet, View, FlatList, Text } from 'react-native'
- import { supabase } from './lib/supabase'
-
- export default function App() {
- const [instruments, setInstruments] = useState([])
-
- useEffect(() => {
- getInstruments()
- }, [])
-
- async function getInstruments() {
- const { data } = await supabase.from('instruments').select()
- setInstruments(data)
- }
-
- return (
-
- item.id.toString()}
- renderItem={({ item }) => (
- {item.name}
- )}
- />
-
- )
- }
-
- const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#fff',
- paddingTop: 50,
- paddingHorizontal: 16,
- },
- item: {
- padding: 16,
- borderBottomWidth: 1,
- borderBottomColor: '#ccc',
- },
- })
- ```
-
-
-
-
-
-
-
-
- Run the development server and scan the QR code with the Expo Go app on your phone, or press `i` for iOS simulator or `a` for Android emulator.
-
-
-
-
-
- ```bash name=Terminal
- npx expo start
- ```
-
-
-
-
-
-
+```bash
+npx expo start
+```
## Next steps
diff --git a/apps/docs/content/guides/getting-started/quickstarts/flask.mdx b/apps/docs/content/guides/getting-started/quickstarts/flask.mdx
index c20e845f9ea..aa3be961447 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/flask.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/flask.mdx
@@ -2,150 +2,103 @@
title: 'Use Supabase with Python'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Python app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a Python app with Flask
- <$Partial path="quickstart_db_setup.mdx" />
+Create a new directory for your Python app and set up a virtual environment.
-
+```bash
+mkdir my-app && cd my-app
+python3 -m venv venv
+source venv/bin/activate
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- Create a new directory for your Python app and set up a virtual environment.
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install Flask and the Supabase client library
- ```bash name=Terminal
- mkdir my-app && cd my-app
- python3 -m venv venv
- source venv/bin/activate
- ```
+The fastest way to get started is to use Flask for the web framework and the `supabase-py` client library which provides a convenient interface for working with Supabase from a Python app.
-
+Install both packages using pip.
-
+```bash
+pip install flask supabase
+```
-
-
+## 6. Create environment variables file
- The fastest way to get started is to use Flask for the web framework and the `supabase-py` client library which provides a convenient interface for working with Supabase from a Python app.
+Create a `.env` file in your project root and populate it with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true):
- Install both packages using pip.
+
+ Open Connect panel
+
-
+```text name=.env
+SUPABASE_URL=
+SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "", "tab": "" }} />
- ```bash name=Terminal
- pip install flask supabase
- ```
+## 7. Query data from the app
-
+Install the `python-dotenv` package to load environment variables:
-
+```bash
+pip install python-dotenv
+```
-
-
+Create an `app.py` file and add a route that fetches data from your `instruments` table using the Supabase client.
- Create a `.env` file in your project root and populate it with your Supabase connection variables:
+```python name=app.py
+import os
+from flask import Flask
+from supabase import create_client, Client
+from dotenv import load_dotenv
-
-
+load_dotenv()
+app = Flask(__name__)
-
+supabase: Client = create_client(
+ os.environ.get("SUPABASE_URL"),
+ os.environ.get("SUPABASE_PUBLISHABLE_KEY")
+)
-
+@app.route('/')
+def index():
+ response = supabase.table('instruments').select("*").execute()
+ instruments = response.data
- <$CodeTabs>
+ html = 'Instruments '
+ for instrument in instruments:
+ html += f'{instrument["name"]} '
+ html += ' '
- ```text name=.env
- SUPABASE_URL=
- SUPABASE_PUBLISHABLE_KEY=
- ```
+ return html
- $CodeTabs>
+if __name__ == '__main__':
+ app.run(debug=True)
+```
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "", "tab": "" }} />
+## 8. Start the app
-
+Run the Flask development server, and go to http://localhost:5000 in your browser, you should see the list of instruments.
-
-
-
-
-
- Install the `python-dotenv` package to load environment variables:
-
- ```bash
- pip install python-dotenv
- ```
-
- Create an `app.py` file and add a route that fetches data from your `instruments` table using the Supabase client.
-
-
-
-
- ```python name=app.py
- import os
- from flask import Flask
- from supabase import create_client, Client
- from dotenv import load_dotenv
-
- load_dotenv()
-
- app = Flask(__name__)
-
- supabase: Client = create_client(
- os.environ.get("SUPABASE_URL"),
- os.environ.get("SUPABASE_PUBLISHABLE_KEY")
- )
-
- @app.route('/')
- def index():
- response = supabase.table('instruments').select("*").execute()
- instruments = response.data
-
- html = 'Instruments '
- for instrument in instruments:
- html += f'{instrument["name"]} '
- html += ' '
-
- return html
-
- if __name__ == '__main__':
- app.run(debug=True)
- ```
-
-
-
-
-
-
-
-
- Run the Flask development server, go to http://localhost:5000 in a browser and you should see the list of instruments.
-
-
-
-
-
- ```bash name=Terminal
- python app.py
- ```
-
-
-
-
-
+```bash
+python app.py
+```
## Next steps
diff --git a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx
index 498936ca67f..c34cef02b6a 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx
@@ -2,179 +2,133 @@
title: 'Use Supabase with Flutter'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Flutter app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a Flutter app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a Flutter app using the `flutter create` command.
-
+```bash
+flutter create my_app
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- Create a Flutter app using the `flutter create` command. You can skip this step if you already have a working app.
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
- ```bash name=Terminal
- flutter create my_app
- ```
+The fastest way to get started is to use the [`supabase_flutter`](https://pub.dev/packages/supabase_flutter) client library which provides a convenient interface for working with Supabase from a Flutter app.
-
+Open the `pubspec.yaml` file inside your Flutter app and add `supabase_flutter` as a dependency.
-
+```yaml name=pubspec.yaml
+supabase_flutter: ^2.0.0
+```
-
+## 6. Initialize the Supabase client
-
+Open `lib/main.dart` and edit the main function to initialize Supabase using your project URL and publishable key, which you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=flutter&tab=mobiles):
- The fastest way to get started is to use the [`supabase_flutter`](https://pub.dev/packages/supabase_flutter) client library which provides a convenient interface for working with Supabase from a Flutter app.
+
+
+ Open Connect panel
+
+
- Open the `pubspec.yaml` file inside your Flutter app and add `supabase_flutter` as a dependency.
+```dart name=lib/main.dart
+import 'package:supabase_flutter/supabase_flutter.dart';
-
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
-
+ await Supabase.initialize(
+ url: 'YOUR_SUPABASE_URL',
+ publishableKey: 'YOUR_SUPABASE_PUBLISHABLE_KEY',
+ );
+ runApp(MyApp());
+}
+```
- ```yaml name=pubspec.yaml
- supabase_flutter: ^2.0.0
- ```
+<$Partial path="api_settings.mdx" variables={{ "framework": "flutter", "tab": "mobiles" }} />
-
+## 7. Query data from the app
-
+Use a `FutureBuilder` to fetch the data when the home page loads and display the query result in a `ListView`.
-
+Replace the default `MyApp` and `MyHomePage` classes with the following code.
-
+```dart name=lib/main.dart
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
- Open `lib/main.dart` and edit the main function to initialize Supabase using your project URL and publishable key:
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ title: 'Instruments',
+ home: HomePage(),
+ );
+ }
+}
-
-
+class HomePage extends StatefulWidget {
+ const HomePage({super.key});
+ @override
+ State createState() => _HomePageState();
+}
-
+class _HomePageState extends State {
+ final _future = Supabase.instance.client
+ .from('instruments')
+ .select();
-
-
- ```dart name=lib/main.dart
- import 'package:supabase_flutter/supabase_flutter.dart';
-
- Future main() async {
- WidgetsFlutterBinding.ensureInitialized();
-
- await Supabase.initialize(
- url: 'YOUR_SUPABASE_URL',
- publishableKey: 'YOUR_SUPABASE_PUBLISHABLE_KEY',
- );
- runApp(MyApp());
- }
- ```
-
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "flutter", "tab": "mobiles" }} />
-
-
-
-
-
-
-
-
-
- Use a `FutureBuilder` to fetch the data when the home page loads and display the query result in a `ListView`.
-
- Replace the default `MyApp` and `MyHomePage` classes with the following code.
-
-
-
-
-
- ```dart name=lib/main.dart
- class MyApp extends StatelessWidget {
- const MyApp({super.key});
-
- @override
- Widget build(BuildContext context) {
- return const MaterialApp(
- title: 'Instruments',
- home: HomePage(),
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: FutureBuilder(
+ future: _future,
+ builder: (context, snapshot) {
+ if (!snapshot.hasData) {
+ return const Center(child: CircularProgressIndicator());
+ }
+ final instruments = snapshot.data!;
+ return ListView.builder(
+ itemCount: instruments.length,
+ itemBuilder: ((context, index) {
+ final instrument = instruments[index];
+ return ListTile(
+ title: Text(instrument['name']),
+ );
+ }),
);
- }
- }
+ },
+ ),
+ );
+ }
+}
+```
- class HomePage extends StatefulWidget {
- const HomePage({super.key});
+## 8. Start the app
- @override
- State createState() => _HomePageState();
- }
+Run your app on a platform of your choosing! By default an app should launch in your web browser.
- class _HomePageState extends State {
- final _future = Supabase.instance.client
- .from('instruments')
- .select();
+Note that `supabase_flutter` is compatible with web, iOS, Android, macOS, and Windows apps.
+Running the app on macOS requires additional configuration to [set the entitlements](https://docs.flutter.dev/development/platform-integration/macos/building#setting-up-entitlements).
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: FutureBuilder(
- future: _future,
- builder: (context, snapshot) {
- if (!snapshot.hasData) {
- return const Center(child: CircularProgressIndicator());
- }
- final instruments = snapshot.data!;
- return ListView.builder(
- itemCount: instruments.length,
- itemBuilder: ((context, index) {
- final instrument = instruments[index];
- return ListTile(
- title: Text(instrument['name']),
- );
- }),
- );
- },
- ),
- );
- }
- }
- ```
+```bash
+flutter run
+```
-
-
-
-
-
-
-
- Run your app on a platform of your choosing! By default an app should launch in your web browser.
-
- Note that `supabase_flutter` is compatible with web, iOS, Android, macOS, and Windows apps.
- Running the app on macOS requires additional configuration to [set the entitlements](https://docs.flutter.dev/development/platform-integration/macos/building#setting-up-entitlements).
-
-
-
-
-
- ```bash name=Terminal
- flutter run
- ```
-
-
-
-
-
-
-
-## Setup deep links
+## 9. Setup deep links (optional)
Many sign in methods require deep links to redirect the user back to your app after authentication. Read more about setting deep links up for all platforms (including web) in the [Flutter Mobile Guide](/docs/guides/getting-started/tutorials/with-flutter#setup-deep-links).
diff --git a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx
index 4f81cce2f7f..6d9c331172c 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx
@@ -2,91 +2,58 @@
title: 'Use Supabase with Hono'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, secure it with auth, and query the data from a Hono app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a Hono app
-
+Bootstrap the Hono example app from the Supabase Samples using the CLI.
- Bootstrap the Hono example app from the Supabase Samples using the CLI.
+```bash
+npx supabase@latest bootstrap hono
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- ```bash name=Terminal
- npx supabase@latest bootstrap hono
- ```
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
-
-
+The `package.json` file in the project includes the necessary dependencies, including `@supabase/supabase-js` and `@supabase/ssr` to help with server-side auth.
- The `package.json` file in the project includes the necessary dependencies, including `@supabase/supabase-js` and `@supabase/ssr` to help with server-side auth.
+```bash
+npm install
+```
-
+## 6. Set up the required environment variables
-
+Copy the `.env.example` file to `.env` and update the values with your Supabase project URL and publishable key, which you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&tab=frameworks).
- ```bash name=Terminal
- npm install
- ```
+Lastly, [enable anonymous sign-ins](/dashboard/project/_/auth/providers) in the Auth settings.
-
-
-
-
-
-
-
- Copy the `.env.example` file to `.env` and update the values with your Supabase project URL and publishable key.
-
- Lastly, [enable anonymous sign-ins](/dashboard/project/_/auth/providers) in the Auth settings.
-
-
-
-
-
-
-
-
-
- ```bash name=Terminal
- cp .env.example .env
- ```
+```bash
+cp .env.example .env
+```
{/* TODO: Not ideal for frameworks that have no entry in Connect */}
-<$Partial path="api_settings_steps.mdx" variables={{ "framework": "", "tab": "frameworks" }} />
+<$Partial path="api_settings.mdx" variables={{ "framework": "", "tab": "frameworks" }} />
-
+## 7. Start the app
-
+Start the app, go to http://localhost:5173.
-
-
+Learn how [server side auth](/docs/guides/auth/server-side/creating-a-client?queryGroups=framework&framework=hono) works with Hono.
- Start the app, go to http://localhost:5173.
-
- Learn how [server side auth](/docs/guides/auth/server-side/creating-a-client?queryGroups=framework&framework=hono) works with Hono.
-
-
-
-
-
- ```bash name=Terminal
- npm run dev
- ```
-
-
-
-
-
+```bash
+npm run dev
+```
## Next steps
diff --git a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx
index d19a1ddb758..24523e7027e 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx
@@ -2,148 +2,104 @@
title: 'Use Supabase with iOS and SwiftUI'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an iOS app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create an iOS SwiftUI app with Xcode
- <$Partial path="quickstart_db_setup.mdx" />
+Select the **Xcode > New Project > iOS > App** menu item.
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
-
+To install, run the following command in the root of your project:
- Open Xcode > New Project > iOS > App. You can skip this step if you already have a working app.
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
-
+Add the [supabase-swift](https://github.com/supabase/supabase-swift) package to your app using the Swift Package Manager.
-
+In Xcode, navigate to **File > Add Package Dependencies...** and enter the repository URL `https://github.com/supabase/supabase-swift` in the search bar. For detailed instructions, see Apple's [tutorial on adding package dependencies](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app).
-
+Make sure to add `Supabase` product package as a dependency to your application target.
- Add the [supabase-swift](https://github.com/supabase/supabase-swift) package to your app using the Swift Package Manager.
+## 6. Initialize the Supabase client
- In Xcode, navigate to **File > Add Package Dependencies...** and enter the repository URL `https://github.com/supabase/supabase-swift` in the search bar. For detailed instructions, see Apple's [tutorial on adding package dependencies](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app).
+Create a new `Supabase.swift` file add a new Supabase instance using your project URL and publishable key, which you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=swift&tab=mobiles):
- Make sure to add `Supabase` product package as a dependency to your application target.
+
+
+ Open Connect panel
+
+
-
+```swift name=Supabase.swift
+import Supabase
-
+let supabase = SupabaseClient(
+ supabaseURL: URL(string: "YOUR_SUPABASE_URL")!,
+ supabaseKey: "YOUR_SUPABASE_PUBLISHABLE_KEY"
+)
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "swift", "tab": "mobiles" }} />
-
+## 7. Create a data model for instruments
- Create a new `Supabase.swift` file add a new Supabase instance using your project URL and publishable key:
+Create a decodable struct to deserialize the data from the database.
-
-
+Add the following code to a new file named `Instrument.swift`.
+```swift name=Instrument.swift
+struct Instrument: Decodable, Identifiable {
+ let id: Int
+ let name: String
+}
+```
-
+## 8. Query data from the app
-
+Use a `task` to fetch the data from the database and display it using a `List`.
- ```swift name=Supabase.swift
- import Supabase
+Replace the default `ContentView` with the following code.
- let supabase = SupabaseClient(
- supabaseURL: URL(string: "YOUR_SUPABASE_URL")!,
- supabaseKey: "YOUR_SUPABASE_PUBLISHABLE_KEY"
- )
- ```
+```swift name=ContentView.swift
+import SwiftUI
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "swift", "tab": "mobiles" }} />
+struct ContentView: View {
-
+ @State var instruments: [Instrument] = []
-
-
-
-
-
-
- Create a decodable struct to deserialize the data from the database.
-
- Add the following code to a new file named `Instrument.swift`.
-
-
-
-
-
- ```swift name=Instrument.swift
- struct Instrument: Decodable, Identifiable {
- let id: Int
- let name: String
+ var body: some View {
+ List(instruments) { instrument in
+ Text(instrument.name)
+ }
+ .overlay {
+ if instruments.isEmpty {
+ ProgressView()
}
- ```
-
-
-
-
-
-
-
-
-
- Use a `task` to fetch the data from the database and display it using a `List`.
-
- Replace the default `ContentView` with the following code.
-
-
-
-
-
- ```swift name=ContentView.swift
- import SwiftUI
-
- struct ContentView: View {
-
- @State var instruments: [Instrument] = []
-
- var body: some View {
- List(instruments) { instrument in
- Text(instrument.name)
- }
- .overlay {
- if instruments.isEmpty {
- ProgressView()
- }
- }
- .task {
- do {
- instruments = try await supabase.from("instruments").select().execute().value
- } catch {
- dump(error)
- }
- }
- }
+ }
+ .task {
+ do {
+ instruments = try await supabase.from("instruments").select().execute().value
+ } catch {
+ dump(error)
}
- ```
+ }
+ }
+}
+```
-
+## 9. Start the app
-
+Run the app on a simulator or a physical device by hitting `Cmd + R` on Xcode.
-
-
-
- Run the app on a simulator or a physical device by hitting `Cmd + R` on Xcode.
-
-
-
-
-
-
-
-## Setting up deep links
+## 10. Setting up deep links (optional)
If you want to implement authentication features like magic links or OAuth, you need to set up deep links to redirect users back to your app. For instructions on configuring custom URL schemes for your iOS app, see the [deep linking guide](/docs/guides/auth/native-mobile-deep-linking?platform=swift).
diff --git a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx
index bf300112b3f..6c9b2d1fa71 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx
@@ -2,189 +2,155 @@
title: 'Use Supabase with Android Kotlin'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create an Android app with Android Studio
- <$Partial path="quickstart_db_setup.mdx" />
+Select the **Android Studio > New > New Android Project** menu item.
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
-
- Open Android Studio > New > New Android Project.
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install dependencies
-
+Open `build.gradle.kts` (app) file and add the serialization plugin, Ktor client, and Supabase client.
-
- Open `build.gradle.kts` (app) file and add the serialization plug, Ktor client, and Supabase client.
+Replace the version placeholders `$kotlin_version` with the Kotlin version of the project, and `$supabase_version` and `$ktor_version` with the respective latest versions.
- Replace the version placeholders `$kotlin_version` with the Kotlin version of the project, and `$supabase_version` and `$ktor_version` with the respective latest versions.
+
- The latest supabase-kt version can be found [here](https://github.com/supabase-community/supabase-kt/releases) and Ktor version can be found [here](https://ktor.io/docs/welcome.html).
+You can find the latest supabase-kt version [on GitHub](https://github.com/supabase-community/supabase-kt/releases) and Ktor [in the Ktor documentation](https://ktor.io/docs/welcome.html).
-
+
-
- ```kotlin
- plugins {
- ...
- kotlin("plugin.serialization") version "$kotlin_version"
- }
- ...
- dependencies {
- ...
- implementation(platform("io.github.jan-tennert.supabase:bom:$supabase_version"))
- implementation("io.github.jan-tennert.supabase:postgrest-kt")
- implementation("io.ktor:ktor-client-android:$ktor_version")
- }
- ```
+```kotlin
+plugins {
+ ...
+ kotlin("plugin.serialization") version "$kotlin_version"
+}
+...
+dependencies {
+ ...
+ implementation(platform("io.github.jan-tennert.supabase:bom:$supabase_version"))
+ implementation("io.github.jan-tennert.supabase:postgrest-kt")
+ implementation("io.ktor:ktor-client-android:$ktor_version")
+}
+```
-
+## 6. Add internet access permission
-
+Add the following line to the `AndroidManifest.xml` file under the `manifest` tag and outside the `application` tag.
-
+```xml
+...
+
+...
+```
-
- Add the following line to the `AndroidManifest.xml` file under the `manifest` tag and outside the `application` tag.
-
+## 7. Initialize the Supabase client
-
- ```xml
- ...
-
- ...
- ```
+You can create a Supabase client whenever you need to perform an API call.
-
+For a quick example, create a client at the top of the `MainActivity.kt` file below the imports.
-
+Replace the `supabaseUrl` and `supabaseKey` with your own, which you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=androidkotlin&tab=mobiles):
-
+
+
+ Open Connect panel
+
+
-
- You can create a Supabase client whenever you need to perform an API call.
+```kotlin
+import ...
- For the sake of simplicity, we will create a client in the `MainActivity.kt` file at the top just below the imports.
+val supabase = createSupabaseClient(
+ supabaseUrl = "https://xyzcompany.supabase.co",
+ supabaseKey = "your_publishable_key"
+ ) {
+ install(Postgrest)
+}
+...
+```
- Replace the `supabaseUrl` and `supabaseKey` with your own:
+<$Partial path="api_settings.mdx" variables={{ "framework": "androidkotlin", "tab": "mobiles" }} />
-
-
+## 8. Create a data model for instruments
+Create a serializable data class to represent the data from the database.
-
+Add the following below the `createSupabaseClient` function in the `MainActivity.kt` file.
-
+```kotlin
+@Serializable
+data class Instrument(
+ val id: Int,
+ val name: String,
+)
+```
- ```kotlin
- import ...
+## 9. Query data from the app
- val supabase = createSupabaseClient(
- supabaseUrl = "https://xyzcompany.supabase.co",
- supabaseKey = "your_publishable_key"
- ) {
- install(Postgrest)
- }
- ...
- ```
+Use `LaunchedEffect` to fetch data from the database and display it in a `LazyColumn`.
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "androidkotlin", "tab": "mobiles" }} />
+Replace the default `MainActivity` class with the following code.
-
+
-
+This example application makes a network request from the UI code. In production, you should use a `ViewModel` to separate the UI and data fetching logic.
-
+
-
- Create a serializable data class to represent the data from the database.
+```kotlin
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ SupabaseTutorialTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background
+ ) {
+ InstrumentsList()
+ }
+ }
+ }
+ }
+}
- Add the following below the `createSupabaseClient` function in the `MainActivity.kt` file.
-
+@Composable
+fun InstrumentsList() {
+ var instruments by remember { mutableStateOf>(listOf()) }
+ LaunchedEffect(Unit) {
+ withContext(Dispatchers.IO) {
+ instruments = supabase.from("instruments")
+ .select().decodeList()
+ }
+ }
+ LazyColumn {
+ items(
+ instruments,
+ key = { instrument -> instrument.id },
+ ) { instrument ->
+ Text(
+ instrument.name,
+ modifier = Modifier.padding(8.dp),
+ )
+ }
+ }
+}
+```
-
- ```kotlin
- @Serializable
- data class Instrument(
- val id: Int,
- val name: String,
- )
- ```
+## 10. Start the app
-
-
-
-
-
-
-
- Use `LaunchedEffect` to fetch data from the database and display it in a `LazyColumn`.
-
- Replace the default `MainActivity` class with the following code.
-
- Note that we are making a network request from our UI code. In production, you should probably use a `ViewModel` to separate the UI and data fetching logic.
-
-
-
- ```kotlin
- class MainActivity : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- SupabaseTutorialTheme {
- // A surface container using the 'background' color from the theme
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- InstrumentsList()
- }
- }
- }
- }
- }
-
- @Composable
- fun InstrumentsList() {
- var instruments by remember { mutableStateOf>(listOf()) }
- LaunchedEffect(Unit) {
- withContext(Dispatchers.IO) {
- instruments = supabase.from("instruments")
- .select().decodeList()
- }
- }
- LazyColumn {
- items(
- instruments,
- key = { instrument -> instrument.id },
- ) { instrument ->
- Text(
- instrument.name,
- modifier = Modifier.padding(8.dp),
- )
- }
- }
- }
- ```
-
-
-
-
-
-
- Run the app on an emulator or a physical device by clicking the `Run app` button in Android Studio.
-
-
-
-
-
+Run the app on an emulator or a physical device by clicking the `Run app` button in Android Studio.
diff --git a/apps/docs/content/guides/getting-started/quickstarts/laravel.mdx b/apps/docs/content/guides/getting-started/quickstarts/laravel.mdx
index 6e7744279bf..6c9e55acc60 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/laravel.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/laravel.mdx
@@ -2,144 +2,97 @@
title: 'Use Supabase with Laravel'
subtitle: 'Learn how to create a PHP Laravel project, connect it to your Supabase Postgres database, and configure user authentication.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
-
+## 3. Create a Laravel project
- Make sure your PHP and Composer versions are up to date, then use `composer create-project` to scaffold a new Laravel project.
+Make sure your PHP and Composer versions are up to date, then use `composer create-project` to scaffold a new Laravel project.
- See the [Laravel docs](https://laravel.com/docs/10.x/installation#creating-a-laravel-project) for more details.
+See the [Laravel docs](https://laravel.com/docs/10.x/installation#creating-a-laravel-project) for more details.
-
+```bash
+composer create-project laravel/laravel example-app
+```
-
+## 4. Install Agent Skills (optional)
- ```bash name=Terminal
- composer create-project laravel/laravel example-app
- ```
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
-
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
-
-
+## 5. Install the authentication template
- Install [Laravel Breeze](https://laravel.com/docs/10.x/starter-kits#laravel-breeze), a basic implementation of all of Laravel's [authentication features](https://laravel.com/docs/10.x/authentication).
+Install [Laravel Breeze](https://laravel.com/docs/10.x/starter-kits#laravel-breeze), a basic implementation of all of Laravel's [authentication features](https://laravel.com/docs/10.x/authentication).
-
+```bash
+composer require laravel/breeze --dev
+php artisan breeze:install
+```
-
+## 6. Set up the Postgres connection details
- ```bash name=Terminal
- composer require laravel/breeze --dev
- php artisan breeze:install
- ```
+Go to [database.new](https://database.new) and create a new Supabase project. Save your database password securely.
-
+When your project is up and running, navigate to your project dashboard and click on [Connect](/dashboard/project/_?showConnect=true&method=session).
-
+Look for the Session Pooler connection string and copy the string. You will need to replace the Password with your saved database password. You can reset your database password in your [Database Settings](/dashboard/project/_/database/settings) if you do not have it.
-
-
+
- Go to [database.new](https://database.new) and create a new Supabase project. Save your database password securely.
+If you're in an [IPv6 environment](https://github.com/orgs/supabase/discussions/27034) or have the IPv4 Add-On, you can use the direct connection string instead of Supavisor in Session mode.
- When your project is up and running, navigate to your project dashboard and click on [Connect](/dashboard/project/_?showConnect=true&method=session).
+
- Look for the Session Pooler connection string and copy the string. You will need to replace the Password with your saved database password. You can reset your database password in your [Database Settings](/dashboard/project/_/database/settings) if you do not have it.
+```bash name=.env
+DB_CONNECTION=pgsql
+DB_URL=postgres://postgres.xxxx:password@xxxx.pooler.supabase.com:5432/postgres
+```
-
+## 7. Change the default schema
- If you're in an [IPv6 environment](https://github.com/orgs/supabase/discussions/27034) or have the IPv4 Add-On, you can use the direct connection string instead of Supavisor in Session mode.
+By default Laravel uses the `public` schema. We recommend changing this as Supabase exposes the `public` schema as a [data API](/docs/guides/api).
-
+You can change the schema of your Laravel application by modifying the `search_path` variable `app/config/database.php`.
-
+The schema you specify in `search_path` has to exist on Supabase. You can create a new schema from the [Table Editor](/dashboard/project/_/editor).
-
+```php name=app/config/database.php
+'pgsql' => [
+ 'driver' => 'pgsql',
+ 'url' => env('DB_URL'),
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '5432'),
+ 'database' => env('DB_DATABASE', 'laravel'),
+ 'username' => env('DB_USERNAME', 'root'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => env('DB_CHARSET', 'utf8'),
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'search_path' => 'laravel',
+ 'sslmode' => 'prefer',
+],
+```
- ```bash name=.env
- DB_CONNECTION=pgsql
- DB_URL=postgres://postgres.xxxx:password@xxxx.pooler.supabase.com:5432/postgres
- ```
+## 8. Run the database migrations
-
+Laravel ships with database migration files that set up the required tables for Laravel Authentication and User Management.
-
+Note: Laravel does not use Supabase Auth but rather implements its own authentication system!
-
-
+```bash
+php artisan migrate
+```
- By default Laravel uses the `public` schema. We recommend changing this as Supabase exposes the `public` schema as a [data API](/docs/guides/api).
+## 9. Start the app
- You can change the schema of your Laravel application by modifying the `search_path` variable `app/config/database.php`.
+Run the development server. Go to http://127.0.0.1:8000 in a browser to see your application. You can also navigate to http://127.0.0.1:8000/register and http://127.0.0.1:8000/login to register and log in users.
- The schema you specify in `search_path` has to exist on Supabase. You can create a new schema from the [Table Editor](/dashboard/project/_/editor).
-
-
-
-
-
- ```php name=app/config/database.php
- 'pgsql' => [
- 'driver' => 'pgsql',
- 'url' => env('DB_URL'),
- 'host' => env('DB_HOST', '127.0.0.1'),
- 'port' => env('DB_PORT', '5432'),
- 'database' => env('DB_DATABASE', 'laravel'),
- 'username' => env('DB_USERNAME', 'root'),
- 'password' => env('DB_PASSWORD', ''),
- 'charset' => env('DB_CHARSET', 'utf8'),
- 'prefix' => '',
- 'prefix_indexes' => true,
- 'search_path' => 'laravel',
- 'sslmode' => 'prefer',
- ],
- ```
-
-
-
-
-
-
-
-
- Laravel ships with database migration files that set up the required tables for Laravel Authentication and User Management.
-
- Note: Laravel does not use Supabase Auth but rather implements its own authentication system!
-
-
-
-
-
- ```bash name=Terminal
- php artisan migrate
- ```
-
-
-
-
-
-
-
-
- Run the development server. Go to http://127.0.0.1:8000 in a browser to see your application. You can also navigate to http://127.0.0.1:8000/register and http://127.0.0.1:8000/login to register and log in users.
-
-
-
-
-
- ```bash name=Terminal
- php artisan serve
- ```
-
-
-
-
-
-
+```bash
+php artisan serve
+```
diff --git a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx
index fab3593778d..654823cecf8 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx
@@ -2,128 +2,86 @@
title: 'Use Supabase with Next.js'
subtitle: 'Learn how to create a Supabase project, add some sample data, and query from a Next.js app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a Next.js app
- <$Partial path="quickstart_db_setup.mdx" />
+Use the `create-next-app` command and the `with-supabase` template, to create a Next.js app pre-configured with [Cookie-based Auth](/docs/guides/auth/server-side/creating-a-client?queryGroups=package-manager&package-manager=npm&queryGroups=framework&framework=nextjs&queryGroups=environment&environment=server), [TypeScript](https://www.typescriptlang.org/), and [Tailwind CSS](https://tailwindcss.com/).
-
+```bash
+npx create-next-app -e with-supabase
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- Use the `create-next-app` command and the `with-supabase` template, to create a Next.js app pre-configured with:
- - [Cookie-based Auth](/docs/guides/auth/server-side/creating-a-client?queryGroups=package-manager&package-manager=npm&queryGroups=framework&framework=nextjs&queryGroups=environment&environment=server)
- - [TypeScript](https://www.typescriptlang.org/)
- - [Tailwind CSS](https://tailwindcss.com/)
+To install, run the following command in the root of your project:
- <$Partial path="uiLibCta.mdx" />
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Declare Supabase environment variables
-
+Rename `.env.example` to `.env.local` and populate with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=nextjs&tab=frameworks).
- ```bash
- npx create-next-app -e with-supabase
- ```
+
+
+ Open Connect panel
+
+
-
+```text name=.env.local
+NEXT_PUBLIC_SUPABASE_URL=
+NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} />
-
-
+## 6. Query Supabase data from Next.js
- Rename `.env.example` to `.env.local` and populate with your Supabase connection variables:
+Create a new file at `app/instruments/page.tsx` and populate with the following.
-
-
+This selects all the rows from the `instruments` table you created earlier and renders them on the page.
+<$CodeTabs>
-
+```ts name=app/instruments/page.tsx
+import { createClient } from "@/lib/supabase/server";
+import { Suspense } from "react";
-
+async function InstrumentsData() {
+ const supabase = await createClient();
+ const { data: instruments } = await supabase.from("instruments").select();
- <$CodeTabs>
+ return {JSON.stringify(instruments, null, 2)} ;
+}
- ```text name=.env.local
- NEXT_PUBLIC_SUPABASE_URL=
- NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
- ```
+export default function Instruments() {
+ return (
+ Loading instruments... }>
+
+
+ );
+}
+```
- $CodeTabs>
+$CodeTabs>
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "nextjs", "tab": "frameworks" }} />
+## 7. Start the app
-
+Run the development server, go to http://localhost:3000/instruments in a browser and you should see the list of instruments.
-
-
-
-
-
- Create a new file at `app/instruments/page.tsx` and populate with the following.
-
- This selects all the rows from the `instruments` table in Supabase and render them on the page.
-
-
-
-
-
- <$CodeTabs>
-
- ```ts name=app/instruments/page.tsx
- import { createClient } from "@/lib/supabase/server";
- import { Suspense } from "react";
-
- async function InstrumentsData() {
- const supabase = await createClient();
- const { data: instruments } = await supabase.from("instruments").select();
-
- return {JSON.stringify(instruments, null, 2)} ;
- }
-
- export default function Instruments() {
- return (
- Loading instruments...}>
-
-
- );
- }
- ```
-
- $CodeTabs>
-
-
-
-
-
-
-
-
- Run the development server, go to http://localhost:3000/instruments in a browser and you should see the list of instruments.
-
-
-
-
-
- ```bash Terminal
- npm run dev
- ```
-
-
-
-
-
-
+```bash
+npm run dev
+```
## Next steps
+- Explore [drop-in UI components](/ui) for your Supabase app
- Set up [Auth](/docs/guides/auth) for your app
- [Insert more data](/docs/guides/database/import-data) into your database
- Upload and serve static files using [Storage](/docs/guides/storage)
diff --git a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx
index cb1ce765b79..c91850e0df3 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx
@@ -2,148 +2,106 @@
title: 'Use Supabase with Nuxt'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Nuxt app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a Nuxt app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a Nuxt app using the `npx nuxi` command.
-
+```bash
+npx nuxi@latest init my-app
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- - Create a Nuxt app using the `npx nuxi` command.
+To install, run the following command in the root of your project:
- <$Partial path="uiLibCta.mdx" />
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
-
+The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a Nuxt app.
- ```bash name=Terminal
- npx nuxi@latest init my-app
- ```
+Navigate to the Nuxt app and install `supabase-js`.
-
+```bash
+cd my-app && npm install @supabase/supabase-js
+```
-
+## 6. Declare Supabase environment variables
-
-
+Create a `.env` file and populate with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=nuxt&tab=frameworks):
- The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a Nuxt app.
+
+
+ Open Connect panel
+
+
- Navigate to the Nuxt app and install `supabase-js`.
+<$CodeTabs>
-
+```text name=.env
+SUPABASE_URL=
+SUPABASE_PUBLISHABLE_KEY=
+```
-
+```ts name=nuxt.config.ts
+export default defineNuxtConfig({
+ runtimeConfig: {
+ public: {
+ supabaseUrl: process.env.SUPABASE_URL,
+ supabasePublishableKey: process.env.SUPABASE_PUBLISHABLE_KEY,
+ },
+ },
+})
+```
- ```bash name=Terminal
- cd my-app && npm install @supabase/supabase-js
- ```
+$CodeTabs>
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "nuxt", "tab": "frameworks" }} />
-
+## 7. Query data from the app
-
-
+In `app.vue`, create a Supabase client using your config values and replace the existing content with the following code.
- Create a `.env` file and populate with your Supabase connection variables:
+```vue name=app.vue
+
-
+
+
+ {{ instrument.name }}
+
+
+```
- <$CodeTabs>
+## 8. Start the app
- ```text name=.env.local
- SUPABASE_URL=
- SUPABASE_PUBLISHABLE_KEY=
- ```
+Start the app, navigate to http://localhost:3000 in the browser, and you should see the list of instruments.
- ```ts name=nuxt.config.tsx
- export default defineNuxtConfig({
- runtimeConfig: {
- public: {
- supabaseUrl: process.env.SUPABASE_URL,
- supabasePublishableKey: process.env.SUPABASE_PUBLISHABLE_KEY,
- },
- },
- });
- ```
-
- $CodeTabs>
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "nuxt", "tab": "frameworks" }} />
-
-
-
-
-
-
-
-
- In `app.vue`, create a Supabase client using your config values and replace the existing content with the following code.
-
-
-
-
-
- ```vue name=app.vue
-
-
-
-
- {{ instrument.name }}
-
-
- ```
-
-
-
-
-
-
-
-
- Start the app, navigate to http://localhost:3000 in the browser, open the browser console, and you should see the list of instruments.
-
-
-
-
-
- ```bash name=Terminal
- npm run dev
- ```
-
-
-
-
-
+```bash
+npm run dev
+```
diff --git a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx
index 66d9743c162..515e6267c68 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx
@@ -2,152 +2,109 @@
title: 'Use Supabase with React'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a React app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a React app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a React app using a [Vite](https://vitejs.dev/guide/) template.
-
+```bash
+npm create vite@latest my-app -- --template react
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- - Create a React app using a [Vite](https://vitejs.dev/guide/) template.
+To install, run the following command in the root of your project:
- <$Partial path="uiLibCta.mdx" />
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
-
+The fastest way to get started is to use the `supabase-js` client library, which provides a convenient interface for working with Supabase from a React app.
- ```bash name=Terminal
- npm create vite@latest my-app -- --template react
- ```
+Navigate to the React app and install `supabase-js`.
-
+```bash
+cd my-app && npm install @supabase/supabase-js
+```
-
+## 6. Declare Supabase environment variables
-
-
+Create a `.env.local` file and populate it with your Supabase URL and publishable key that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=react&tab=frameworks)
- The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a React app.
+
+
+ Open Connect panel
+
+
- Navigate to the React app and install `supabase-js`.
+```text name=.env.local
+VITE_SUPABASE_URL=
+VITE_SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "react", "tab": "frameworks" }} />
-
+## 7. Query data from the app
- ```bash name=Terminal
- cd my-app && npm install @supabase/supabase-js
- ```
+Replace the contents of `App.jsx` with a `getInstruments` function that fetches the data and displays the query result on the page using a Supabase client.
-
+```js name=src/App.jsx
+import { createClient } from '@supabase/supabase-js'
+import { useEffect, useState } from 'react'
-
+const supabase = createClient(
+ import.meta.env.VITE_SUPABASE_URL,
+ import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
+)
-
-
+function App() {
+ const [instruments, setInstruments] = useState([])
- Create a `.env.local` file and populate with your Supabase connection variables:
+ useEffect(() => {
+ getInstruments()
+ }, [])
-
-
+ async function getInstruments() {
+ const { data, error } = await supabase.from('instruments').select()
+ if (error) {
+ console.error(error)
+ return
+ }
-
+ setInstruments(data)
+ }
-
+ return (
+
+ {instruments.map((instrument) => (
+ {instrument.name}
+ ))}
+
+ )
+}
- <$CodeTabs>
+export default App
+```
- ```text name=.env.local
- VITE_SUPABASE_URL=
- VITE_SUPABASE_PUBLISHABLE_KEY=
- ```
+## 8. Start the app
- $CodeTabs>
+Run the development server, go to http://localhost:5173 in a browser, and you should see the list of instruments.
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "react", "tab": "frameworks" }} />
-
-
-
-
-
-
-
-
- Replace the contents of `App.jsx` to add a `getInstruments` function to fetch the data and display the query result to the page using a Supabase client.
-
-
-
-
- ```js name=src/App.jsx
- import { useEffect, useState } from "react";
- import { createClient } from "@supabase/supabase-js";
-
- const supabase = createClient(import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY);
-
- function App() {
- const [instruments, setInstruments] = useState([]);
-
- useEffect(() => {
- getInstruments();
- }, []);
-
- async function getInstruments() {
- const { data, error } = await supabase.from("instruments").select();
-
- if (error) {
- console.error(error);
- return;
- }
-
- setInstruments(data);
- }
-
- return (
-
- {instruments.map((instrument) => (
- {instrument.name}
- ))}
-
- );
- }
-
- export default App;
- ```
-
-
-
-
-
-
-
-
- Run the development server, go to http://localhost:5173 in a browser and you should see the list of instruments.
-
-
-
-
-
- ```bash name=Terminal
- npm run dev
- ```
-
-
-
-
-
+```bash
+npm run dev
+```
## Next steps
+- Explore [drop-in UI components](/ui) for your Supabase app
- Set up [Auth](/docs/guides/auth) for your app
- [Insert more data](/docs/guides/database/import-data) into your database
- Upload and serve static files using [Storage](/docs/guides/storage)
diff --git a/apps/docs/content/guides/getting-started/quickstarts/redwoodjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/redwoodjs.mdx
index 661521fefbc..4bd411ef331 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/redwoodjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/redwoodjs.mdx
@@ -2,230 +2,168 @@
title: 'Use Supabase with RedwoodJS'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database using Prisma migration and seeds, and query the data from a RedwoodJS app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+## 1. Setup your new Supabase project
-
-
- [Create a new project](/dashboard) in the Supabase Dashboard.
+[Create a new project](/dashboard) in the Supabase Dashboard.
-
+
- Be sure to make note of the Database Password you used as you will need this later to connect to your database.
+Be sure to make note of the Database Password you used as you will need this later to connect to your database.
-
-
+
-
- 
-
+
-
+## 2. Gather database connection strings
-
-
+Open the project [**Connect** panel](/dashboard/project/_?showConnect=true). This quickstart connects using the [**Transaction pooler**](/dashboard/project/_?showConnect=true&method=transaction) and [**Session pooler**](/dashboard/project/_?showConnect=true&method=session) mode. Transaction mode is used for application queries and Session mode is used for running migrations with Prisma.
- Open the project [**Connect** panel](/dashboard/project/_?showConnect=true). This quickstart connects using the [**Transaction pooler**](/dashboard/project/_?showConnect=true&method=transaction) and [**Session pooler**](/dashboard/project/_?showConnect=true&method=session) mode. Transaction mode is used for application queries and Session mode is used for running migrations with Prisma.
+To do this, set the connection mode to `Transaction` in the [Database Settings page](/dashboard/project/_/database/settings) and copy the connection string and append `?pgbouncer=true&connection_limit=1`. `pgbouncer=true` disables Prisma from generating prepared statements. This is required since our connection pooler does not support prepared statements in transaction mode yet. The `connection_limit=1` parameter is only required if you are using Prisma from a serverless environment. This is the Transaction mode connection string.
- To do this, set the connection mode to `Transaction` in the [Database Settings page](/dashboard/project/_/database/settings) and copy the connection string and append `?pgbouncer=true&&connection_limit=1`. `pgbouncer=true` disables Prisma from generating prepared statements. This is required since our connection pooler does not support prepared statements in transaction mode yet. The `connection_limit=1` parameter is only required if you are using Prisma from a serverless environment. This is the Transaction mode connection string.
+To get the Session mode connection pooler string, change the port of the connection string from the dashboard to 5432.
- To get the Session mode connection pooler string, change the port of the connection string from the dashboard to 5432.
+You will need the Transaction mode connection string and the Session mode connection string to set up environment variables in Step 6.
- You will need the Transaction mode connection string and the Session mode connection string to setup environment variables in Step 5.
+
-
+You can copy and paste these connection strings from the Supabase Dashboard when needed in later steps.
- You can copy and paste these connection strings from the Supabase Dashboard when needed in later steps.
+
-
-
+
-
- 
-
+## 3. Create a RedwoodJS app
-
+Create a RedwoodJS app with TypeScript.
-
-
- Create a RedwoodJS app with TypeScript.
+
-
+The [`yarn` package manager](https://yarnpkg.com) is required to create a RedwoodJS app. You will use it to run RedwoodJS commands later.
- The [`yarn` package manager](https://yarnpkg.com) is required to create a RedwoodJS app. You will use it to run RedwoodJS commands later.
+While TypeScript is recommended, If you want a JavaScript app, omit the `--ts` flag.
- While TypeScript is recommended, If you want a JavaScript app, omit the `--ts` flag.
+
-
-
+```bash
+yarn create redwood-app my-app --ts
+```
-
- ```bash name=Terminal
- yarn create redwood-app my-app --ts
- ```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
-
-
- You'll develop your app, manage database migrations, and run your app in VS Code.
-
+To install, run the following command in the root of your project:
-
- ```bash name=Terminal
- cd my-app
- code .
- ```
-
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install MCP server (optional)
-
-
+The Supabase MCP server connects AI assistants to Supabase, allowing you to interact with your projects on your behalf. Find out more on how to add it to your client in [the MCP docs](/docs/guides/ai-tools/mcp).
- In your `.env` file, add the following environment variables for your database connection:
+## 6. Configure environment variables
- * The `DATABASE_URL` should use the Transaction mode connection string you copied in Step 1.
+In your `.env` file, add the following environment variables for your database connection:
- * The `DIRECT_URL` should use the Session mode connection string you copied in Step 1.
+- The `DATABASE_URL` should use the Transaction mode connection string you copied in Step 2.
-
+- The `DIRECT_URL` should use the Session mode connection string you copied in Step 2.
-
- ```bash name=.env
- # Transaction mode connection string used for migrations
- DATABASE_URL="postgres://postgres.[project-ref]:[db-password]@xxx.pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1"
+```bash name=.env
+# Transaction mode connection string used for migrations
+DATABASE_URL="postgres://postgres.[project-ref]:[db-password]@xxx.pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1"
- # Session mode connection string — used by Prisma Client
- DIRECT_URL="postgres://postgres.[project-ref]:[db-password]@xxx.pooler.supabase.com:5432/postgres"
- ```
-
+# Session mode connection string — used by Prisma Client
+DIRECT_URL="postgres://postgres.[project-ref]:[db-password]@xxx.pooler.supabase.com:5432/postgres"
+```
-
+## 7. Update your Prisma schema
-
-
- By default, RedwoodJS ships with a SQLite database, but we want to use Postgres.
+By default, RedwoodJS ships with a SQLite database, but we want to use Postgres.
- Update your Prisma schema file `api/db/schema.prisma` to use your Supabase Postgres database connection environment variables you setup in Step 5.
-
+Update your Prisma schema file `api/db/schema.prisma` to use your Supabase Postgres database connection environment variables you set up in Step 6.
-
- ```prisma name=api/db/schema.prisma
- datasource db {
- provider = "postgresql"
- url = env("DATABASE_URL")
- directUrl = env("DIRECT_URL")
- }
- ```
-
+```prisma name=api/db/schema.prisma
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+ directUrl = env("DIRECT_URL")
+}
+```
-
+## 8. Create the instrument model and apply a schema migration
-
-
- Create the Instrument model in `api/db/schema.prisma` and then run `yarn rw prisma migrate dev` from your terminal to apply the migration.
-
+Create the Instrument model in `api/db/schema.prisma` and then run `yarn rw prisma migrate dev` from your terminal to apply the migration.
-
- ```prisma name=api/db/schema.prisma
- model Instrument {
- id Int @id @default(autoincrement())
- name String @unique
- }
- ```
-
+```prisma name=api/db/schema.prisma
+model Instrument {
+ id Int @id @default(autoincrement())
+ name String @unique
+}
+```
-
+## 9. Update seed script
-
-
- Seed the database with a few instruments.
+Seed the database with a few instruments.
- Update the file `scripts/seed.ts` to contain the following code:
-
+Update the file `scripts/seed.ts` to contain the following code:
-
+```ts name=scripts/seed.ts
+import type { Prisma } from '@prisma/client'
+import { db } from 'api/src/lib/db'
- ```ts name=scripts/seed.ts
- import type { Prisma } from '@prisma/client'
- import { db } from 'api/src/lib/db'
+export default async () => {
+ try {
+ const data: Prisma.InstrumentCreateArgs['data'][] = [
+ { name: 'dulcimer' },
+ { name: 'harp' },
+ { name: 'guitar' },
+ ]
- export default async () => {
- try {
- const data: Prisma.InstrumentCreateArgs['data'][] = [
- { name: 'dulcimer' },
- { name: 'harp' },
- { name: 'guitar' },
- ]
+ console.log('Seeding instruments ...')
- console.log('Seeding instruments ...')
+ const instruments = await db.instrument.createMany({ data })
- const instruments = await db.instrument.createMany({ data })
+ console.log('Done.', instruments)
+ } catch (error) {
+ console.error(error)
+ }
+}
+```
- console.log('Done.', instruments)
- } catch (error) {
- console.error(error)
- }
- }
- ```
-
+## 10. Seed your database
-
+Run the seed database command to populate the `Instrument` table with the instruments you created.
-
-
- Run the seed database command to populate the `Instrument` table with the instruments you just created.
+
-
+The reset database command `yarn rw prisma db reset` recreates the tables and also runs the seed script.
- The reset database command `yarn rw prisma db reset` will recreate the tables and will also run the seed script.
+
-
-
+```bash
+yarn rw prisma db seed
+```
-
- ```bash name=Terminal
- yarn rw prisma db seed
- ```
-
+## 11. Scaffold the instrument UI
-
+Use RedwoodJS generators to scaffold a CRUD UI for the `Instrument` model.
-
-
- Now, we'll use RedwoodJS generators to scaffold a CRUD UI for the `Instrument` model.
-
+```bash
+yarn rw g scaffold instrument
+```
-
- ```bash name=Terminal
- yarn rw g scaffold instrument
- ```
-
+## 12. Start the app
-
+Start the app via `yarn rw dev`. A browser will open to the RedwoodJS Splash page.
-
-
- Start the app via `yarn rw dev`. A browser will open to the RedwoodJS Splash page.
-
+
-
- 
+## 13. View instruments UI
-
+Click on `/instruments` to visit http://localhost:8910/instruments where should see the list of instruments.
-
-
-
-
- Click on `/instruments` to visit http://localhost:8910/instruments where should see the list of instruments.
-
- You may now edit, delete, and add new books using the scaffolded UI.
-
-
-
-
+You may now edit, delete, and add new instruments using the scaffolded UI.
diff --git a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx
index 1706c86e717..5fe26f5057a 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx
@@ -2,224 +2,164 @@
title: 'Use Supabase with Refine'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Refine app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a Refine app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a [Refine](https://github.com/refinedev/refine) app using the [create refine-app](https://refine.dev/docs/getting-started/quickstart/).
-
+The `refine-supabase` preset adds `@refinedev/supabase` supplementary package that supports Supabase in a Refine app. `@refinedev/supabase` out-of-the-box includes the Supabase dependency: [supabase-js](https://github.com/supabase/supabase-js).
-
+```bash
+npm create refine-app@latest -- --preset refine-supabase my-app
+```
-
+
- Create a [Refine](https://github.com/refinedev/refine) app using the [create refine-app](https://refine.dev/docs/getting-started/quickstart/).
+## 4. Install Agent Skills (optional)
- The `refine-supabase` preset adds `@refinedev/supabase` supplementary package that supports Supabase in a Refine app. `@refinedev/supabase` out-of-the-box includes the Supabase dependency: [supabase-js](https://github.com/supabase/supabase-js).
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
-
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
- ```bash name=Terminal
- npm create refine-app@latest -- --preset refine-supabase my-app
- ```
+## 5. Update `supabaseClient` with environment variables
-
+Update the `supabaseClient` with the `SUPABASE_URL` and `SUPABASE_KEY` of your Supabase API, which you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=refine&tab=frameworks). The `supabaseClient` is used in auth provider and data provider methods that allow the Refine app to connect to your Supabase backend.
-
+
+
+ Open Connect panel
+
+
-
-
+```ts name=src/utility/supabaseClient.ts
+import { createClient } from '@refinedev/supabase'
- You will develop your app, connect to the Supabase backend and run the Refine app in VS Code.
+const SUPABASE_URL = ''
+const SUPABASE_KEY = ''
+
+export const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY, {
+ db: {
+ schema: 'public',
+ },
+ auth: {
+ persistSession: true,
+ },
+})
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "refine", "tab": "frameworks" }} />
-
+## 6. Add instruments resource and pages
- ```bash name=Terminal
- cd my-app
- code .
- ```
+Use the following code to automatically add resources and generate code for the pages to show the `instruments` data using Refine Inferencer.
-
+This defines pages for `list`, `create`, `show` and `edit` actions inside the `src/pages/instruments/` directory with a ` ` component.
-
+The ` ` component depends on `@refinedev/react-table` and `@refinedev/react-hook-form` packages. To avoid errors, you should install them as dependencies with `npm install @refinedev/react-table @refinedev/react-hook-form`.
-
-
+
- Start the app, go to http://localhost:5173 in a browser, and you should be greeted with the Refine Welcome page.
+The ` ` is a Refine Inferencer component that automatically generates necessary code for the `list`, `create`, `show` and `edit` pages.
-
+Read more on [how the Inferencer works is in the Refine docs](https://refine.dev/docs/packages/documentation/inferencer/).
-
+
- ```bash name=Terminal
- npm run dev
- ```
+```bash
+npm run refine create-resource instruments
+```
-
- 
-
+## 7. Add routes for instruments pages
-
+Add routes for the `list`, `create`, `show`, and `edit` pages.
-
+
-
-
+Remove the `index` route for the Welcome page presented with the ` ` component.
- You now have to update the `supabaseClient` with the `SUPABASE_URL` and `SUPABASE_KEY` of your Supabase API. The `supabaseClient` is used in auth provider and data provider methods that allow the Refine app to connect to your Supabase backend.
+
-
-
+```tsx name=src/App.tsx
+import { Refine } from '@refinedev/core'
+import { RefineKbar, RefineKbarProvider } from '@refinedev/kbar'
+import routerProvider, {
+ DocumentTitleHandler,
+ NavigateToResource,
+ UnsavedChangesNotifier,
+} from '@refinedev/react-router'
+import { dataProvider, liveProvider } from '@refinedev/supabase'
+import { BrowserRouter, Route, Routes } from 'react-router-dom'
+import './App.css'
-
+import authProvider from './authProvider'
+import {
+ InstrumentsCreate,
+ InstrumentsEdit,
+ InstrumentsList,
+ InstrumentsShow,
+} from './pages/instruments'
+import { supabaseClient } from './utility'
-
-
- ```ts name=src/utility/supabaseClient.ts
- import { createClient } from "@refinedev/supabase";
-
- const SUPABASE_URL = YOUR_SUPABASE_URL;
- const SUPABASE_KEY = YOUR_SUPABASE_KEY
-
- export const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY, {
- db: {
- schema: "public",
- },
- auth: {
- persistSession: true,
- },
- });
- ```
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "refine", "tab": "frameworks" }} />
-
-
-
-
-
-
-
-
- You have to then configure resources and define pages for `instruments` resource.
-
- Use the following command to automatically add resources and generate code for pages for `instruments` using Refine Inferencer.
-
- This defines pages for `list`, `create`, `show` and `edit` actions inside the `src/pages/instruments/` directory with ` ` component.
-
- The ` ` component depends on `@refinedev/react-table` and `@refinedev/react-hook-form` packages. In order to avoid errors, you should install them as dependencies with `npm install @refinedev/react-table @refinedev/react-hook-form`.
-
-
-
- The ` ` is a Refine Inferencer component that automatically generates necessary code for the `list`, `create`, `show` and `edit` pages.
-
- More on [how the Inferencer works is available in the docs here](https://refine.dev/docs/packages/documentation/inferencer/).
-
-
-
-
-
-
-
-
- ```bash name=Terminal
- npm run refine create-resource instruments
- ```
-
-
-
-
-
-
-
- Add routes for the `list`, `create`, `show`, and `edit` pages.
-
-
-
- You should remove the `index` route for the Welcome page presented with the ` ` component.
-
-
-
-
-
-
- ```tsx name=src/App.tsx
- import { Refine } from "@refinedev/core";
- import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
- import routerProvider, {
- DocumentTitleHandler,
- NavigateToResource,
- UnsavedChangesNotifier,
- } from "@refinedev/react-router";
- import { dataProvider, liveProvider } from "@refinedev/supabase";
- import { BrowserRouter, Route, Routes } from "react-router-dom";
-
- import "./App.css";
- import authProvider from "./authProvider";
- import { supabaseClient } from "./utility";
- import { InstrumentsCreate, InstrumentsEdit, InstrumentsList, InstrumentsShow } from "./pages/instruments";
-
- function App() {
- return (
-
-
-
-
- }
- />
-
- } />
- } />
- } />
- } />
-
-
-
-
-
-
-
-
- );
- }
-
- export default App;
- ```
-
-
-
-
-
-
-
- Now you should be able to see the instruments pages along the `/instruments` routes. You may now edit and add new instruments using the Inferencer generated UI.
-
- The Inferencer auto-generated code gives you a good starting point on which to keep building your `list`, `create`, `show` and `edit` pages. They can be obtained by clicking the `Show the auto-generated code` buttons in their respective pages.
-
-
-
-
+function App() {
+ return (
+
+
+
+
+ } />
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
+
+
+
+ )
+}
+
+export default App
+```
+
+## 8. View instruments pages
+
+Start the app with the following command:
+
+```bash
+npm run dev
+```
+
+Open http://localhost:5173/instruments in a browser, and you should be able to see the instruments pages along the `/instruments` routes. You can edit and add new instruments using the Inferencer generated UI.
+
+The Inferencer auto-generated code gives you a good starting point on which to keep building your `list`, `create`, `show` and `edit` pages. You can get these by clicking the `Show the auto-generated code` buttons in their respective pages.
diff --git a/apps/docs/content/guides/getting-started/quickstarts/ruby-on-rails.mdx b/apps/docs/content/guides/getting-started/quickstarts/ruby-on-rails.mdx
index bec7d960cfd..f1d6a8aef66 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/ruby-on-rails.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/ruby-on-rails.mdx
@@ -2,116 +2,80 @@
title: 'Use Supabase with Ruby on Rails'
subtitle: 'Learn how to create a Rails project and connect it to your Supabase Postgres database.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+## 1. Create a Rails project
-
-
+With your Ruby and Rails versions up to date, run `rails new` on your terminal to scaffold a new project.
- Make sure your Ruby and Rails versions are up to date, then use `rails new` to scaffold a new Rails project. Use the `-d=postgresql` flag to set it up for Postgres.
+Use the `-d=postgresql` flag to set it up for Postgres.
- Go to the [Rails docs](https://guides.rubyonrails.org/getting_started.html) for more details.
+Check the [Rails docs](https://guides.rubyonrails.org/getting_started.html) for more details.
-
+```bash
+rails new blog -d=postgresql
+```
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- ```bash name=Terminal
- rails new blog -d=postgresql
- ```
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 3. Install MCP server (optional)
-
-
+The Supabase MCP server connects AI assistants to Supabase, allowing you to interact with your projects on your behalf. Find out more on how to add it to your client in [the MCP docs](/docs/guides/ai-tools/mcp).
- Go to [database.new](https://database.new) and create a new Supabase project. Save your database password securely.
+## 4. Set up the Postgres connection details
- When your project is up and running, navigate to your project dashboard and click on [Connect](/dashboard/project/_?showConnect=true&method=session).
+Go to [database.new](https://database.new) and create a new Supabase project. Save your database password securely.
- Look for the Session Pooler connection string and copy the string. You will need to replace the Password with your saved database password. You can reset your database password in your [Database Settings](/dashboard/project/_/database/settings) if you do not have it.
+When your project is up and running, navigate to your project dashboard and click on [Connect](/dashboard/project/_?showConnect=true&method=session).
-
+Look for the Session Pooler connection string and copy the string. You will need to replace the Password with your saved database password. You can reset your database password in your [Database Settings](/dashboard/project/_/database/settings) if you do not have it.
- If you're in an [IPv6 environment](https://github.com/orgs/supabase/discussions/27034) or have the IPv4 Add-On, you can use the direct connection string instead of Supavisor in Session mode.
+
-
+If you're in an [IPv6 environment](https://github.com/orgs/supabase/discussions/27034) or have the IPv4 Add-On, you can use the direct connection string instead of Supavisor in Session mode.
-
+
-
+```bash name=.env
+export DATABASE_URL=postgres://postgres.xxxx:password@xxxx.pooler.supabase.com:5432/postgres
+```
- ```bash name=Terminal
- export DATABASE_URL=postgres://postgres.xxxx:password@xxxx.pooler.supabase.com:5432/postgres
- ```
+## 5. Create and run a database migration
-
+Rails includes Active Record as the ORM as well as database migration tooling which generates the SQL migration files for you.
-
+Create an example `Article` model and generate the migration files.
-
-
+```bash
+bin/rails generate model Article title:string body:text
+bin/rails db:migrate
+```
- Rails includes Active Record as the ORM as well as database migration tooling which generates the SQL migration files for you.
+## 6. Use the model to interact with the database
- Create an example `Article` model and generate the migration files.
+You can use the included Rails console to interact with the database. For example, you can create new entries or list all entries in a Model's table.
-
+```bash
+bin/rails console
+```
-
+```rb name=irb
+article = Article.new(title: "Hello Rails", body: "I am on Rails!")
+article.save # Saves the entry to the database
- ```bash name=Terminal
- bin/rails generate model Article title:string body:text
- bin/rails db:migrate
- ```
+Article.all
+```
-
+## 7. Start the app
-
+Run the development server. Go to http://127.0.0.1:3000 in a browser to see your application running.
-
-
-
- You can use the included Rails console to interact with the database. For example, you can create new entries or list all entries in a Model's table.
-
-
-
-
-
- ```bash name=Terminal
- bin/rails console
- ```
-
- ```rb name=irb
- article = Article.new(title: "Hello Rails", body: "I am on Rails!")
- article.save # Saves the entry to the database
-
- Article.all
- ```
-
-
-
-
-
-
-
-
- Run the development server. Go to http://127.0.0.1:3000 in a browser to see your application running.
-
-
-
-
-
- ```bash name=Terminal
- bin/rails server
- ```
-
-
-
-
-
-
+```bash
+bin/rails server
+```
diff --git a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx
index 1d5d01fbda5..6ac87800239 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx
@@ -2,135 +2,92 @@
title: 'Use Supabase with SolidJS'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SolidJS app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a SolidJS app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a SolidJS app using the `degit` command.
-
+```bash
+npx degit solidjs/templates/js my-app
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- Create a SolidJS app using the `degit` command.
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
- ```bash name=Terminal
- npx degit solidjs/templates/js my-app
- ```
+The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SolidJS app.
-
+Navigate to the SolidJS app and install `supabase-js`.
-
+```bash
+cd my-app && npm install @supabase/supabase-js
+```
-
-
+## 6. Declare Supabase environment variables
- The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SolidJS app.
+Create a `.env.local` file and populate with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=solidjs&tab=frameworks):
- Navigate to the SolidJS app and install `supabase-js`.
+
+
+ Open Connect panel
+
+
-
+```text name=.env.local
+VITE_SUPABASE_URL=
+VITE_SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "solidjs", "tab": "frameworks" }} />
- ```bash name=Terminal
- cd my-app && npm install @supabase/supabase-js
- ```
+## 7. Query data from the app
-
+In `App.jsx`, create a Supabase client to fetch the instruments data.
-
+Add a `getInstruments` function to fetch the data and display the query result to the page.
-
-
+```jsx name=src/App.jsx
+import { createClient } from '@supabase/supabase-js'
+import { createResource, For } from 'solid-js'
- Create a `.env.local` file and populate with your Supabase connection variables:
+const supabase = createClient(
+ import.meta.env.VITE_SUPABASE_URL,
+ import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
+)
-
-
+async function getInstruments() {
+ const { data } = await supabase.from('instruments').select()
+ return data
+}
+function App() {
+ const [instruments] = createResource(getInstruments)
-
+ return (
+
+ {(instrument) => {instrument.name} }
+
+ )
+}
-
+export default App
+```
- <$CodeTabs>
+## 8. Start the app
- ```text name=.env.local
- VITE_SUPABASE_URL=
- VITE_SUPABASE_PUBLISHABLE_KEY=
- ```
+Start the app and go to http://localhost:3000 in a browser and you should see the list of instruments.
- $CodeTabs>
-
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "solidjs", "tab": "frameworks" }} />
-
-
-
-
-
-
-
-
- In `App.jsx`, create a Supabase client to fetch the instruments data.
-
- Add a `getInstruments` function to fetch the data and display the query result to the page.
-
-
-
-
-
- ```jsx name=src/App.jsx
- import { createClient } from "@supabase/supabase-js";
- import { createResource, For } from "solid-js";
-
- const supabase = createClient('https://.supabase.co', '');
-
- async function getInstruments() {
- const { data } = await supabase.from("instruments").select();
- return data;
- }
-
- function App() {
- const [instruments] = createResource(getInstruments);
-
- return (
-
- {(instrument) => {instrument.name} }
-
- );
- }
-
- export default App;
- ```
-
-
-
-
-
-
-
-
- Start the app and go to http://localhost:3000 in a browser and you should see the list of instruments.
-
-
-
-
-
- ```bash name=Terminal
- npm run dev
- ```
-
-
-
-
-
+```bash
+npm run dev
+```
diff --git a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
index 30be6126edc..669079174bc 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
@@ -2,204 +2,143 @@
title: 'Use Supabase with SvelteKit'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SvelteKit app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a SvelteKit app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a SvelteKit app using the `npm create` command.
-
+```bash
+npx sv create my-app
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- Create a SvelteKit app using the `npm create` command.
+To install, run the following command in the root of your project:
-
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
- ```bash name=Terminal
- npx sv create my-app
- ```
+The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SvelteKit app.
-
+Navigate to the SvelteKit app and install `supabase-js`.
-
+```bash
+cd my-app && npm install @supabase/supabase-js
+```
-
-
+## 6. Declare Supabase environment variables
- The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SvelteKit app.
+Create a `.env` file at the root of your project and populate with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=sveltekit&tab=frameworks):
- Navigate to the SvelteKit app and install `supabase-js`.
+
+
+ Open Connect panel
+
+
-
+```text name=.env
+PUBLIC_SUPABASE_URL=
+PUBLIC_SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "sveltekit", "tab": "frameworks" }} />
- ```bash name=Terminal
- cd my-app && npm install @supabase/supabase-js
- ```
+## 7. Create the Supabase client
-
+Create a `src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client:
-
+<$CodeTabs>
-
+```js name=src/lib/supabaseClient.js
+import { createClient } from '@supabase/supabase-js'
+import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
-
+export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY)
+```
- Create a `.env` file at the root of your project and populate with your Supabase connection variables:
+```ts name=src/lib/supabaseClient.ts
+import { createClient } from '@supabase/supabase-js'
+import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
-
-
+export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY)
+```
+$CodeTabs>
-
+## 8. Query data from the app
-
+Use `load` method to fetch the data server-side and display the query results as a list.
- <$CodeTabs>
+Create `+page.server.js` file in the `src/routes` directory with the following code.
- ```text name=.env
- PUBLIC_SUPABASE_URL=
- PUBLIC_SUPABASE_PUBLISHABLE_KEY=
- ```
+<$CodeTabs>
- $CodeTabs>
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "sveltekit", "tab": "frameworks" }} />
+```js name=src/routes/+page.server.js
+import { supabase } from '$lib/supabaseClient'
-
+export async function load() {
+ const { data } = await supabase.from('instruments').select()
+ return {
+ instruments: data ?? [],
+ }
+}
+```
-
+```ts name=src/routes/+page.server.ts
+import { supabase } from '$lib/supabaseClient'
-
-
+import type { PageServerLoad } from './$types'
- Create a `src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client:
+type Instrument = {
+ id: number
+ name: string
+}
-
+export const load: PageServerLoad = async () => {
+ const { data, error } = await supabase.from('instruments').select<'instruments', Instrument>()
-
+ if (error) {
+ console.error('Error loading instruments:', error.message)
+ return { instruments: [] }
+ }
- <$CodeTabs>
+ return {
+ instruments: data ?? [],
+ }
+}
+```
- ```js name=src/lib/supabaseClient.js
- import { createClient } from '@supabase/supabase-js';
- import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
+$CodeTabs>
- export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY)
- ```
+Replace the existing content in your `+page.svelte` file in the `src/routes` directory with the following code.
- ```ts name=src/lib/supabaseClient.ts
- import { createClient } from '@supabase/supabase-js';
- import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
+```svelte name=src/routes/+page.svelte
+
- export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY)
- ```
+
+ {#each data.instruments as instrument}
+ {instrument.name}
+ {/each}
+
+```
- $CodeTabs>
+## 9. Start the app
-
+Start the app and go to http://localhost:5173 in a browser and you should see the list of instruments.
-
-
-
-
-
- Use `load` method to fetch the data server-side and display the query results as a list.
-
- Create `+page.server.js` file in the `src/routes` directory with the following code.
-
-
-
-
- <$CodeTabs>
-
- ```js name=src/routes/+page.server.js
- import { supabase } from "$lib/supabaseClient";
-
- export async function load() {
- const { data } = await supabase.from("instruments").select();
- return {
- instruments: data ?? [],
- };
- }
- ```
-
- ```ts name=src/routes/+page.server.ts
- import type { PageServerLoad } from './$types';
- import { supabase } from '$lib/supabaseClient';
-
- type Instrument = {
- id: number;
- name: string;
- };
-
- export const load: PageServerLoad = async () => {
- const { data, error } = await supabase.from('instruments').select<'instruments', Instrument>();
-
- if (error) {
- console.error('Error loading instruments:', error.message);
- return { instruments: [] };
- }
-
- return {
- instruments: data ?? [],
- };
- };
- ```
-
- $CodeTabs>
-
-
-
-
-
- Replace the existing content in your `+page.svelte` file in the `src/routes` directory with the following code.
-
-
-
-
-
- ```svelte name=src/routes/+page.svelte
-
-
-
- {#each data.instruments as instrument}
- {instrument.name}
- {/each}
-
- ```
-
-
-
-
-
-
-
-
- Start the app and go to http://localhost:5173 in a browser and you should see the list of instruments.
-
-
-
-
-
- ```bash name=Terminal
- npm run dev
- ```
-
-
-
-
-
+```bash
+npm run dev
+```
## Next steps
diff --git a/apps/docs/content/guides/getting-started/quickstarts/tanstack.mdx b/apps/docs/content/guides/getting-started/quickstarts/tanstack.mdx
index 569ac3591ae..b59f80d39ad 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/tanstack.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/tanstack.mdx
@@ -2,163 +2,107 @@
title: 'Use Supabase with TanStack Start'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a TanStack Start app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a TanStack Start app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a TanStack Start app using the official CLI.
-
+```bash
+npm create @tanstack/start@latest my-app -- --package-manager npm --toolchain biome
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- - Create a TanStack Start app using the official CLI.
+To install, run the following command in the root of your project:
- <$Partial path="uiLibCta.mdx" />
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
-
+The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a TanStack Start app.
- ```bash name=Terminal
- npm create @tanstack/start@latest my-app -- --package-manager npm --toolchain biome
- ```
+Navigate to the TanStack Start app and install `supabase-js`.
-
+```bash
+cd my-app && npm install @supabase/supabase-js
+```
-
+## 6. Declare Supabase environment variables
-
-
+Create a `.env` file in the root of your project and populate with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true):
- The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a TanStack Start app.
+
+ Open Connect panel
+
- Navigate to the TanStack Start app and install `supabase-js`.
+```text name=.env
+VITE_SUPABASE_URL=
+VITE_SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "", "tab": "" }} />
-
+## 7. Create a Supabase client utility
- ```bash name=Terminal
- cd my-app && npm install @supabase/supabase-js
- ```
+Create a new file at `src/utils/supabase.ts` to initialize the Supabase client.
-
+```ts name=src/utils/supabase.ts
+import { createClient } from '@supabase/supabase-js'
-
+export const supabase = createClient(
+ import.meta.env.VITE_SUPABASE_URL,
+ import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
+)
+```
-
-
+## 8. Query data from the app
- Create a `.env` file in the root of your project and populate with your Supabase connection variables:
+Replace the contents of `src/routes/index.tsx` with the following code to add a loader function that fetches the instruments data and displays it on the page.
-
-
+```tsx name=src/routes/index.tsx
+import { createFileRoute } from '@tanstack/react-router'
+import { supabase } from '../utils/supabase'
-
+export const Route = createFileRoute('/')({
+ loader: async () => {
+ const { data: instruments } = await supabase.from('instruments').select()
+ return { instruments }
+ },
+ component: Home,
+})
-
+function Home() {
+ const { instruments } = Route.useLoaderData()
- <$CodeTabs>
+ return (
+
+ {instruments?.map((instrument) => (
+ {instrument.name}
+ ))}
+
+ )
+}
+```
- ```text name=.env
- VITE_SUPABASE_URL=
- VITE_SUPABASE_PUBLISHABLE_KEY=
- ```
+## 9. Start the app
- $CodeTabs>
+Run the development server, go to http://localhost:3000 in a browser and you should see the list of instruments.
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "", "tab": "" }} />
-
-
-
-
-
-
-
-
- Create a new file at `src/utils/supabase.ts` to initialize the Supabase client.
-
-
-
-
-
- ```ts name=src/utils/supabase.ts
- import { createClient } from "@supabase/supabase-js";
-
- export const supabase = createClient(
- import.meta.env.VITE_SUPABASE_URL,
- import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
- );
- ```
-
-
-
-
-
-
-
-
- Replace the contents of `src/routes/index.tsx` with the following code to add a loader function that fetches the instruments data and displays it on the page.
-
-
-
-
- ```tsx name=src/routes/index.tsx
- import { createFileRoute } from '@tanstack/react-router'
- import { supabase } from '../utils/supabase'
-
- export const Route = createFileRoute('/')({
- loader: async () => {
- const { data: instruments } = await supabase.from('instruments').select()
- return { instruments }
- },
- component: Home,
- })
-
- function Home() {
- const { instruments } = Route.useLoaderData()
-
- return (
-
- {instruments?.map((instrument) => (
- {instrument.name}
- ))}
-
- )
- }
- ```
-
-
-
-
-
-
-
-
- Run the development server, go to http://localhost:3000 in a browser and you should see the list of instruments.
-
-
-
-
-
- ```bash name=Terminal
- npm run dev
- ```
-
-
-
-
-
+```bash
+npm run dev
+```
## Next steps
+- Explore [drop-in UI components](/ui) for your Supabase app
- Set up [Auth](/docs/guides/auth) for your app
- [Insert more data](/docs/guides/database/import-data) into your database
- Upload and serve static files using [Storage](/docs/guides/storage)
diff --git a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx
index 269233acf16..6e34ba1244e 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx
@@ -2,156 +2,101 @@
title: 'Use Supabase with Vue'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Vue app.'
breadcrumb: 'Framework Quickstarts'
-hideToc: true
---
-
+<$Partial path="quickstart_db_setup.mdx" />
-
+## 3. Create a Vue app
- <$Partial path="quickstart_db_setup.mdx" />
+Create a Vue app using the `npm init` command.
-
+```sh
+npm init vue@latest my-app
+```
-
+## 4. Install Agent Skills (optional)
-
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
- - Create a Vue app using the `npm init` command.
+To install, run the following command in the root of your project:
- <$Partial path="uiLibCta.mdx" />
+```bash
+npx skills add supabase/agent-skills
+```
-
+## 5. Install the Supabase client library
-
+The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a Vue app.
- ```sh name=Terminal
- npm init vue@latest my-app
- ```
+Navigate to the Vue app and install `supabase-js`.
-
+```bash
+cd my-app && npm install @supabase/supabase-js
+```
-
+## 6. Declare Supabase environment variables
-
-
+Create a `.env.local` file and populate with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&framework=vue&tab=frameworks):
- The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a Vue app.
+
+
+ Open Connect panel
+
+
- Navigate to the Vue app and install `supabase-js`.
+```text name=.env.local
+VITE_SUPABASE_URL=
+VITE_SUPABASE_PUBLISHABLE_KEY=
+```
-
+<$Partial path="api_settings.mdx" variables={{ "framework": "vue", "tab": "frameworks" }} />
-
+## 7. Create the Supabase client
- ```bash name=Terminal
- cd my-app && npm install @supabase/supabase-js
- ```
+Create a `/src/lib` directory in your Vue app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client:
-
+```js name=src/lib/supabaseClient.js
+import { createClient } from '@supabase/supabase-js'
-
+const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
+const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
-
-
+export const supabase = createClient(supabaseUrl, supabasePublishableKey)
+```
- Create a `.env.local` file and populate with your Supabase connection variables:
+## 8. Query data from the app
-
-
+Replace the existing content in your `App.vue` file with the following code.
+```vue name=src/App.vue
+
- $CodeTabs>
- <$Partial path="api_settings_steps.mdx" variables={{ "framework": "vue", "tab": "frameworks" }} />
+
+
+ {{ instrument.name }}
+
+
+```
-
+## 9. Start the app
-
+Start the app and go to http://localhost:5173 in a browser and you should see the list of instruments.
-
-
-
- Create a `/src/lib` directory in your Vue app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client:
-
-
-
-
-
- ```js name=src/lib/supabaseClient.js
- import { createClient } from '@supabase/supabase-js'
-
- const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
- const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
-
- export const supabase = createClient(supabaseUrl, supabasePublishableKey)
- ```
-
-
-
-
-
-
-
-
- Replace the existing content in your `App.vue` file with the following code.
-
-
-
-
-
- ```vue name=src/App.vue
-
-
-
-
- {{ instrument.name }}
-
-
- ```
-
-
-
-
-
-
-
-
- Start the app and go to http://localhost:5173 in a browser and you should see the list of instruments.
-
-
-
-
-
- ```bash name=Terminal
- npm run dev
- ```
-
-
-
-
-
+```bash
+npm run dev
+```
diff --git a/apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx b/apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx
index 9f9d8b943f5..19ea149237c 100644
--- a/apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx
+++ b/apps/docs/content/guides/getting-started/tutorials/with-flutter.mdx
@@ -43,7 +43,7 @@ Run `flutter pub get` to install the dependencies.
With dependencies installed, set up deep links.
Setting up deep links is required to bring back the user to the app when they click on the magic link to sign in.
-We can setup deep links with just a minor tweak on our Flutter application.
+We can setup deep links with a minor tweak on our Flutter application.
We have to use `io.supabase.flutterquickstart` as the scheme. In this example, we will use `login-callback` as the host for our deep link, but you can change it to whatever you would like.
diff --git a/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx b/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx
index baff725b244..fcf2908a826 100644
--- a/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx
+++ b/apps/docs/content/guides/getting-started/tutorials/with-ionic-vue.mdx
@@ -78,6 +78,8 @@ meta="name=src/views/Account.vue"
With all the components in place, update `App.vue` and the app routes:
+<$Partial path="auth_methods.mdx" />
+
<$CodeSample
path="/user-management/ionic-vue-user-management/src/router/index.ts"
lines={[[1, -1]]}
diff --git a/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx
index f7fd26e7894..bc3467a8f54 100644
--- a/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx
+++ b/apps/docs/content/guides/getting-started/tutorials/with-nextjs.mdx
@@ -53,7 +53,7 @@ You can find the full contents of this file [in the example repository](https://
Next.js is a versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and proxy edge-functions.
-To better integrate with the framework, we've created the `@supabase/ssr` package for Server-Side Auth. It has all the functionalities to quickly configure your Supabase project to use cookies for storing user sessions. Read the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/creating-a-client?queryGroups=package-manager&package-manager=npm&queryGroups=framework&framework=nextjs) for more information.
+To better integrate with the framework, we've created the `@supabase/ssr` package for Server-Side Auth. It has all the functionalities to configure your Supabase project to use cookies for storing user sessions. Read the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/creating-a-client?queryGroups=package-manager&package-manager=npm&queryGroups=framework&framework=nextjs) for more information.
Install the package for Next.js.
@@ -216,7 +216,7 @@ lines={[[1, 4], [7, 78], [88, 89], [99, -1]]}
meta="name=app/account/account-form.tsx"
/>
-Create an account page for the `AccountForm` component you just created
+Create an account page for the `AccountForm` component you created
<$CodeSample
path="/user-management/nextjs-user-management/app/account/page.tsx"
diff --git a/apps/docs/content/guides/getting-started/tutorials/with-redwoodjs.mdx b/apps/docs/content/guides/getting-started/tutorials/with-redwoodjs.mdx
index 7ff85870f22..cb0774b4cd2 100644
--- a/apps/docs/content/guides/getting-started/tutorials/with-redwoodjs.mdx
+++ b/apps/docs/content/guides/getting-started/tutorials/with-redwoodjs.mdx
@@ -115,7 +115,7 @@ SUPABASE_JWT_SECRET=YOUR_SUPABASE_JWT_SECRET
$CodeTabs>
-And finally, you will also need to save **just** the `web side` environment variables to the `redwood.toml`.
+And finally, you will also need to save **only** the `web side` environment variables to the `redwood.toml`.
<$CodeTabs>
@@ -207,7 +207,7 @@ The `/` is important here as it creates a root level route.
-You can stop the `dev` server if you want; to see your changes, just be sure to run `yarn rw dev` again.
+You can stop the `dev` server if you want; to see your changes, run `yarn rw dev` again.
You should see the `Home` page route in `web/src/Routes.js`:
diff --git a/apps/docs/content/guides/integrations/vercel-marketplace.mdx b/apps/docs/content/guides/integrations/vercel-marketplace.mdx
index 35c554d1695..ac3c21bbf61 100644
--- a/apps/docs/content/guides/integrations/vercel-marketplace.mdx
+++ b/apps/docs/content/guides/integrations/vercel-marketplace.mdx
@@ -8,7 +8,7 @@ description: 'Manage your Supabase projects directly through Vercel'
The Vercel Marketplace is a feature that allows you to manage third-party resources, such as Supabase, directly from the Vercel platform. This integration offers a seamless experience with unified billing, streamlined authentication, and easy access management for your team.
-When you create an organization and projects through Vercel Marketplace, they function just like those created directly within Supabase. However, the billing is handled through your Vercel account, and you can manage your resources directly from the Vercel dashboard or CLI. Additionally, environment variables are automatically synchronized, making them immediately available for your connected projects.
+When you create an organization and projects through Vercel Marketplace, they function like those created directly within Supabase. However, the billing is handled through your Vercel account, and you can manage your resources directly from the Vercel dashboard or CLI. Additionally, environment variables are automatically synchronized, making them immediately available for your connected projects.
For more information, see [Introducing the Vercel Marketplace](https://vercel.com/blog/introducing-the-vercel-marketplace) blog post.
diff --git a/apps/docs/content/guides/local-development/cli/getting-started.mdx b/apps/docs/content/guides/local-development/cli/getting-started.mdx
index 8b531f49e4b..10838693b14 100644
--- a/apps/docs/content/guides/local-development/cli/getting-started.mdx
+++ b/apps/docs/content/guides/local-development/cli/getting-started.mdx
@@ -4,7 +4,7 @@ description: 'The Supabase CLI provides tools to develop your project locally, d
subtitle: 'Develop locally, deploy to the Supabase Platform, and set up CI/CD workflows'
---
-The Supabase CLI enables you to run the entire Supabase stack locally, on your machine or in a CI environment. With just two commands, you can set up and start a new local project:
+The Supabase CLI enables you to run the entire Supabase stack locally, on your machine or in a CI environment. With two commands, you can set up and start a new local project:
1. `supabase init` to create a new local project
2. `supabase start` to launch the Supabase services
@@ -58,7 +58,7 @@ and run one of the following:
- `sudo rpm -i <...>.rpm`
-
+
Run the CLI by prefixing each command with `npx` or `bunx`:
@@ -122,7 +122,7 @@ brew link --overwrite supabase-beta
Beta builds are attached to [GitHub pre-releases](https://github.com/supabase/cli/releases). Download the `.apk`, `.deb`, or `.rpm` for your platform and install with the same commands as [Linux packages](#linux-packages) above.
-
+
Install as a dev dependency:
@@ -199,7 +199,7 @@ brew upgrade supabase-beta
- `sudo rpm -i <...>.rpm`
-
+
If you have installed the CLI as dev dependency via [npm](https://www.npmjs.com/package/supabase), you can update it with:
diff --git a/apps/docs/content/guides/local-development/customizing-email-templates.mdx b/apps/docs/content/guides/local-development/customizing-email-templates.mdx
index bc281e89d15..d0ad4d727b9 100644
--- a/apps/docs/content/guides/local-development/customizing-email-templates.mdx
+++ b/apps/docs/content/guides/local-development/customizing-email-templates.mdx
@@ -106,7 +106,7 @@ There are several authentication-related email templates which can be configured
**Default subject**: "`{{ .Token }} is your verification code`"
**When sent**: When a user needs to re-authenticate for sensitive operations
**Purpose**: Ask users to verify their identity before a sensitive operation
-**Content**: Contains a 6-digit OTP code for verification
+**Content**: Contains a 8-digit OTP code for verification
## Available security notification email templates
@@ -181,7 +181,7 @@ https://project-ref.supabase.co/auth/v1/verify?token={{ .TokenHash }}&type=email
### `Token`
-Contains a 6-digit One-Time-Password (OTP) that can be used instead of the `ConfirmationURL`.
+Contains a 8-digit One-Time-Password (OTP) that can be used instead of the `ConfirmationURL`.
**Usage**
diff --git a/apps/docs/content/guides/local-development/overview.mdx b/apps/docs/content/guides/local-development/overview.mdx
index 708a4d065c0..0c2b6f00169 100644
--- a/apps/docs/content/guides/local-development/overview.mdx
+++ b/apps/docs/content/guides/local-development/overview.mdx
@@ -7,7 +7,7 @@ video: 'https://www.youtube-nocookie.com/v/vyHyYpvjaks'
tocVideo: 'vyHyYpvjaks'
---
-Supabase is a flexible platform that lets you decide how you want to build your projects. You can use the Dashboard directly to get up and running quickly, or use a proper local setup. We suggest you work locally and deploy your changes to a linked project on the [Supabase Platform](https://app.supabase.io/).
+Supabase is a flexible platform that lets you decide how you want to build your projects. You can use the Dashboard directly to get up and running, or use a proper local setup. We suggest you work locally and deploy your changes to a linked project on the [Supabase Platform](https://app.supabase.io/).
Develop locally using the CLI to run a local Supabase stack. You can use the integrated Studio Dashboard to make changes, then capture your changes in schema migration files, which can be saved in version control.
diff --git a/apps/docs/content/guides/platform/backups.mdx b/apps/docs/content/guides/platform/backups.mdx
index 620117f4e2c..9be4bdb6459 100644
--- a/apps/docs/content/guides/platform/backups.mdx
+++ b/apps/docs/content/guides/platform/backups.mdx
@@ -91,7 +91,6 @@ Projects that want to use PITR must also use at least a Small compute add-on to
ca3-F1HvR9i938OgVwpCFwi1jTsbhe1hvT0Ic3efPY3Q
```
-Add the record to your domains' DNS settings. Make sure to trim surrounding whitespace. Use a low TTL value so you can quickly change the records if you make a mistake.
+Add the record to your domains' DNS settings. Make sure to trim surrounding whitespace. Use a low TTL value so you can change the records if you make a mistake.
Some DNS registrars automatically append your domain name to the DNS entries being created. As such, creating a DNS record for `api.example.com` might instead create a record for `api.example.com.example.com`. In such cases, remove the domain name from the records you're creating; as an example, you would create a TXT record for `api`, instead of `api.example.com`.
@@ -168,7 +168,7 @@ Assume your Supabase project's domain is `abcdefghijklmnopqrst.supabase.co` and
Use the [`vanity-subdomains check-availability`](/docs/reference/cli/supabase-vanity-subdomains-check-availability) command of the CLI to check if your desired subdomain is available for use:
```bash
-supabase vanity-subdomains --project-ref abcdefghijklmnopqrst check-availability --desired-subdomain my-example-brand --experimental
+supabase vanity-subdomains check-availability --project-ref abcdefghijklmnopqrst --desired-subdomain my-example-brand --experimental
```
### Prepare to activate the subdomain
@@ -200,7 +200,7 @@ Once you've chosen an available subdomain and have done all the necessary prepar
Use the [`vanity-subdomains activate`](/docs/reference/cli/supabase-vanity-subdomains-activate) command to activate and claim your subdomain:
```bash
-supabase vanity-subdomains --project-ref abcdefghijklmnopqrst activate --desired-subdomain my-example-brand --experimental
+supabase vanity-subdomains activate --project-ref abcdefghijklmnopqrst --desired-subdomain my-example-brand --experimental
```
If you wish to use the new domain in client code, you can set it up like so:
diff --git a/apps/docs/content/guides/platform/delete-project.mdx b/apps/docs/content/guides/platform/delete-project.mdx
new file mode 100644
index 00000000000..3c3214d62fd
--- /dev/null
+++ b/apps/docs/content/guides/platform/delete-project.mdx
@@ -0,0 +1,136 @@
+---
+title: Deleting Your Project
+subtitle: Understanding the permanent consequences and how to protect yourself
+---
+
+Deleting a Supabase project is a **permanent and irreversible action**. Before proceeding, understand the full scope of what will be deleted and take precautions to prevent accidental data loss.
+
+
+
+We cannot recover deleted projects. All data, backups, and configurations are permanently removed. Ensure you have exported all critical data or saved backups before proceeding to delete your project.
+
+
+
+## What gets deleted
+
+When you delete a project, **all artifacts are permanently removed and you cannot recover them**.
+Some of these artifacts includes (but are not limited to):
+
+- **Database and all data**: Your entire Postgres database, including all tables, schemas, and records
+- **Edge Functions**: All deployed Edge Functions and their source code are removed
+- **Storage objects**: All files in Storage buckets are permanently deleted
+- **Backups**: All automated backups and point-in-time recovery snapshots are inaccessible
+- **Authentication data**: User accounts, sessions, and auth logs are removed
+- **Real-time subscriptions**: All active subscriptions and configurations are cleared
+- **API keys and credentials**: All project API keys, service role keys, and webhooks are invalidated
+- **Custom domains and SSL certificates**: Any custom domains linked to the project are removed
+
+## How to delete a project
+
+You can delete a project through any of these methods:
+
+### Via dashboard
+
+1. Navigate to your project's [**Settings** > **General** > **Delete project**](/dashboard/project/qiuwhyoiycwkuobqirkr/settings/general#delete-project)
+2. Click **Delete Project**
+3. Enter your project name exactly as it appears to confirm
+4. Review the confirmation dialog and click **Delete**
+
+### Via Supabase CLI
+
+```bash
+supabase projects delete
+```
+
+For more information, see the [Supabase CLI documentation](/docs/reference/cli/supabase-projects-delete).
+
+### Via management API
+
+```bash
+curl -X DELETE https://api.supabase.com/v1/projects/ \
+ -H "Authorization: Bearer "
+```
+
+For more information, see the [Management API documentation](/docs/reference/api/v1-delete-a-project).
+
+## After deletion
+
+Once a project is deleted:
+
+- Your project URL will no longer be accessible
+- DNS records will be cleaned up (may take up to 24 hours to fully propagate)
+- Billing for this project will stop immediately
+- You cannot recover or restore the project
+
+
+
+Deleting projects stops new usage from accumulating, but does not remove usage that already occurred during the current billing cycle. For quota-based limits, that usage still counts until the billing period resets.
+See our [Fair Use Policy](/docs/guides/platform/billing-faq#fair-use-policy) for further details.
+
+
+
+## Alternative: Pause your project
+
+If you're unsure about deletion, consider pausing your project instead:
+
+
+
+Note: Only Free Projects can be paused at this time.
+
+
+
+- **Paused projects** stop incurring compute charges
+- **Data is preserved** and can be accessed when you resume
+- **Quick recovery** — Resume the project at any time without data loss
+
+To pause a project, navigate to [**Settings** > **General** > **Project availability**](/dashboard/project/qiuwhyoiycwkuobqirkr/settings/general) and click **Pause Project**.
+
+## Protective measures to consider
+
+To safeguard against accidental deletion, consider the following:
+
+### 1. Backup your database
+
+You can backup your database by following the [backup and restore guide](/docs/guides/platform/migrating-within-supabase/backup-restore) provided.
+
+If you are on the Pro, Team or Enterprise Plan, with legacy logical backups, you can download a copy from [your Supabase dashboard](/dashboard/project/_/database/backups/scheduled)
+
+### 2. Download storage objects
+
+- Back up all important files from Storage buckets
+- Use the Supabase dashboard or API to download files in bulk
+- Store in a secure location outside of Supabase
+
+### 3. Document configuration
+
+- Export Edge Function code and configurations from the dashboard
+- Save authentication provider settings (OAuth, SAML, etc.)
+- Document any custom database functions, triggers, or policies
+- Record webhook configurations and integrations
+
+### 4. Enable access controls
+
+
+
+Restrict who can delete projects within your organization to prevent accidental deletion by team members.
+See [Access Controls](/docs/guides/platform/access-control)
+
+
+
+- Set up role-based access controls to limit deletion permissions
+- Require multi-factor authentication (MFA) for sensitive operations
+- Use the Supabase dashboard to configure team member permissions
+
+### 5. Monitor project activity
+
+- Enable audit logging to track who has access to your project
+- Review and revoke unnecessary API keys before deletion
+- Check for any scheduled jobs or integrations that depend on the project
+
+## Need help?
+
+If you're unsure about deletion or need assistance:
+
+- Review this guide and the protection measures above
+- Contact [Supabase support](/support) for guidance
+- Consider reaching out to your team before deleting shared projects
diff --git a/apps/docs/content/guides/platform/free-project-pausing.mdx b/apps/docs/content/guides/platform/free-project-pausing.mdx
new file mode 100644
index 00000000000..9e6b2fc6bbf
--- /dev/null
+++ b/apps/docs/content/guides/platform/free-project-pausing.mdx
@@ -0,0 +1,45 @@
+---
+title: 'Project Pausing'
+description: 'Free project pausing behavior.'
+---
+
+{/* supa-mdx-lint-disable-next-line Rule003Spelling */}
+Supabase pauses Free Plan projects that show low activity over a 7-day period to save server resources. This guide explains how pausing works, how to restore a paused project, and how to avoid pausing altogether.
+
+
+
+Projects under a paid plan cannot be paused and are not subject to automatic pausing for inactivity. To pause a project currently under a paid plan, first transfer the project to an organization on the Free plan.
+
+
+
+## How automatic pausing works
+
+A Free plan project is considered inactive if it does not receive sufficient user database activity over the past week. Projects with too few user queries during that window are the clearest candidates for pausing. While you may be actively using the project, it's possible that usage is not enough to exclude it from automatic pausing. Typically a few user requests to the database each day over the previous week is enough to keep the project from being paused.
+
+Supabase sends two emails to the project owner regarding project pausing:
+
+1. A warning email roughly one week before the pause takes effect.
+2. A confirmation email once the project has been paused.
+
+After receiving an initial email warning, the pause can be prevented by taking one of the following steps:
+
+- Visit the project from the [Supabase Dashboard](/dashboard/project/_) to generate activity.
+- Generate a sufficient amount of activity by making API calls to your project or sending requests via your connected application.
+
+## Restoring a paused project
+
+You can restore a paused project for up to 90 days after it was paused:
+
+1. Open the [Supabase Dashboard](/dashboard/organizations)
+2. Select the organization, followed by the paused project
+3. Click **Resume project** and confirm
+
+The project will return to its previous state, including data and configurations.
+
+### 90-day window to restore
+
+Once the project is paused, there is a 90-day window to restore the project on the platform from within Supabase Studio. The 90-day window allows Supabase to introduce platform changes that may not be backward compatible with older backups. Unlike active projects, static backups can't be updated to accommodate such changes.
+
+## Preventing automatic project pausing
+
+To prevent future automatic pausing, upgrade to the Pro Plan from [Billing Settings](/dashboard/org/_/billing?panel=subscriptionPlan). Paid projects cannot be paused and are not subject to pausing for inactivity.
diff --git a/apps/docs/content/guides/platform/manage-your-usage/branching.mdx b/apps/docs/content/guides/platform/manage-your-usage/branching.mdx
index 774214977f3..64c6f18a4bd 100644
--- a/apps/docs/content/guides/platform/manage-your-usage/branching.mdx
+++ b/apps/docs/content/guides/platform/manage-your-usage/branching.mdx
@@ -5,7 +5,7 @@ title: 'Manage Branching usage'
## What you are charged for
-Each [Preview branch](/docs/guides/deployment/branching) is a separate environment with all Supabase services (Database, Auth, Storage, etc.). You're charged for usage within that environment—such as [Compute](/docs/guides/platform/manage-your-usage/compute), [Disk Size](/docs/guides/platform/manage-your-usage/disk-size), [Egress](/docs/guides/platform/manage-your-usage/egress), and [Storage](/docs/guides/platform/manage-your-usage/storage-size)—just like the project you branched from.
+Each [Preview branch](/docs/guides/deployment/branching) is a separate environment with all Supabase services (Database, Auth, Storage, etc.). You're charged for usage within that environment—such as [Compute](/docs/guides/platform/manage-your-usage/compute), [Disk Size](/docs/guides/platform/manage-your-usage/disk-size), [Egress](/docs/guides/platform/manage-your-usage/egress), and [Storage](/docs/guides/platform/manage-your-usage/storage-size)—the same as the project you branched from.
diff --git a/apps/docs/content/guides/platform/manage-your-usage/ipv4.mdx b/apps/docs/content/guides/platform/manage-your-usage/ipv4.mdx
index a43266ac178..8c004f63755 100644
--- a/apps/docs/content/guides/platform/manage-your-usage/ipv4.mdx
+++ b/apps/docs/content/guides/platform/manage-your-usage/ipv4.mdx
@@ -117,4 +117,4 @@ If you remove the IPv4 add-on, you are no longer billed from the time of removal
## Optimize usage
-To see whether your database actually needs a dedicated IPv4 address, refer to [When you need the IPv4 add-on](/docs/guides/platform/ipv4-address#when-you-need-the-ipv4-add-on).
+To see whether your database needs a dedicated IPv4 address, refer to [When you need the IPv4 add-on](/docs/guides/platform/ipv4-address#when-you-need-the-ipv4-add-on).
diff --git a/apps/docs/content/guides/platform/manage-your-usage/logs-ingest.mdx b/apps/docs/content/guides/platform/manage-your-usage/logs-ingest.mdx
index af40d0e9678..7c26bbc978c 100644
--- a/apps/docs/content/guides/platform/manage-your-usage/logs-ingest.mdx
+++ b/apps/docs/content/guides/platform/manage-your-usage/logs-ingest.mdx
@@ -65,7 +65,7 @@ Every service in your Supabase project automatically generates Logs — you don'
- **Reduce log-level verbosity** in your Edge Functions and server-side code (for example, `info` → `warn` in production).
- **Audit verbose logging in your application code.** Application-level logs forwarded to Supabase services count toward ingest.
-- **Cap log payload size.** Large structured payloads inflate GB-billed volume quickly.
+- **Cap log payload size.** Large structured payloads can inflate GB-billed volume.
- **Investigate spikes.** Use the [**Logs Explorer**](/dashboard/project/_/logs-explorer) section of the Dashboard to find services or endpoints producing unusually high volume.
## Exceeding Quotas
diff --git a/apps/docs/content/guides/platform/migrating-to-supabase/auth0.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/auth0.mdx
index c8f9825d7ef..806660c54a5 100644
--- a/apps/docs/content/guides/platform/migrating-to-supabase/auth0.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/auth0.mdx
@@ -80,6 +80,7 @@ Migrate existing users to Supabase Auth. This requires two main steps: first, ch
```ts
import { createClient } from '@supabase/supabase-js'
+
const supabase = createClient('your_project_url', 'your_supabase_api_key')
// ---cut---
@@ -100,6 +101,7 @@ Migrate existing users to Supabase Auth. This requires two main steps: first, ch
```ts
import { createClient } from '@supabase/supabase-js'
+
const supabase = createClient('your_project_url', 'your_supabase_api_key')
// ---cut---
@@ -122,6 +124,7 @@ For passwordless signin via email or phone, check for users with verified email
```ts
import { createClient } from '@supabase/supabase-js'
+
const supabase = createClient('your_project_url', 'your_supabase_api_key')
// ---cut---
@@ -162,6 +165,7 @@ Both columns are accessible from the admin user methods. To create a user with c
```ts
import { createClient } from '@supabase/supabase-js'
+
const supabase = createClient('your_project_url', 'your_supabase_api_key')
// ---cut---
@@ -198,7 +202,6 @@ create table private.user_metadata (
I have IDs assigned to existing users in my database, how can I maintain these IDs?}
+header="I have IDs assigned to existing users in my database, how can I maintain these IDs?"
id="custom-user-id"
>
@@ -229,7 +232,7 @@ const { data, error } = await supabase.auth.admin.createUser({
How can I allow my users to retain their existing password?}
+header="How can I allow my users to retain their existing password?"
id="existing-password"
>
@@ -238,7 +241,7 @@ Supabase Auth never stores passwords as plaintext. Since Supabase Auth supports
My users have multi-factor authentication (MFA) enabled, how do I make sure they don't have to set up MFA again?}
+header="My users have multi-factor authentication (MFA) enabled, how do I make sure they don't have to set up MFA again?"
id="mfa"
>
@@ -247,7 +250,7 @@ You can obtain an export of your users' MFA secrets by opening a support ticket
How do I migrate existing SAML Single Sign-On (SSO) connections?}
+header="How do I migrate existing SAML Single Sign-On (SSO) connections?"
id="saml"
>
@@ -256,7 +259,7 @@ Customers may need to link their identity provider with Supabase Auth separately
How do I migrate my Auth0 organizations to Supabase?}
+header="How do I migrate my Auth0 organizations to Supabase?"
id="migrate-org"
>
diff --git a/apps/docs/content/guides/platform/migrating-to-supabase/firestore-data.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/firestore-data.mdx
index de95b7aee8b..aafd172a0b4 100644
--- a/apps/docs/content/guides/platform/migrating-to-supabase/firestore-data.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/firestore-data.mdx
@@ -124,7 +124,7 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
- `writeRecord`: This function automatically handles the process of writing data to other JSON files (useful for "flatting" your document into separate JSON files to be written to separate database tables). `writeRecord` takes the following parameters:
- `name`: Name of the JSON file to write to.
- `doc`: The document to write to the file.
- - `recordCounters`: The same `recordCounters` object that was passed to this hook (just passes it on).
+ - `recordCounters`: The same `recordCounters` object that was passed to this hook (passes it on).
### Examples
diff --git a/apps/docs/content/guides/platform/migrating-to-supabase/heroku.mdx b/apps/docs/content/guides/platform/migrating-to-supabase/heroku.mdx
index f361f224f8d..036416e8746 100644
--- a/apps/docs/content/guides/platform/migrating-to-supabase/heroku.mdx
+++ b/apps/docs/content/guides/platform/migrating-to-supabase/heroku.mdx
@@ -8,7 +8,7 @@ tocVideo: 'xsRhPMphtZ4'
Supabase is one of the best [free alternatives to Heroku Postgres](/alternatives/supabase-vs-heroku-postgres). This guide shows how to migrate your Heroku Postgres database to Supabase. This migration requires the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) CLI tools, which are installed automatically as part of the complete Postgres installation package.
-Alternatively, use the [Heroku to Supabase migration tool](https://migrate.supabase.com/) to migrate in just a few clicks.
+Alternatively, use the [Heroku to Supabase migration tool](https://migrate.supabase.com/) to migrate in a few clicks.
## Quick demo
diff --git a/apps/docs/content/guides/platform/migrating-within-supabase/backup-restore.mdx b/apps/docs/content/guides/platform/migrating-within-supabase/backup-restore.mdx
index 744d61ee7c5..cd989bbccc6 100644
--- a/apps/docs/content/guides/platform/migrating-within-supabase/backup-restore.mdx
+++ b/apps/docs/content/guides/platform/migrating-within-supabase/backup-restore.mdx
@@ -78,7 +78,6 @@ breadcrumb: 'Migrations'
-If you can't find the Network Restrictions section at the bottom of your [Database Settings](/dashboard/project/_/database/settings), update your version of Postgres in the [Infrastructure Settings](/dashboard/project/_/settings/infrastructure).
+If you can't find the Network Restrictions section in your [Database Settings](/dashboard/project/_/database/settings), update your Postgres version in [Infrastructure Settings](/dashboard/project/_/settings/infrastructure).
-Each Supabase project comes with configurable restrictions on the IP ranges that are allowed to connect to Postgres and its pooler ("your database"). These restrictions are enforced before traffic reaches your database. If a connection is not restricted by IP, it still needs to authenticate successfully with valid database credentials.
+Each Supabase project supports configurable restrictions on the IP ranges allowed to connect to Postgres and its pooler. These restrictions are enforced before traffic reaches your database. Connections that aren't restricted by IP still need to authenticate with valid database credentials.
-If direct connections to your database [resolve to a IPv6 address](/dashboard/project/_/database/settings), you need to add both IPv4 and IPv6 CIDRs to the list of allowed CIDRs. Network Restrictions will be applied to all database connection routes, whether pooled or direct. You will need to add both the IPv4 and IPv6 networks you want to allow. There are two exceptions: If you have been granted an extension on the IPv6 migration OR if you have purchased the [IPv4 add-on](/dashboard/project/_/settings/addons), you need only add IPv4 CIDRs.
+If direct connections to your database [resolve to an IPv6 address](/dashboard/project/_/database/settings), add both IPv4 and IPv6 CIDRs to your allowlist. Network restrictions apply to all connection routes, whether pooled or direct. There are two exceptions: if you have an extension on the IPv6 migration, or if you have the [IPv4 add-on](/dashboard/project/_/settings/addons), you only need to add IPv4 CIDRs.
-## To get started via the Dashboard:
+## Configure with the dashboard [#to-get-started-via-the-dashboard]
-Network restrictions can be configured in the [Database Settings](/dashboard/project/_/database/settings) 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 configure network restrictions with the dashboard:
-## To get started via the Management API:
+1. Open your project's [Database Settings](/dashboard/project/_/database/settings) page.
+1. In the Network Restrictions section, make your changes. You need [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) to make changes.
-You can also manage network restrictions using the Management API:
+## Configure with the CLI [#to-get-started-via-the-cli]
-```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:
+To configure network restrictions with the CLI:
1. [Install](/docs/guides/cli) the Supabase CLI 1.22.0+.
-1. [Log in](/docs/guides/cli/local-development#log-in-to-the-supabase-cli) to your Supabase account using the CLI.
-1. If your project was created before 23rd December 2022, it will need to be [upgraded to the latest Supabase version](/docs/guides/platform/migrating-and-upgrading-projects) before Network Restrictions can be used.
-1. 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.
+1. [Log in](/docs/guides/cli/local-development#log-in-to-the-supabase-cli) to your Supabase account.
+1. If your project was created before December 23, 2022, [upgrade it to the latest Supabase version](/docs/guides/platform/migrating-and-upgrading-projects) before using network restrictions.
+1. Ensure you have [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project.
### Check restrictions
-You can use the `get` subcommand of the CLI to retrieve the restrictions currently in effect.
+To check your current network restrictions:
-If restrictions have been applied, the output of the `get` command will reflect the IP ranges allowed to connect:
+1. Complete the steps in [Configure with the CLI](#to-get-started-via-the-cli).
+1. Run the `get` subcommand to retrieve the restrictions currently in effect:
-```bash
-> supabase network-restrictions --project-ref {ref} get --experimental
-DB Allowed IPv4 CIDRs: &[183.12.1.1/24]
-DB Allowed IPv6 CIDRs: &[2001:db8:3333:4444:5555:6666:7777:8888/64]
-Restrictions applied successfully: true
-```
+ ```bash
+ > supabase network-restrictions get --project-ref {ref} --experimental
+ DB Allowed IPv4 CIDRs: &[183.12.1.1/24]
+ DB Allowed IPv6 CIDRs: &[2001:db8:3333:4444:5555:6666:7777:8888/64]
+ Restrictions applied successfully: true
+ ```
-If restrictions have never been applied to your project, the list of allowed CIDRs will be empty, but they will also not have been applied ("Restrictions applied successfully: false"). As a result, all IPs are allowed to connect to your database:
+ If restrictions have never been applied, the allowed CIDRs list is empty and `Restrictions applied successfully` is `false`. All IPs can connect:
-```bash
-> supabase network-restrictions --project-ref {ref} get --experimental
-DB Allowed IPv4 CIDRs: []
-DB Allowed IPv6 CIDRs: []
-Restrictions applied successfully: false
-```
+ ```bash
+ > supabase network-restrictions get --project-ref {ref} --experimental
+ DB Allowed IPv4 CIDRs: []
+ DB Allowed IPv6 CIDRs: []
+ Restrictions applied successfully: false
+ ```
### Update restrictions
-The `update` subcommand is used to apply network restrictions to your project:
+To update your network restrictions:
-```bash
-> supabase network-restrictions --project-ref {ref} update --db-allow-cidr 183.12.1.1/24 --db-allow-cidr 2001:db8:3333:4444:5555:6666:7777:8888/64 --experimental
-DB Allowed IPv4 CIDRs: &[183.12.1.1/24]
-DB Allowed IPv6 CIDRs: &[2001:db8:3333:4444:5555:6666:7777:8888/64]
-Restrictions applied successfully: true
-```
+1. Complete the steps in [Configure with the CLI](#to-get-started-via-the-cli).
+1. Run the `update` subcommand with the CIDRs you want to allow:
-The restrictions specified (in the form of CIDRs) replaces any restrictions that might have been applied in the past.
-To add to the existing restrictions, you must include the existing restrictions within the list of CIDRs provided to the `update` command.
+ ```bash
+ > supabase network-restrictions update --project-ref {ref} --db-allow-cidr 183.12.1.1/24 --db-allow-cidr 2001:db8:3333:4444:5555:6666:7777:8888/64 --experimental
+ DB Allowed IPv4 CIDRs: &[183.12.1.1/24]
+ DB Allowed IPv6 CIDRs: &[2001:db8:3333:4444:5555:6666:7777:8888/64]
+ Restrictions applied successfully: true
+ ```
+
+ The CIDRs you provide replace any previously applied restrictions. To keep existing restrictions, include them alongside any new CIDRs in the `update` command.
+
+### Append a CIDR to existing restrictions
+
+To append a CIDR to your existing restrictions:
+
+1. Complete the steps in [Configure with the CLI](#to-get-started-via-the-cli).
+1. Run the `update` subcommand with the `--append` flag to add a CIDR without replacing existing restrictions:
+
+ ```bash
+ > supabase network-restrictions update --project-ref {ref} --db-allow-cidr 1.2.3.4/32 --append --experimental
+ DB Allowed IPv4 CIDRs: &[183.12.1.1/24 1.2.3.4/32]
+ DB Allowed IPv6 CIDRs: &[2001:db8:3333:4444:5555:6666:7777:8888/64]
+ Restrictions applied successfully: true
+ ```
### Remove restrictions
-To remove all restrictions on your project, you can use the `update` subcommand with the CIDR `0.0.0.0/0`:
+To remove all network restrictions:
-```bash
-> supabase network-restrictions --project-ref {ref} update --db-allow-cidr 0.0.0.0/0 --db-allow-cidr ::/0 --experimental
-DB Allowed IPv4 CIDRs: &[0.0.0.0/0]
-DB Allowed IPv6 CIDRs: &[::/0]
-Restrictions applied successfully: true
-```
+1. Complete the steps in [Configure with the CLI](#to-get-started-via-the-cli).
+1. Run the `update` subcommand with the CIDR `0.0.0.0/0` to remove all restrictions:
+
+ ```bash
+ > supabase network-restrictions update --project-ref {ref} --db-allow-cidr 0.0.0.0/0 --db-allow-cidr ::/0 --experimental
+ DB Allowed IPv4 CIDRs: &[0.0.0.0/0]
+ DB Allowed IPv6 CIDRs: &[::/0]
+ Restrictions applied successfully: true
+ ```
## Limitations
-- The current iteration of Network Restrictions applies to connections to Postgres and the database pooler; it doesn't currently apply to APIs offered over HTTPS (e.g., PostgREST, Storage, and Auth). This includes using Supabase client libraries like [supabase-js](/docs/reference/javascript).
-- If network restrictions are enabled, direct access to your database from Edge Functions will always be blocked. Using the Supabase client library [supabase-js](/docs/reference/javascript) is recommended to connect to a database with network restrictions from Edge Functions.
+- Network restrictions apply to Postgres and the database pooler. They don't apply to HTTPS APIs such as PostgREST, Storage, and Auth, or to Supabase client libraries like [supabase-js](/docs/reference/javascript).
+- With network restrictions applied, Edge functions lose direct access to the database. Use [supabase-js](/docs/reference/javascript) to connect to the database from Edge Functions instead.
diff --git a/apps/docs/content/guides/platform/performance.mdx b/apps/docs/content/guides/platform/performance.mdx
index 0c75a8d30fb..b95198471ff 100644
--- a/apps/docs/content/guides/platform/performance.mdx
+++ b/apps/docs/content/guides/platform/performance.mdx
@@ -31,7 +31,7 @@ In such a scenario, you can consider:
You can use the [pg_stat_activity](https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW) view to debug which clients are holding open connections on your DB. `pg_stat_activity` only exposes information on direct connections to the database. Information on the number of connections to Supavisor is available [via the metrics endpoint](../telemetry/metrics).
-Depending on the clients involved, you might be able to configure them to work with fewer connections (e.g. by imposing a limit on the maximum number of connections they're allowed to use), or shift specific workloads to connect via [Supavisor](/docs/guides/database/connecting-to-postgres#connection-pooler) instead. Transient workflows, which can quickly scale up and down in response to traffic (e.g. serverless functions), can especially benefit from using a connection pooler rather than connecting to the DB directly.
+Depending on the clients involved, you might be able to configure them to work with fewer connections (e.g. by imposing a limit on the maximum number of connections they're allowed to use), or shift specific workloads to connect via [Supavisor](/docs/guides/database/connecting-to-postgres#connection-pooler) instead. Transient workflows, which can scale up and down rapidly in response to traffic (e.g. serverless functions), can especially benefit from using a connection pooler rather than connecting to the DB directly.
### Allowing higher number of connections
diff --git a/apps/docs/content/guides/platform/privatelink.mdx b/apps/docs/content/guides/platform/privatelink.mdx
index 4e602ed56cc..d3417eb68a5 100644
--- a/apps/docs/content/guides/platform/privatelink.mdx
+++ b/apps/docs/content/guides/platform/privatelink.mdx
@@ -26,7 +26,7 @@ Supabase PrivateLink is an organisation level configuration. It works by sharing
The connection architecture changes from public internet routing to a dedicated private path through AWS's secure network backbone.
-Supabase PrivateLink is currently just for direct database and PgBouncer connections only. It does not support other Supabase services like API, Storage, Auth, or Realtime. These services will continue to operate over public internet connections.
+Supabase PrivateLink currently supports direct database and PgBouncer connections only. It does not support other Supabase services like API, Storage, Auth, or Realtime. These services will continue to operate over public internet connections.
## Requirements
diff --git a/apps/docs/content/guides/platform/read-replicas.mdx b/apps/docs/content/guides/platform/read-replicas.mdx
index 12133addca9..b709c7281d9 100644
--- a/apps/docs/content/guides/platform/read-replicas.mdx
+++ b/apps/docs/content/guides/platform/read-replicas.mdx
@@ -32,7 +32,6 @@ You can only read data from a Read Replica. This is in contrast to a Primary dat
- When your database starts slowing down, you face a choice: make your existing database bigger (scale vertically), or spread the load across multiple databases (scale horizontally). Both approaches work. Neither is universally correct. The right answer depends on your workload, your budget, and where the bottleneck actually is.
+ When your database starts slowing down, you face a choice: make your existing database bigger (scale vertically), or spread the load across multiple databases (scale horizontally). Both approaches work. Neither is universally correct. The right answer depends on your workload, your budget, and where the bottleneck is.
```mermaid
flowchart TD
diff --git a/apps/docs/content/guides/platform/ssl-enforcement.mdx b/apps/docs/content/guides/platform/ssl-enforcement.mdx
index 74249a6da89..2f70acddc3f 100644
--- a/apps/docs/content/guides/platform/ssl-enforcement.mdx
+++ b/apps/docs/content/guides/platform/ssl-enforcement.mdx
@@ -70,7 +70,7 @@ To get started:
You can use the `get` subcommand of the CLI to check whether SSL is currently being enforced:
```bash
-supabase ssl-enforcement --project-ref {ref} get --experimental
+supabase ssl-enforcement get --project-ref {ref} --experimental
```
Response if SSL is being enforced:
@@ -90,13 +90,13 @@ SSL is *NOT* being enforced.
The `update` subcommand is used to change the SSL enforcement status for your project:
```bash
-supabase ssl-enforcement --project-ref {ref} update --enable-db-ssl-enforcement --experimental
+supabase ssl-enforcement update --project-ref {ref} --enable-db-ssl-enforcement --experimental
```
Similarly, to disable SSL enforcement:
```bash
-supabase ssl-enforcement --project-ref {ref} update --disable-db-ssl-enforcement --experimental
+supabase ssl-enforcement update --project-ref {ref} --disable-db-ssl-enforcement --experimental
```
### A note about Postgres SSL modes
diff --git a/apps/docs/content/guides/platform/sso.mdx b/apps/docs/content/guides/platform/sso.mdx
index 9a690a703e7..2349a28d27c 100644
--- a/apps/docs/content/guides/platform/sso.mdx
+++ b/apps/docs/content/guides/platform/sso.mdx
@@ -74,7 +74,7 @@ Users start their login at supabase.com by entering their email address, then ar
- **Login flows** - Choose between IdP-initiated (users start from identity provider), SP-initiated (users start at supabase.com), or both. IdP-initiated is recommended for most organizations and requires no domain configuration. See [Choosing the Right Login Flow](/docs/guides/platform/sso/choosing-login-flow) for guidance.
- **Email domains** - Required only if you enable SP-initiated login. You can associate one or more email domains with your SSO provider. Users with matching email addresses can sign in via SSO at supabase.com. Not required for IdP-initiated flow.
-- **Auto-join** - Optionally allow users with a matching domain to be added to your organization automatically when they sign in via SSO. Auto-join applies on every login, not just first signup, making it easy to test before enabling.
+- **Auto-join** - Optionally allow users with a matching domain to join your organization automatically when they sign in via SSO. This applies on every login, not only on first signup.
- **Default role for auto-joined users** - Choose the role (e.g., `Read-only`, `Developer`, `Administrator`, `Owner`) that automatically joined users receive. We recommend using `Developer` as the default (principle of least privilege) and promoting users individually as needed. Refer to [access control](/docs/guides/platform/access-control) for more information about roles.
- **Invitation types** - When inviting users to your organization, you can explicitly choose whether the invitation requires SSO authentication or allows non-SSO login (password/social). This enables mixed authentication organizations with both SSO and non-SSO users.
diff --git a/apps/docs/content/guides/platform/sso/azure.mdx b/apps/docs/content/guides/platform/sso/azure.mdx
index 62d0a7ef3eb..6daa75a6f94 100644
--- a/apps/docs/content/guides/platform/sso/azure.mdx
+++ b/apps/docs/content/guides/platform/sso/azure.mdx
@@ -64,7 +64,7 @@ First you need to download Supabase's SAML metadata file. Click the button below
Alternatively, visit this page to initiate a download: `https://alt.supabase.io/auth/v1/sso/saml/metadata?download=true`
-Click on the _Upload metadata file_ option in the toolbar and select the file you just downloaded.
+Click on the _Upload metadata file_ option in the toolbar and select the file you downloaded.

@@ -134,7 +134,7 @@ By default this setting is disabled, users logging in via SSO will not be added

-Toggle this on if you want SSO-authenticated users to be **automatically added to your organization** when they log in via SSO. Auto-join applies on **every login**, not just first signup - this makes it safe to test SSO before enabling this feature.
+Toggle this on if you want SSO-authenticated users to be **automatically added to your organization** when they log in via SSO. Auto-join applies on **every login**, not only on first signup - this makes it safe to test SSO before enabling this feature.

diff --git a/apps/docs/content/guides/platform/sso/gsuite.mdx b/apps/docs/content/guides/platform/sso/gsuite.mdx
index 4babba24020..608d8f03d0c 100644
--- a/apps/docs/content/guides/platform/sso/gsuite.mdx
+++ b/apps/docs/content/guides/platform/sso/gsuite.mdx
@@ -144,7 +144,7 @@ By default this setting is disabled, users logging in via SSO will not be added

-Toggle this on if you want SSO-authenticated users to be **automatically added to your organization** when they log in via SSO. Auto-join applies on **every login**, not just first signup - this makes it safe to test SSO before enabling this feature.
+Toggle this on if you want SSO-authenticated users to be **automatically added to your organization** when they log in via SSO. Auto-join applies on **every login**, not only on first signup - this makes it safe to test SSO before enabling this feature.

diff --git a/apps/docs/content/guides/platform/sso/okta.mdx b/apps/docs/content/guides/platform/sso/okta.mdx
index 370b410872a..b62cd6588ab 100644
--- a/apps/docs/content/guides/platform/sso/okta.mdx
+++ b/apps/docs/content/guides/platform/sso/okta.mdx
@@ -130,7 +130,7 @@ By default this setting is disabled, users logging in via SSO will not be added

-Toggle this on if you want SSO-authenticated users to be **automatically added to your organization** when they log in via SSO. Auto-join applies on **every login**, not just first signup - this makes it safe to test SSO before enabling this feature.
+Toggle this on if you want SSO-authenticated users to be **automatically added to your organization** when they log in via SSO. Auto-join applies on **every login**, not only on first signup - this makes it safe to test SSO before enabling this feature.

diff --git a/apps/docs/content/guides/platform/sso/testing-best-practices.mdx b/apps/docs/content/guides/platform/sso/testing-best-practices.mdx
index f9c4a5de033..a85c237a688 100644
--- a/apps/docs/content/guides/platform/sso/testing-best-practices.mdx
+++ b/apps/docs/content/guides/platform/sso/testing-best-practices.mdx
@@ -180,7 +180,7 @@ Test with 2-3 additional users to verify:
-**Recent improvement:** Auto-join now applies on EVERY login, not just first signup. This resolves a common issue where org owners would test with auto-join disabled, enable it, then log in again expecting to auto-join.
+**Recent improvement:** Auto-join now applies on EVERY login, not only on first signup. This resolves a common issue where org owners would test with auto-join disabled, enable it, then log in again expecting to auto-join.
@@ -253,7 +253,7 @@ Test with 2-3 additional users to verify:
- Auto-join works when enabled
- Users receive correct default role
- Non-matching domains are excluded (if using SP-initiated with domains)
-- Existing users auto-join on their next login (not just new signups)
+- Existing users auto-join on their next login (not only on new signups)
- Auto-join can be disabled and re-enabled as needed
- Auto-join is idempotent (no duplicate memberships)
- Auto-join works with IdP-initiated only (no domains)
@@ -369,7 +369,7 @@ SSO accounts have specific restrictions to prevent accidental organization locko
- SSO accounts CAN disable SSO providers
- Non-SSO owners CAN delete SSO providers
- Error messages clearly explain the restriction
-- Restriction applies to all SSO accounts (not just certain roles)
+- Restriction applies to all SSO accounts (not only certain roles)
## Common issues and troubleshooting
@@ -387,7 +387,7 @@ Based on customer pain points that previously required support intervention:
**Solution:**
-- Auto-join now applies on **every login**, not just first signup
+- Auto-join now applies on **every login**, not only on first signup
- To test: Enable auto-join, log out completely, log back in via SSO
- If still not working, verify domain configuration matches user email exactly
@@ -795,7 +795,7 @@ Before rolling out SSO to your organization:
- Auto-join adds users to correct organization (if enabled)
- Auto-joined users receive correct default role
-- Auto-join works on first login (not just signup)
+- Auto-join works on first login (not only on signup)
- Existing users auto-join when feature enabled
- Auto-join is idempotent (no duplicate memberships)
- Auto-join works with IdP-initiated (no domains required)
diff --git a/apps/docs/content/guides/platform/temporary-access.mdx b/apps/docs/content/guides/platform/temporary-access.mdx
index 32e067c380c..06f76a8bc15 100644
--- a/apps/docs/content/guides/platform/temporary-access.mdx
+++ b/apps/docs/content/guides/platform/temporary-access.mdx
@@ -97,6 +97,12 @@ curl -X PUT "https://api.supabase.com/v1/projects/$PROJECT_REF/database/jit" \
## Using temporary access
+
+
+This feature does not work with IPv6 Transaction pooler (PgBouncer). Direct connections and connections through the IPv4 connection pooler are fully supported.
+
+
+
To log in to the database using temporary access, existing connection strings can be used and only the password needs to be changed to the user's API or dashboard token.
For example, if a user has been authorized to assume the `postgres` role:
@@ -111,8 +117,8 @@ Connecting via the shared connection pooler requires the addition of a new conne
```
# directly in the URI
-psql 'postgres://postgres.{project-ref}:sbp_111222333aaabbbccc@aws-1-us-west-1.pooler.supabase.com:5432/postgres?options=-c%20jit%3don'
+psql 'postgres://postgres.{project-ref}:sbp_111222333aaabbbccc@aws-1-us-west-1.pooler.supabase.com:5432/postgres?options=-c%20jit%3dtrue'
# or as a connection info string
-psql "host=aws-1-us-west-1.pooler.supabase.com user=postgres.{project-ref} options='-c jit=on'"
+psql "host=aws-1-us-west-1.pooler.supabase.com user=postgres.{project-ref} options='-c jit=true'"
```
diff --git a/apps/docs/content/guides/queues/pgmq.mdx b/apps/docs/content/guides/queues/pgmq.mdx
index 6b7f85d8cb5..caca4f8a191 100644
--- a/apps/docs/content/guides/queues/pgmq.mdx
+++ b/apps/docs/content/guides/queues/pgmq.mdx
@@ -6,7 +6,7 @@ pgmq is a lightweight message queue built on Postgres.
## Features
-- Lightweight - No background worker or external dependencies, just Postgres functions packaged in an extension
+- Lightweight - No background worker or external dependencies, only Postgres functions packaged in an extension
- "exactly once" delivery of messages to a consumer within a visibility timeout
- API parity with AWS SQS and RSMQ
- Messages stay in the queue until explicitly removed
diff --git a/apps/docs/content/guides/realtime.mdx b/apps/docs/content/guides/realtime.mdx
index cd79fccadff..8352d90659b 100644
--- a/apps/docs/content/guides/realtime.mdx
+++ b/apps/docs/content/guides/realtime.mdx
@@ -20,60 +20,8 @@ Supabase provides a globally distributed [Realtime](https://github.com/supabase/
- **Multiplayer games** - Synchronized game state and player interactions
- **Social features** - Live notifications, reactions, and user activity feeds
-Check the [Getting Started](/docs/guides/realtime/getting_started) guide to get started.
+
-## Examples
+
-
-
-
-
- Showcase application displaying cursor movements and chat messages using Broadcast.
-
-
-
-
-
-
- Supabase UI chat component using Broadcast to send message between users.
-
-
-
-
-
-
- Supabase UI avatar stack component using Presence to track connected users.
-
-
-
-
-
-
- Supabase UI realtime cursor component using Broadcast to share users' cursors to build
- collaborative applications.
-
-
-
-
-
-## Resources
-
-Find the source code and documentation in the Supabase GitHub repository.
-
-
-
-
- View the source code.
-
-
-
-
-
- Read more about Supabase Realtime.
-
-
-
-
+
diff --git a/apps/docs/content/guides/realtime/architecture.mdx b/apps/docs/content/guides/realtime/architecture.mdx
index cbf6b59737f..0d0b0aee9b6 100644
--- a/apps/docs/content/guides/realtime/architecture.mdx
+++ b/apps/docs/content/guides/realtime/architecture.mdx
@@ -44,7 +44,7 @@ Broadcast lets you send a message from any connected client to a Channel. Any ot
This works globally. A client connected to a Realtime node in the United States can send a message to another client connected to a node in Singapore. Connect two clients to the same Realtime Channel and they'll all receive the same messages.
-Broadcast is useful for getting messages to users in the same location very quickly. If a group of clients are connected to a node in Singapore, the message only needs to go to that Realtime node in Singapore and back down. If users are close to a Realtime node they'll get Broadcast messages in the time it takes to ping the cluster.
+Broadcast is useful for getting messages to users in the same location rapidly. If a group of clients are connected to a node in Singapore, the message only needs to go to that Realtime node in Singapore and back down. If users are close to a Realtime node they'll get Broadcast messages in the time it takes to ping the cluster.
Thanks to the Realtime cluster, you (an amazing Supabase user) don't have to think about which regions your clients are connected to.
diff --git a/apps/docs/content/guides/realtime/postgres-changes.mdx b/apps/docs/content/guides/realtime/postgres-changes.mdx
index fc5673ed61b..d2ee8773622 100644
--- a/apps/docs/content/guides/realtime/postgres-changes.mdx
+++ b/apps/docs/content/guides/realtime/postgres-changes.mdx
@@ -320,7 +320,9 @@ changes = supabase.channel('schema-db-changes').on_postgres_changes(
The channel name can be any string except 'realtime'.
-### Listening to `INSERT` events
+### Listening to specific events
+
+Use the `event` parameter to listen only to a specific database event. `event` can be `INSERT`, `UPDATE`, `DELETE`, or `*` to listen to all changes.
-Use the `event` parameter to listen only to database `INSERT`s:
+
+
```js
const changes = supabase
@@ -339,7 +348,7 @@ const changes = supabase
.on(
'postgres_changes',
{
- event: 'INSERT', // Listen only to INSERTs
+ event: 'INSERT',
schema: 'public',
},
(payload) => console.log(payload)
@@ -347,12 +356,75 @@ const changes = supabase
.subscribe()
```
+
+
+
+```js
+const changes = supabase
+ .channel('schema-db-changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'UPDATE',
+ schema: 'public',
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+```js
+const changes = supabase
+ .channel('schema-db-changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'DELETE',
+ schema: 'public',
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+```js
+const changes = supabase
+ .channel('schema-db-changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: '*',
+ schema: 'public',
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
<$Show if="sdk:dart">
+
+
+
```dart
-final changes = supabase
+supabase
.channel('schema-db-changes')
.onPostgresChanges(
event: PostgresChangeEvent.insert,
@@ -361,12 +433,63 @@ final changes = supabase
.subscribe();
```
+
+
+
+```dart
+supabase
+ .channel('schema-db-changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.update,
+ schema: 'public',
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+
+
+```dart
+supabase
+ .channel('schema-db-changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.delete,
+ schema: 'public',
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+
+
+```dart
+supabase
+ .channel('schema-db-changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.all,
+ schema: 'public',
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+
+
$Show>
<$Show if="sdk:swift">
-Use `InsertAction.self` as type to listen only to database `INSERT`s:
+Pass the action type to select the event.
+
+
+
```swift
let myChannel = await supabase.channel("schema-db-changes")
@@ -380,12 +503,74 @@ for await change in changes {
}
```
+
+
+
+```swift
+let myChannel = await supabase.channel("schema-db-changes")
+
+let changes = await myChannel.postgresChange(UpdateAction.self, schema: "public")
+
+await myChannel.subscribe()
+
+for await change in changes {
+ print(change.record)
+}
+```
+
+
+
+
+```swift
+let myChannel = await supabase.channel("schema-db-changes")
+
+let changes = await myChannel.postgresChange(DeleteAction.self, schema: "public")
+
+await myChannel.subscribe()
+
+for await change in changes {
+ print(change.oldRecord)
+}
+```
+
+
+
+
+```swift
+let myChannel = await supabase.channel("schema-db-changes")
+
+let changes = await myChannel.postgresChange(AnyAction.self, schema: "public")
+
+await myChannel.subscribe()
+
+for await change in changes {
+ switch change {
+ case .insert(let action): print(action.record)
+ case .update(let action): print(action.record)
+ case .delete(let action): print(action.oldRecord)
+ case .select(let action): print(action.record)
+ }
+}
+```
+
+
+
+
$Show>
<$Show if="sdk:kotlin">
-Use `PostgresAction.Insert` as type to listen only to database `INSERT`s:
+Pass the action type to select the event.
+
+
+
```kotlin
val myChannel = supabase.channel("db-changes")
@@ -402,91 +587,7 @@ myChannel.subscribe()
```
-$Show>
-<$Show if="sdk:python">
-
-
-```python
-changes = supabase.channel('schema-db-changes').on_postgres_changes(
- "INSERT", # Listen only to INSERTs
- schema="public",
- callback=lambda payload: print(payload)
-)
-.subscribe()
-```
-
-
-$Show>
-
-
-The channel name can be any string except 'realtime'.
-
-### Listening to `UPDATE` events
-
-
-
-
-Use the `event` parameter to listen only to database `UPDATE`s:
-
-```js
-const changes = supabase
- .channel('schema-db-changes')
- .on(
- 'postgres_changes',
- {
- event: 'UPDATE', // Listen only to UPDATEs
- schema: 'public',
- },
- (payload) => console.log(payload)
- )
- .subscribe()
-```
-
-
-<$Show if="sdk:dart">
-
-
-```dart
-supabase
- .channel('schema-db-changes')
- .onPostgresChanges(
- event: PostgresChangeEvent.update, // Listen only to UPDATEs
- schema: 'public',
- callback: (payload) => print(payload))
- .subscribe();
-```
-
-
-$Show>
-<$Show if="sdk:swift">
-
-
-Use `UpdateAction.self` as type to listen only to database `UPDATE`s:
-
-```swift
-let myChannel = await supabase.channel("schema-db-changes")
-
-let changes = await myChannel.postgresChange(UpdateAction.self, schema: "public")
-
-await myChannel.subscribe()
-
-for await change in changes {
- print(change.oldRecord, change.record)
-}
-```
-
-
-$Show>
-<$Show if="sdk:kotlin">
-
-
-Use `PostgresAction.Update` as type to listen only to database `UPDATE`s:
+
```kotlin
val myChannel = supabase.channel("db-changes")
@@ -503,91 +604,7 @@ myChannel.subscribe()
```
-$Show>
-<$Show if="sdk:python">
-
-
-```python
-changes = supabase.channel('schema-db-changes').on_postgres_changes(
- "UPDATE", # Listen only to UPDATEs
- schema="public",
- callback=lambda payload: print(payload)
-)
-.subscribe()
-```
-
-
-$Show>
-
-
-The channel name can be any string except 'realtime'.
-
-### Listening to `DELETE` events
-
-
-
-
-Use the `event` parameter to listen only to database `DELETE`s:
-
-```js
-const changes = supabase
- .channel('schema-db-changes')
- .on(
- 'postgres_changes',
- {
- event: 'DELETE', // Listen only to DELETEs
- schema: 'public',
- },
- (payload) => console.log(payload)
- )
- .subscribe()
-```
-
-
-<$Show if="sdk:dart">
-
-
-```dart
-supabase
- .channel('schema-db-changes')
- .onPostgresChanges(
- event: PostgresChangeEvent.delete, // Listen only to DELETEs
- schema: 'public',
- callback: (payload) => print(payload))
- .subscribe();
-```
-
-
-$Show>
-<$Show if="sdk:swift">
-
-
-Use `DeleteAction.self` as type to listen only to database `DELETE`s:
-
-```swift
-let myChannel = await supabase.channel("schema-db-changes")
-
-let changes = await myChannel.postgresChange(DeleteAction.self, schema: "public")
-
-await myChannel.subscribe()
-
-for await change in changes {
- print(change.oldRecord)
-}
-```
-
-
-$Show>
-<$Show if="sdk:kotlin">
-
-
-Use `PostgresAction.Delete` as type to listen only to database `DELETE`s:
+
```kotlin
val myChannel = supabase.channel("db-changes")
@@ -603,20 +620,92 @@ changes
myChannel.subscribe()
```
+
+
+
+```kotlin
+val myChannel = supabase.channel("db-changes")
+
+val changes = myChannel.postgresChangeFlow(schema = "public")
+
+changes
+ .onEach {
+ when (it) { //You can also check for , etc.. manually
+ is HasRecord -> println(it.record)
+ is HasOldRecord -> println(it.oldRecord)
+ else -> println(it)
+ }
+ }
+ .launchIn(yourCoroutineScope)
+
+myChannel.subscribe()
+```
+
+
+
+
$Show>
<$Show if="sdk:python">
+
+
+
```python
changes = supabase.channel('schema-db-changes').on_postgres_changes(
- "DELETE", # Listen only to DELETEs
+ "INSERT",
schema="public",
callback=lambda payload: print(payload)
)
.subscribe()
```
+
+
+
+```python
+changes = supabase.channel('schema-db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+
+
+```python
+changes = supabase.channel('schema-db-changes').on_postgres_changes(
+ "DELETE",
+ schema="public",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+
+
+```python
+changes = supabase.channel('schema-db-changes').on_postgres_changes(
+ "*",
+ schema="public",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+
+
$Show>
@@ -955,7 +1044,40 @@ changes = supabase.channel('db-changes').on_postgres_changes(
## Available filters
-Realtime offers filters so you can specify the data your client receives at a more granular level.
+Realtime offers filters so you can specify the data your client receives at a more granular level. A filter is a `column=operator.value` expression (for example `id=eq.1` or `title=like.%foo%`) that Realtime evaluates on the server, so filtered-out events never leave the database.
+
+The following operators are available:
+
+| Operator | Matches when the column… | Example |
+| ------------------ | ---------------------------------------------------- | ------------------------- |
+| `eq` | equals the value | `id=eq.1` |
+| `neq` | does not equal the value | `status=neq.done` |
+| `lt` / `lte` | is less than / less than or equal to | `age=lt.65` |
+| `gt` / `gte` | is greater than / greater than or equal to | `quantity=gte.10` |
+| `in` | is one of a list (max 100 values) | `name=in.(red,blue)` |
+| `like` / `ilike` | matches a pattern (case-sensitive / insensitive) | `title=like.%foo%` |
+| `match` / `imatch` | matches a POSIX regex (case-sensitive / insensitive) | `slug=match.^post-` |
+| `is` | `IS null` / `true` / `false` / `unknown` | `deleted_at=is.null` |
+| `isdistinct` | is distinct from the value (NULL-safe `!=`) | `state=isdistinct.active` |
+
+You can also [negate any operator](#negating-a-filter-not) with `not.` and [combine multiple conditions](#combining-filters-with-and) with commas (applied as an `AND`).
+
+<$Show if="sdk:js">
+
+
+
+In JavaScript you can pass a raw filter string, or build one with the type-safe `postgresChangesFilter()` helper, which handles operator names, negation, `AND` composition, and escaping for you:
+
+```js
+import { postgresChangesFilter } from '@supabase/supabase-js'
+
+// → 'quantity=gte.10,status=eq.open'
+const filter = postgresChangesFilter().gte('quantity', 10).eq('status', 'open')
+```
+
+
+
+$Show>
### Equal to (`eq`)
@@ -970,6 +1092,34 @@ To listen to changes when a column's value in a table equals a client-specified
>
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'UPDATE',
+ schema: 'public',
+ table: 'messages',
+ filter: postgresChangesFilter().eq('body', 'hey'),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
```js
const channel = supabase
.channel('changes')
@@ -986,6 +1136,9 @@ const channel = supabase
.subscribe()
```
+
+
+
<$Show if="sdk:dart">
@@ -1085,6 +1238,34 @@ To listen to changes when a column's value in a table does not equal a client-sp
>
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'messages',
+ filter: postgresChangesFilter().neq('body', 'bye'),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
```js
const channel = supabase
.channel('changes')
@@ -1101,6 +1282,9 @@ const channel = supabase
.subscribe()
```
+
+
+
<$Show if="sdk:dart">
@@ -1201,6 +1385,34 @@ To listen to changes when a column's value in a table is less than a client-spec
>
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'profiles',
+ filter: postgresChangesFilter().lt('age', 65),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
```js
const channel = supabase
.channel('changes')
@@ -1217,6 +1429,9 @@ const channel = supabase
.subscribe()
```
+
+
+
<$Show if="sdk:dart">
@@ -1316,6 +1531,34 @@ To listen to changes when a column's value in a table is less than or equal to a
>
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'UPDATE',
+ schema: 'public',
+ table: 'profiles',
+ filter: postgresChangesFilter().lte('age', 65),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
```js
const channel = supabase
.channel('changes')
@@ -1332,6 +1575,9 @@ const channel = supabase
.subscribe()
```
+
+
+
<$Show if="sdk:dart">
@@ -1431,6 +1677,34 @@ To listen to changes when a column's value in a table is greater than a client-s
>
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'products',
+ filter: postgresChangesFilter().gt('quantity', 10),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
```js
const channel = supabase
.channel('changes')
@@ -1447,6 +1721,9 @@ const channel = supabase
.subscribe()
```
+
+
+
<$Show if="sdk:dart">
@@ -1546,6 +1823,34 @@ To listen to changes when a column's value in a table is greater than or equal t
>
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'products',
+ filter: postgresChangesFilter().gte('quantity', 10),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
```js
const channel = supabase
.channel('changes')
@@ -1562,6 +1867,9 @@ const channel = supabase
.subscribe()
```
+
+
+
<$Show if="sdk:dart">
@@ -1661,6 +1969,15 @@ To listen to changes when a column's value in a table equals any client-specifie
>
+
+
+
```js
const channel = supabase
.channel('changes')
@@ -1670,13 +1987,35 @@ const channel = supabase
event: 'INSERT',
schema: 'public',
table: 'colors',
- filter: 'name=in.(red, blue, yellow)',
+ filter: postgresChangesFilter().in('name', ['red', 'blue', 'yellow']),
},
(payload) => console.log(payload)
)
.subscribe()
```
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'colors',
+ filter: 'name=in.(red,blue,yellow)',
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
<$Show if="sdk:dart">
@@ -1729,7 +2068,7 @@ val myChannel = supabase.channel("db-changes")
val changes = myChannel.postgresChangeFlow(schema = "public") {
table = "products"
- filter = "name=in.(red, blue, yellow)"
+ filter = "name=in.(red,blue,yellow)"
}
changes
@@ -1751,7 +2090,7 @@ changes = supabase.channel('db-changes').on_postgres_changes(
"UPDATE",
schema="public",
table="products",
- filter="name=in.(red, blue, yellow)",
+ filter="name=in.(red,blue,yellow)",
callback=lambda payload: print(payload)
)
.subscribe()
@@ -1763,6 +2102,970 @@ changes = supabase.channel('db-changes').on_postgres_changes(
This filter uses Postgres's `= ANY`. Realtime allows a maximum of 100 values for this filter.
+### Pattern matching (`like`, `ilike`)
+
+To listen to changes when a text column matches a pattern, use `like` (case-sensitive) or `ilike` (case-insensitive). Use `%` to match any sequence of characters and `_` to match a single character.
+
+
+
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'articles',
+ // matches "Breaking News", "BREAKING", ...
+ filter: postgresChangesFilter().ilike('title', '%breaking%'),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'articles',
+ filter: 'title=ilike.%breaking%', // matches "Breaking News", "BREAKING", ...
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+
+<$Show if="sdk:dart">
+
+
+```dart
+supabase
+ .channel('changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.insert,
+ schema: 'public',
+ table: 'articles',
+ filter: PostgresChangeFilter(
+ type: PostgresChangeFilterType.ilike,
+ column: 'title',
+ value: '%breaking%',
+ ),
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+$Show>
+<$Show if="sdk:swift">
+
+
+```swift
+let myChannel = await supabase.channel("db-changes")
+
+let changes = await myChannel.postgresChange(
+ InsertAction.self,
+ schema: "public",
+ table: "articles",
+ filter: .ilike("title", value: "%breaking%")
+)
+
+await myChannel.subscribe()
+
+for await change in changes {
+ print(change.record)
+}
+```
+
+
+$Show>
+<$Show if="sdk:kotlin">
+
+
+```kotlin
+val myChannel = supabase.channel("db-changes")
+
+val changes = myChannel.postgresChangeFlow(schema = "public") {
+ table = "articles"
+ filter = "title=ilike.%breaking%"
+}
+
+changes
+ .onEach {
+ println(it.record)
+ }
+ .launchIn(yourCoroutineScope)
+
+myChannel.subscribe()
+```
+
+
+$Show>
+<$Show if="sdk:python">
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "INSERT",
+ schema="public",
+ table="articles",
+ filter="title=ilike.%breaking%",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+$Show>
+
+
+`like` uses Postgres's `LIKE` and `ilike` uses `ILIKE`. Both require a text-compatible column. The examples above use `ilike`; swap in `like` for case-sensitive matching—usage is otherwise identical.
+
+### Regular expression matching (`match`, `imatch`)
+
+To listen to changes when a text column matches a POSIX regular expression, use `match` (case-sensitive) or `imatch` (case-insensitive).
+
+
+
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'posts',
+ // matches "post-1", "post-42", ...
+ filter: postgresChangesFilter().match('slug', '^post-\\d+$'),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'posts',
+ filter: 'slug=match.^post-\\d+$', // matches "post-1", "post-42", ...
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+
+<$Show if="sdk:dart">
+
+
+```dart
+supabase
+ .channel('changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.insert,
+ schema: 'public',
+ table: 'posts',
+ filter: PostgresChangeFilter(
+ type: PostgresChangeFilterType.match,
+ column: 'slug',
+ value: r'^post-\d+$',
+ ),
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+$Show>
+<$Show if="sdk:swift">
+
+
+```swift
+let myChannel = await supabase.channel("db-changes")
+
+let changes = await myChannel.postgresChange(
+ InsertAction.self,
+ schema: "public",
+ table: "posts",
+ filter: .match("slug", value: "^post-\\d+$")
+)
+
+await myChannel.subscribe()
+
+for await change in changes {
+ print(change.record)
+}
+```
+
+
+$Show>
+<$Show if="sdk:kotlin">
+
+
+```kotlin
+val myChannel = supabase.channel("db-changes")
+
+val changes = myChannel.postgresChangeFlow(schema = "public") {
+ table = "posts"
+ filter = "slug=match.^post-\\d+$"
+}
+
+changes
+ .onEach {
+ println(it.record)
+ }
+ .launchIn(yourCoroutineScope)
+
+myChannel.subscribe()
+```
+
+
+$Show>
+<$Show if="sdk:python">
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "INSERT",
+ schema="public",
+ table="posts",
+ filter="slug=match.^post-\\d+$",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+$Show>
+
+
+`match` uses Postgres's `~` operator and `imatch` uses `~*`. Both require a text-compatible column, and the pattern is validated when you subscribe. The examples above use `match`; swap in `imatch` for case-insensitive matching—usage is otherwise identical.
+
+### Null and boolean checks (`is`)
+
+To listen to changes when a column `IS` `null`, `true`, `false`, or `unknown`, use `is`. `is.null` works on any column type; `is.true`, `is.false`, and `is.unknown` require a boolean column.
+
+
+
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'UPDATE',
+ schema: 'public',
+ table: 'todos',
+ // only rows that are not yet completed
+ filter: postgresChangesFilter().is('completed_at', null),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'UPDATE',
+ schema: 'public',
+ table: 'todos',
+ filter: 'completed_at=is.null', // only rows that are not yet completed
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+
+<$Show if="sdk:dart">
+
+
+```dart
+supabase
+ .channel('changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.update,
+ schema: 'public',
+ table: 'todos',
+ filter: PostgresChangeFilter(
+ type: PostgresChangeFilterType.isFilter,
+ column: 'completed_at',
+ value: null,
+ ),
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+$Show>
+<$Show if="sdk:swift">
+
+
+```swift
+let myChannel = await supabase.channel("db-changes")
+
+let changes = await myChannel.postgresChange(
+ UpdateAction.self,
+ schema: "public",
+ table: "todos",
+ filter: .is("completed_at", value: .null)
+)
+
+await myChannel.subscribe()
+
+for await change in changes {
+ print(change.record)
+}
+```
+
+
+$Show>
+<$Show if="sdk:kotlin">
+
+
+```kotlin
+val myChannel = supabase.channel("db-changes")
+
+val changes = myChannel.postgresChangeFlow(schema = "public") {
+ table = "todos"
+ filter = "completed_at=is.null"
+}
+
+changes
+ .onEach {
+ println(it.record)
+ }
+ .launchIn(yourCoroutineScope)
+
+myChannel.subscribe()
+```
+
+
+$Show>
+<$Show if="sdk:python">
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="todos",
+ filter="completed_at=is.null",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+$Show>
+
+
+This filter uses Postgres's `IS` operator.
+
+### Distinct from (`isdistinct`)
+
+`isdistinct` is a NULL-safe inequality (`IS DISTINCT FROM`). Unlike `neq`, it treats `null` as a comparable value, so a `null` column is considered distinct from a non-null value.
+
+
+
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'UPDATE',
+ schema: 'public',
+ table: 'orders',
+ // includes rows where status is null
+ filter: postgresChangesFilter().isDistinct('status', 'shipped'),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'UPDATE',
+ schema: 'public',
+ table: 'orders',
+ filter: 'status=isdistinct.shipped', // includes rows where status is null
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+
+<$Show if="sdk:dart">
+
+
+```dart
+supabase
+ .channel('changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.update,
+ schema: 'public',
+ table: 'orders',
+ filter: PostgresChangeFilter(
+ type: PostgresChangeFilterType.isDistinct,
+ column: 'status',
+ value: 'shipped',
+ ),
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+$Show>
+<$Show if="sdk:swift">
+
+
+```swift
+let myChannel = await supabase.channel("db-changes")
+
+let changes = await myChannel.postgresChange(
+ UpdateAction.self,
+ schema: "public",
+ table: "orders",
+ filter: .isDistinct("status", value: "shipped")
+)
+
+await myChannel.subscribe()
+
+for await change in changes {
+ print(change.record)
+}
+```
+
+
+$Show>
+<$Show if="sdk:kotlin">
+
+
+```kotlin
+val myChannel = supabase.channel("db-changes")
+
+val changes = myChannel.postgresChangeFlow(schema = "public") {
+ table = "orders"
+ filter = "status=isdistinct.shipped"
+}
+
+changes
+ .onEach {
+ println(it.record)
+ }
+ .launchIn(yourCoroutineScope)
+
+myChannel.subscribe()
+```
+
+
+$Show>
+<$Show if="sdk:python">
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "UPDATE",
+ schema="public",
+ table="orders",
+ filter="status=isdistinct.shipped",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+$Show>
+
+
+### Negating a filter (`not`)
+
+Prefix any operator with `not.` to invert it — for example `not.in`, `not.is`, or `not.like`.
+
+
+
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: '*',
+ schema: 'public',
+ table: 'posts',
+ // anything except drafts and archived
+ filter: postgresChangesFilter().not('status', 'in', ['draft', 'archived']),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: '*',
+ schema: 'public',
+ table: 'posts',
+ filter: 'status=not.in.(draft,archived)', // anything except drafts and archived
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+
+<$Show if="sdk:dart">
+
+
+Set `negate: true` on a `PostgresChangeFilter` to apply the `not.` prefix.
+
+```dart
+supabase
+ .channel('changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.all,
+ schema: 'public',
+ table: 'posts',
+ filter: PostgresChangeFilter(
+ type: PostgresChangeFilterType.inFilter,
+ column: 'status',
+ value: ['draft', 'archived'],
+ negate: true,
+ ),
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+$Show>
+<$Show if="sdk:swift">
+
+
+Wrap any single-condition filter in `.not(...)`.
+
+```swift
+let myChannel = await supabase.channel("db-changes")
+
+let changes = await myChannel.postgresChange(
+ AnyAction.self,
+ schema: "public",
+ table: "posts",
+ filter: .not(.in("status", values: ["draft", "archived"]))
+)
+
+await myChannel.subscribe()
+```
+
+
+$Show>
+<$Show if="sdk:kotlin">
+
+
+```kotlin
+val myChannel = supabase.channel("db-changes")
+
+val changes = myChannel.postgresChangeFlow(schema = "public") {
+ table = "posts"
+ filter = "status=not.in.(draft,archived)"
+}
+
+changes
+ .onEach {
+ println(it.record)
+ }
+ .launchIn(yourCoroutineScope)
+
+myChannel.subscribe()
+```
+
+
+$Show>
+<$Show if="sdk:python">
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "*",
+ schema="public",
+ table="posts",
+ filter="status=not.in.(draft,archived)",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+$Show>
+
+
+### Combining filters with `AND`
+
+Combine multiple conditions by separating them with commas. All conditions must match (logical `AND`). You can only combine conditions with `AND` — `OR` is not supported.
+
+
+
+
+
+
+
+The builder composes conditions and escapes reserved characters for you.
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'orders',
+ // amount > 100 AND status = "open"
+ filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'),
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: 'INSERT',
+ schema: 'public',
+ table: 'orders',
+ filter: 'amount=gt.100,status=eq.open', // amount > 100 AND status = "open"
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+
+
+
+<$Show if="sdk:dart">
+
+
+Pass a list of filters to `filters` to combine them with `AND`.
+
+```dart
+supabase
+ .channel('changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.insert,
+ schema: 'public',
+ table: 'orders',
+ filters: [
+ PostgresChangeFilter(
+ type: PostgresChangeFilterType.gt,
+ column: 'amount',
+ value: 100,
+ ),
+ PostgresChangeFilter(
+ type: PostgresChangeFilterType.eq,
+ column: 'status',
+ value: 'open',
+ ),
+ ],
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+$Show>
+<$Show if="sdk:swift">
+
+
+Use `.and([...])` to combine multiple conditions.
+
+```swift
+let myChannel = await supabase.channel("db-changes")
+
+let changes = await myChannel.postgresChange(
+ InsertAction.self,
+ schema: "public",
+ table: "orders",
+ filter: .and([
+ .gt("amount", value: 100),
+ .eq("status", value: "open"),
+ ])
+)
+
+await myChannel.subscribe()
+```
+
+
+$Show>
+<$Show if="sdk:kotlin">
+
+
+```kotlin
+val myChannel = supabase.channel("db-changes")
+
+val changes = myChannel.postgresChangeFlow(schema = "public") {
+ table = "orders"
+ filter = "amount=gt.100,status=eq.open"
+}
+
+changes
+ .onEach {
+ println(it.record)
+ }
+ .launchIn(yourCoroutineScope)
+
+myChannel.subscribe()
+```
+
+
+$Show>
+<$Show if="sdk:python">
+
+
+```python
+changes = supabase.channel('db-changes').on_postgres_changes(
+ "INSERT",
+ schema="public",
+ table="orders",
+ filter="amount=gt.100,status=eq.open",
+ callback=lambda payload: print(payload)
+)
+.subscribe()
+```
+
+
+$Show>
+
+
+
+
+Values that contain reserved characters (`,`, `(`, `)`, `"`, or `\`) must be double-quoted PostgREST-style so the server doesn't read them as condition or list boundaries — for example `name=eq."Doe, Jane"`. The `postgresChangesFilter()` builder (JavaScript), `PostgresChangeFilter` (Dart), and `RealtimePostgresFilter` (Swift) apply this quoting for you.
+
+
+
+## Selecting specific columns
+
+By default each change event contains the full row. Use `select` to receive only a subset of columns instead. This reduces payload size and the data transferred per event, which is especially useful for tables with large `bytea`, `jsonb`, or `text` columns.
+
+The listed columns must be selectable by the subscribing role, and the table's primary key is always included so you can identify the row. `select` requires an explicit `schema` and `table` — it's not supported on wildcard subscriptions.
+
+
+
+
+```js
+const channel = supabase
+ .channel('changes')
+ .on(
+ 'postgres_changes',
+ {
+ event: '*',
+ schema: 'public',
+ table: 'profiles',
+ select: ['id', 'username'], // payload.new only contains { id, username }
+ },
+ (payload) => console.log(payload)
+ )
+ .subscribe()
+```
+
+
+<$Show if="sdk:dart">
+
+
+```dart
+supabase
+ .channel('changes')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.all,
+ schema: 'public',
+ table: 'profiles',
+ select: ['id', 'username'],
+ callback: (payload) => print(payload))
+ .subscribe();
+```
+
+
+$Show>
+<$Show if="sdk:swift">
+
+
+```swift
+let myChannel = await supabase.channel("db-changes")
+
+let changes = await myChannel.postgresChange(
+ AnyAction.self,
+ schema: "public",
+ table: "profiles",
+ select: ["id", "username"]
+)
+
+await myChannel.subscribe()
+```
+
+
+$Show>
+
+
## Receiving `old` records
By default, only `new` record changes are sent but if you want to receive the `old` record (previous values) whenever you `UPDATE` or `DELETE` a record, you can set the `replica identity` of your table to `full`:
@@ -1875,7 +3178,7 @@ let changes = await myChannel.postgresChange(
UpdateAction.self,
schema: "public",
table: "products",
- filter: "name=in.(red, blue, yellow)"
+ filter: "name=in.(red,blue,yellow)"
)
await myChannel.subscribe()
@@ -1900,7 +3203,7 @@ val myChannel = supabase.channel("db-changes")
val changes = myChannel.postgresChangeFlow(schema = "public") {
table = "products"
- filter = "name=in.(red, blue, yellow)"
+ filter = "name=in.(red,blue,yellow)"
}
changes
@@ -1924,7 +3227,7 @@ changes = supabase.channel('db-changes').on_postgres_changes(
"UPDATE",
schema="public",
table="products",
- filter="name=in.(red, blue, yellow)",
+ filter="name=in.(red,blue,yellow)",
callback=lambda payload: print(payload)
)
.subscribe()
@@ -1934,92 +3237,29 @@ changes = supabase.channel('db-changes').on_postgres_changes(
$Show>
-### Refreshed tokens
-
-You will need to refresh tokens on your own, but once generated, you can pass them to Realtime.
-
-
-
-
-For example, if you're using the `supabase-js` `v2` client then you can pass your token like this:
-
-```js
-// Client setup
-
-supabase.realtime.setAuth('fresh-token')
-```
-
-
-<$Show if="sdk:dart">
-
-
-```dart
-supabase.realtime.setAuth('fresh-token');
-```
-
-
-$Show>
-<$Show if="sdk:swift">
-
-
-```swift
-await supabase.realtime.setAuth("fresh-token")
-```
-
-
-$Show>
-<$Show if="sdk:kotlin">
-
-
-In Kotlin, you have to update the token manually per channel:
-
-```kotlin
-myChannel.updateAuth("fresh-token")
-```
-
-
-$Show>
-<$Show if="sdk:python">
-
-
-```python
-supabase.realtime.set_auth('fresh-token')
-```
-
-
-$Show>
-
-
## Limitations
### Delete events are not filterable
You can't filter Delete events when tracking Postgres Changes. This limitation is due to the way changes are pulled from Postgres.
-### Spaces in table names
+## Scaling Postgres Changes
-Realtime currently does not work when table names contain spaces.
+Postgres Changes authorizes every event against each subscriber. When you make a single change to a table with 100 subscribed users, Realtime performs 100 authorization checks — one per user — so throughput scales with the number of subscribers, not the write rate. Changes are also processed on a single thread to preserve their order, which means larger compute add-ons don't meaningfully increase Postgres Changes throughput.
-### Database instance and realtime performance
+For most applications this is plenty. To get the best performance:
-Realtime systems usually require forethought because of their scaling dynamics. For the `Postgres Changes` feature, every change event must be checked to see if the subscribed user has access. For instance, if you have 100 users subscribed to a table where you make a single insert, it will then trigger 100 "reads": one for each user.
+- Use [filters](#available-filters) and [column selection](#selecting-specific-columns) to send each client only the events and columns it needs.
+- Keep authorization cheap by writing , indexed [RLS policies](/docs/guides/database/postgres/row-level-security).
-There can be a database bottleneck which limits message throughput. If your database cannot authorize the changes rapidly enough, the changes will be delayed until you receive a timeout.
-
-Database changes are processed on a single thread to maintain the change order. That means compute upgrades don't have a large effect on the performance of Postgres change subscriptions. You can estimate the expected maximum throughput for your database below.
-
-If you are using Postgres Changes at scale, you should consider using separate "public" table without RLS and filters. Alternatively, you can use Realtime server-side only and then re-stream the changes to your clients using a Realtime Broadcast.
-
-Enter your database settings to estimate the maximum throughput for your instance:
+Use the estimator below to gauge the maximum throughput for your instance, and run your own benchmarks to confirm it fits your use case:
-Don't forget to run your own benchmarks to make sure that the performance is acceptable for your use case.
+
-We are making many improvements to Realtime's Postgres Changes. If you are uncertain about the performance of your use case, reach out using [Support Form](/dashboard/support/new) and we will be happy to help you. We have a team of engineers that can advise you on the best solution for your use-case.
+If you expect more than ~3,000 concurrent subscribers on the same changes, use [Broadcast to stream database changes](/docs/guides/realtime/subscribing-to-database-changes#using-broadcast) instead. Broadcast sends each change once and fans it out to all subscribers, so it scales to far higher connection counts than per-subscriber authorization allows.
+
+
+
+If you're unsure which approach fits your use case, reach out through the [Support Form](/dashboard/support/new) — our engineers are happy to help you find the best solution.
diff --git a/apps/docs/content/guides/realtime/presence.mdx b/apps/docs/content/guides/realtime/presence.mdx
index 5618cce8863..2cecc485a81 100644
--- a/apps/docs/content/guides/realtime/presence.mdx
+++ b/apps/docs/content/guides/realtime/presence.mdx
@@ -30,7 +30,7 @@ For high-frequency or fire-and-forget updates, use [Broadcast](/docs/guides/real
-During a `sync` event, you may receive `join` and `leave` events simultaneously, even though no users are actually joining or leaving. This is expected behavior—Presence reconciles its local state with the server state, which can trigger these events as part of the synchronization process. This reflects state reconciliation, not real user movement.
+During a `sync` event, you may receive `join` and `leave` events simultaneously, even though no users are joining or leaving. This is expected behavior—Presence reconciles its local state with the server state, which can trigger these events as part of the synchronization process. This reflects state reconciliation, not real user movement.
diff --git a/apps/docs/content/guides/realtime/protocol.mdx b/apps/docs/content/guides/realtime/protocol.mdx
index 05c524e642b..30e6c5c99f2 100644
--- a/apps/docs/content/guides/realtime/protocol.mdx
+++ b/apps/docs/content/guides/realtime/protocol.mdx
@@ -214,7 +214,8 @@ This is the initial message required to join a channel. The client sends this me
"replay" : {
"since": integer,
"limit": integer
- }
+ },
+ "replication_ready": boolean
},
"presence": {
"enabled": boolean,
@@ -225,7 +226,8 @@ This is the initial message required to join a channel. The client sends this me
"event": string,
"schema": string,
"table": string,
- "filter": string
+ "filter": string,
+ "select": string[]
}
]
"private": boolean
@@ -242,6 +244,7 @@ This is the initial message required to join a channel. The client sends this me
- `replay`: Configuration options for broadcast replay (Optional)
- `since`: Replay messages since a specific timestamp in milliseconds
- `limit`: Limit the number of replayed messages (Optional)
+ - `replication_ready`: When `true`, the server emits a `system` event once the Postgres replication connection backing this channel is established and ready to stream changes (Optional). See the [system](#system) event for the payload shape.
- `presence`: Configuration options for presence tracking
- `enabled`: Whether presence tracking is enabled for this channel
- `key`: Key to be used for presence tracking, if not specified or empty, a UUID will be generated and used
@@ -249,7 +252,8 @@ This is the initial message required to join a channel. The client sends this me
- `event`: Database change event to listen to, accepts `INSERT`, `UPDATE`, `DELETE`, or `*` to listen to all events.
- `schema`: Schema of the table to listen to, accepts `*` wildcard to listen to all schemas
- `table`: Table of the database to listen to, accepts `*` wildcard to listen to all tables
- - `filter`: Filter to be used when pulling changes from database. Read more about filters in the usage docs for [Postgres Changes](/docs/guides/realtime/postgres-changes?queryGroups=language&language=js#filtering-for-specific-changes)
+ - `filter`: Filter to be used when pulling changes from the database. A filter is a `column=operator.value` expression (for example `id=eq.1` or `title=like.%foo%`). Multiple conditions can be combined with commas and are applied as an `AND` (for example `id=gt.0,id=lt.100`). Any operator can be negated with the `not.` prefix (for example `status=not.in.(draft,archived)`). Reserved characters (`,`, `(`, `)`) inside a value must be double-quoted PostgREST-style (for example `name=eq."a,b"`). See the [Postgres Changes subscription errors](#postgres-changes-subscription-errors) for the full list of supported operators, and the usage docs for [Postgres Changes](/docs/guides/realtime/postgres-changes?queryGroups=language&language=js#filtering-for-specific-changes).
+ - `select`: Optional array of column names to restrict the change payload to a subset of columns instead of receiving the full row. Reduces payload size and the data transferred per event. The listed columns must be selectable by the subscribing role. Not supported for wildcard (`*`) schema or table subscriptions — an explicit `schema` and `table` are required.
- `access_token`: Optional access token for authentication, if not provided, the server will use the API key.
Example on protocol version `2.0.0`:
@@ -413,7 +417,7 @@ user-event // User Event
}
```
-The payload encoding is just a hint for the client to know if the payload should be treated as JSON or not.
+The payload encoding is a hint for the client to know if the payload should be treated as JSON or not.
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
@@ -564,7 +568,7 @@ The server sends system messages to inform clients about the status of their Rea
- `message`: A human-readable message describing the status of the subscription.
- `status`: The status of the subscription, can be `ok`, `error`, or `timeout`.
-- `extension`: The extension that sent the message.
+- `extension`: The extension that sent the message. `postgres_changes` for Postgres Changes subscription status, or `system` for connection-level messages such as the replication-ready notification.
- `channel`: The channel to which the message belongs, such as `realtime:room1`.
Example on protocol version `2.0.0`:
@@ -584,6 +588,23 @@ Example on protocol version `2.0.0`:
]
```
+When a channel is joined with `config.broadcast.replication_ready` set to `true`, the server sends a `system` message with `extension: "system"` once the Postgres replication connection backing the channel is ready to stream changes. `status` is `"ok"` with `message: "Replication connection established"` on success, or `"error"` if the connection is not established in time (which also closes the channel — see [Channel-level system errors](#channel-level-system-errors)).
+
+```json
+[
+ "14",
+ null,
+ "realtime:chat-room",
+ "system",
+ {
+ "message": "Replication connection established",
+ "status": "ok",
+ "extension": "system",
+ "channel": "main"
+ }
+]
+```
+
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
#### broadcast (text frame)
@@ -667,7 +688,7 @@ message // User Event
}
```
-The metadata field is JSON encoded. The payload encoding is just a hint for the client to know if the payload should be treated as JSON or not.
+The metadata field is JSON encoded. The payload encoding is a hint for the client to know if the payload should be treated as JSON or not.
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
@@ -715,6 +736,8 @@ The server sends this message when a database change occurs in a subscribed sche
- `old_record`: An object representing the old values before the change, with keys as column names and values as their corresponding values.
- `errors`: Any errors that occurred during the change, if applicable.
+When the subscription was joined with a `select` array (see [phx_join](#phx_join)), `columns`, `record`, and `old_record` are restricted to the selected columns instead of the full row.
+
```json
[
null,
@@ -926,15 +949,16 @@ One exception: the `UnknownErrorOnChannel` code arrives as the bare human-readab
`extension: "system"`, `status: "error"`. Match on the `message` field content — there is no machine-readable code field. Every channel-level system error is immediately followed by `phx_close`; the channel is closed. Client libraries should expose a way for users to subscribe to `system` events since there is no automatic handling.
-| Message contains | Cause | Recovery |
-| ------------------------------------------------- | -------------------------- | ----------------------------- |
-| `Too many messages per second` | Broadcast/event rate limit | Throttle sends before rejoin |
-| `Too many presence messages per second` | Tenant presence rate limit | Reduce presence frequency |
-| `Client presence rate limit exceeded` | Per-client presence window | Longer cooldown before rejoin |
-| `Track message size exceeded` | Presence payload too large | Shrink payload |
-| `Token has expired` | JWT expired mid-session | Refresh token, rejoin |
-| `Fields \`role\` and \`exp\` are required in JWT` | Claims missing | Fix token issuance |
-| `Server requested disconnect` | Operational disconnect | Reconnect after delay |
+| Message contains | Cause | Recovery |
+| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------- |
+| `Too many messages per second` | Broadcast/event rate limit | Throttle sends before rejoin |
+| `Too many presence messages per second` | Tenant presence rate limit | Reduce presence frequency |
+| `Client presence rate limit exceeded` | Per-client presence window | Longer cooldown before rejoin |
+| `Track message size exceeded` | Presence payload too large | Shrink payload |
+| `Token has expired` | JWT expired mid-session | Refresh token, rejoin |
+| `Fields \`role\` and \`exp\` are required in JWT` | Claims missing | Fix token issuance |
+| `Server requested disconnect` | Operational disconnect | Reconnect after delay |
+| `Replication connection was not established in time` | Replication connection not ready before the deadline (only when `replication_ready` was requested) | Retry with backoff |
### Postgres Changes subscription errors
@@ -948,7 +972,7 @@ One exception: the `UnknownErrorOnChannel` code arrives as the bare human-readab
| Database error during subscription | Yes, every 5–10 s | Surface as degraded state |
| `"Too many database timeouts"` | No | Reduce subscription load; retry later |
-Supported filter operators: `eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `in`.
+Supported filter operators: `eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `in`, `like`, `ilike`, `is`, `match`, `imatch`, `isdistinct`. Any operator can be negated with the `not.` prefix (for example `id=not.eq.5`). Multiple conditions are combined with commas and applied as an `AND` (for example `col1=eq.val,col2=gt.5`).
The `ids` array on incoming `postgres_changes` payloads must match the subscription IDs returned in the `phx_join` reply. A mismatch means inconsistent server/client state — tear down and rejoin.
@@ -962,7 +986,7 @@ When `ack` is `true`, the server replies on error with `response.error` (an atom
{ "status": "error", "response": { "error": "payload_size_exceeded" } }
```
-Note that the JS client (`send()`) resolves to just the string `'error'` and does not expose the specific `error` atom to callers.
+Note that the JS client (`send()`) resolves to the string `'error'` and does not expose the specific `error` atom to callers.
### Presence errors
diff --git a/apps/docs/content/guides/realtime/reports.mdx b/apps/docs/content/guides/realtime/reports.mdx
index b17d012ecf2..23b10880524 100644
--- a/apps/docs/content/guides/realtime/reports.mdx
+++ b/apps/docs/content/guides/realtime/reports.mdx
@@ -157,7 +157,7 @@ height={625}
## Rate of Channel Joins
-The Rate of Channel Joins report helps you monitor how quickly clients are joining Realtime channels over time. This metric is essential for understanding your application's channel subscription patterns and identifying when you're approaching your plan's channel join rate limits.
+The Rate of Channel Joins report helps you monitor how fast clients are joining Realtime channels over time. This metric is essential for understanding your application's channel subscription patterns and identifying when you're approaching your plan's channel join rate limits.
The report displays the rate of channel joins per second, showing how frequently clients subscribe to channels throughout the selected time period. A channel join occurs whenever a client subscribes to a channel topic to receive real-time updates. Each client connection can join multiple channels (up to 100 per connection for most plans), and the join rate measures how many of these subscriptions happen per second across your entire project.
@@ -377,7 +377,7 @@ height={645}
The Response Speed report helps you monitor the average response time for HTTP requests to the Realtime service over time. This metric is essential for understanding API performance, identifying latency issues, and ensuring your real-time features meet performance expectations.
-The report displays the average response time in milliseconds, showing how quickly the Realtime service responds to HTTP requests throughout the selected time period. This includes response times for REST API requests such as broadcast messages, WebSocket upgrade requests, and other HTTP-based interactions. Higher response times can indicate performance bottlenecks, database load issues, or network problems that may impact the real-time responsiveness of your application.
+The report displays the average response time in milliseconds, showing how fast the Realtime service responds to HTTP requests throughout the selected time period. This includes response times for REST API requests such as broadcast messages, WebSocket upgrade requests, and other HTTP-based interactions. Higher response times can indicate performance bottlenecks, database load issues, or network problems that may impact the real-time responsiveness of your application.
@@ -38,7 +38,7 @@ A committed lockfile is the floor, not the ceiling.
- Commit `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock`.
- In CI, install with `npm ci` / `pnpm install --frozen-lockfile` / `yarn install --immutable` / `bun install --frozen-lockfile`. These fail if `package.json` and the lockfile disagree, which is what you want.
-- Caret ranges (`^1.2.3`) in `package.json` are fine **if** you also have a lockfile and the rest of the guidance below — the lockfile is what actually gets installed.
+- Caret ranges (`^1.2.3`) in `package.json` are fine **if** you also have a lockfile and the rest of the guidance below — the lockfile is what gets installed.
**Pin transitive risk with overrides.** If you don't trust a particular transitive dep version, force a known-good version via:
@@ -102,7 +102,7 @@ npmPreapprovedPackages: # opt specific packages out of all package gates
- '@your-org/*'
```
-Versions newer than the gate are excluded from resolution. Yarn's docs also note this guards against the npm registry's 72-hour unpublish window — a package you just installed could vanish, breaking your build, if you don't wait it out.
+Versions newer than the gate are excluded from resolution. Yarn's docs also note this guards against the npm registry's 72-hour unpublish window — a package you recently installed could vanish, breaking your build, if you don't wait it out.
Two related yarn settings worth knowing about while you're in `.yarnrc.yml`:
@@ -141,7 +141,7 @@ Or per-command:
bun add @supabase/supabase-js --minimum-release-age 604800
```
-Bun's age gate only affects new resolutions — existing entries in `bun.lock` are unchanged. It also runs a stability check: if multiple versions were published close together just outside your gate, Bun extends the filter to skip those (likely unstable) versions and picks an older, more mature one. Exact-version requests (`pkg@1.1.1`) respect the gate but bypass the stability extension.
+Bun's age gate only affects new resolutions — existing entries in `bun.lock` are unchanged. It also runs a stability check: if multiple versions were published close together outside your gate, Bun extends the filter to skip those (likely unstable) versions and picks an older, more mature one. Exact-version requests (`pkg@1.1.1`) respect the gate but bypass the stability extension.
For Deno-based Edge Functions, see the [Edge Functions specifics](#edge-functions-specifics) section below.
@@ -230,7 +230,7 @@ Corepack (bundled with modern Node) and `pnpm/action-setup@v6+` both read this f
## Prune unused dependencies
-Every dependency you don't actually need is attack surface you don't actually need. Two cheap habits:
+Every dependency you don't need is attack surface you don't need. Two cheap habits:
- Periodically run `npx depcheck` (or the equivalent for your stack) and remove dependencies that aren't imported anywhere.
- Look at your direct dependencies when a CVE lands. Is the dep doing something you could do in 20 lines yourself? Some of the most-exploited packages are tiny utilities that became transitive footguns.
@@ -267,7 +267,7 @@ Talk to the Supabase Functions team if your security posture depends on a featur
## If you suspect you installed a compromised version
-Move quickly. Order of operations:
+Act promptly. Order of operations:
1. **Treat the install host as potentially compromised.** Anything readable by the user that ran the install — env vars, files, secrets in memory — should be assumed stolen.
2. **Rotate credentials reachable from that host**: cloud provider keys (AWS, GCP, Azure), Kubernetes / Vault tokens, GitHub tokens, npm tokens, SSH keys, and any Supabase service-role keys or anon keys that touched the box.
diff --git a/apps/docs/content/guides/self-hosting/docker.mdx b/apps/docs/content/guides/self-hosting/docker.mdx
index 348cc44558e..d986ed6b3ae 100644
--- a/apps/docs/content/guides/self-hosting/docker.mdx
+++ b/apps/docs/content/guides/self-hosting/docker.mdx
@@ -229,7 +229,7 @@ For a description of all secrets refer to the [related section](#configuring-sec
Review and change URL configuration variables:
- `SUPABASE_PUBLIC_URL`: base URL for accessing Supabase from the Internet (Dashboard, API, Storage, etc.), e.g., `http://example.com:8000`
-- `API_EXTERNAL_URL`: used by the Auth service to configure callback URLs, e.g., `http://example.com:8000`
+- `API_EXTERNAL_URL`: used by the Auth service to configure callback URLs, e.g., `http://example.com:8000/auth/v1`
- `SITE_URL`: default [redirect URL](/docs/guides/auth/redirect-urls) for Auth, e.g., `http://example.com:3000`
@@ -336,7 +336,7 @@ For transaction-mode connections:
psql 'postgres://postgres.[POOLER_TENANT_ID]:[POSTGRES_PASSWORD]@[your-domain]:6543/postgres'
```
-When using `psql` with command-line parameters instead of a connection string to connect to Supavisor, the `-U` parameter should also be `postgres.[POOLER_TENANT_ID]`, and not just `postgres`.
+When using `psql` with command-line parameters instead of a connection string to connect to Supavisor, the `-U` parameter should also be `postgres.[POOLER_TENANT_ID]`.
If you need to configure Postgres to be directly accessible from the Internet, read [Exposing your Postgres database](#exposing-your-postgres-database).
@@ -514,9 +514,10 @@ sh run.sh recreate
The `generate-keys.sh` script sets the following secrets automatically. You can also configure them manually in the `.env` file if needed:
- `SECRET_KEY_BASE`: encryption key for securing Realtime and Supavisor communications. (Must be at least 64 characters; generate with `openssl rand -base64 48`)
+- `REALTIME_DB_ENC_KEY`: encryption key used by Realtime for sensitive fields in the `_realtime` schema. (Must be exactly 16 characters; generate with `openssl rand -hex 8`)
- `VAULT_ENC_KEY`: encryption key used by Supavisor for storing encrypted configuration. (Must be exactly 32 characters; generate with `openssl rand -hex 16`)
- `PG_META_CRYPTO_KEY`: encryption key for securing connection strings used by Studio against postgres-meta. (Must be at least 32 characters; generate with `openssl rand -base64 24`)
-- `LOGFLARE_PUBLIC_ACCESS_TOKEN`: API token for log ingestion and querying. Used by Vector and Studio to send and query logs. (Must be at least 32 characters; generate with `openssl rand -base64 24`)
+- `LOGFLARE_PUBLIC_ACCESS_TOKEN`: API token for log ingestion used by Logflare and Vector. (Must be at least 32 characters; generate with `openssl rand -base64 24`)
- `LOGFLARE_PRIVATE_ACCESS_TOKEN`: API token for Logflare management operations. Used by Studio for administrative tasks. Never expose client-side. (Must be at least 32 characters; generate with `openssl rand -base64 24`)
- `S3_PROTOCOL_ACCESS_KEY_ID`: Access key ID (username-like) for [accessing](/docs/guides/self-hosting/self-hosted-s3) the S3 protocol endpoint in Storage. (Generate with `openssl rand -hex 16`)
- `S3_PROTOCOL_ACCESS_KEY_SECRET`: Secret key (password-like) used with S3_PROTOCOL_ACCESS_KEY_ID. (Generate with `openssl rand -hex 32`)
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-auth-keys.mdx b/apps/docs/content/guides/self-hosting/self-hosted-auth-keys.mdx
index 63552870758..5101014c539 100644
--- a/apps/docs/content/guides/self-hosting/self-hosted-auth-keys.mdx
+++ b/apps/docs/content/guides/self-hosting/self-hosted-auth-keys.mdx
@@ -61,7 +61,7 @@ storage:
-Podman does not support nested variable interpolation (`${A:-${B}}`). If you are using Podman, replace each nested expression with the required variable directly - see the inline comments in `docker-compose.yml` for the exact substitutions.
+Nested variable interpolation (`${A:-${B}}`) requires `podman-compose >= 1.6.0`. Earlier versions (still shipped by some Linux distributions) do not support it - if you are on an older `podman-compose`, either upgrade or replace each nested expression with the required variable directly, see the inline comments in `docker-compose.yml` for the exact substitutions.
@@ -82,14 +82,14 @@ sb_secret_<22-char-random>_<8-char-checksum>
### Verifying the setup
-Test with the new publishable key:
+Test with the new secret key:
```sh
curl http:///rest/v1/ \
-H "apikey: your-supabase-secret-key"
```
-You should receive a valid response from PostgREST. Then verify that the legacy key still works:
+You should receive a valid response from PostgREST. Then verify that the legacy service role key still works:
```sh
curl http:///rest/v1/ \
@@ -98,6 +98,12 @@ curl http:///rest/v1/ \
Both should work and return the same result.
+
+
+`/rest/v1/` is the Open API root and requires admin-level keys (`sb_secret_*` or legacy `SERVICE_ROLE_KEY`). Public keys (`sb_publishable_*` or legacy `ANON_KEY`) return `403` for this endpoint.
+
+
+
You can also verify the public JWKS endpoint:
```sh
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-envoy.mdx b/apps/docs/content/guides/self-hosting/self-hosted-envoy.mdx
index 52bbd483178..592bc395ed2 100644
--- a/apps/docs/content/guides/self-hosting/self-hosted-envoy.mdx
+++ b/apps/docs/content/guides/self-hosting/self-hosted-envoy.mdx
@@ -127,28 +127,29 @@ docker compose -f docker-compose.yml -f docker-compose.envoy.yml restart api-gw
Routes are matched in the order declared. The first matching prefix wins. Protected routes require a valid `apikey` header; open routes pass through without API key validation.
-| Path prefix | Upstream | Path rewrite | Access control | Notes |
-| ----------------------------------------- | --------- | ------------------------ | ----------------- | ---------------------------------------------------------------------- |
-| `/auth/v1/verify` | auth | `/verify` | Open | Email verification |
-| `/auth/v1/callback` | auth | `/callback` | Open | OAuth callback |
-| `/auth/v1/authorize` | auth | `/authorize` | Open | OAuth authorize |
-| `/auth/v1/.well-known/jwks.json` | auth | `/.well-known/jwks.json` | Open | JWKS for third-party verification |
-| `/.well-known/oauth-authorization-server` | auth | - | Open | OAuth 2.0 Authorization Server Metadata (RFC 8414) |
-| `/sso/saml/acs` | auth | - | Open | SAML assertion consumer |
-| `/sso/saml/metadata` | auth | - | Open | SAML metadata |
-| `/functions/v1/` | functions | `/` | Bypass | Edge Functions runtime performs its own JWT verification; 150s timeout |
-| `/storage/v1/` | storage | `/` | Bypass | Storage performs its own authorization |
-| `/auth/v1/` | auth | `/` | API key | Protected Auth endpoints |
-| `/rest/v1/` | rest | `/` | API key | PostgREST |
-| `/graphql/v1` | rest | `/rpc/graphql` | API key | pg_graphql (adds `Content-Profile: graphql_public`) |
-| `/realtime/v1/api/tenants` | realtime | - | Denied | Realtime management API (blocked by default) |
-| `/realtime/v1/api/openapi` | realtime | - | Denied | Realtime OpenAPI spec (blocked by default) |
-| `/realtime/v1/api` | realtime | `/api` | API key | Realtime REST API (broadcast, channels, ping) |
-| `/realtime/v1/` | realtime | `/socket/` | API key | Realtime WebSocket |
-| `/pg/` | meta | `/` | Service role only | postgres-meta - used by Studio for database access |
-| `/api/mcp` | studio | - | Denied | MCP endpoint (blocked by default via RBAC DENY) |
-| `/mcp` | studio | `/api/mcp` | Denied | MCP endpoint (blocked by default via RBAC DENY) |
-| `/` (catch-all) | studio | - | Basic auth | Dashboard; strips inbound `Authorization` header |
+| Path prefix | Upstream | Path rewrite | Access control | Notes |
+| ----------------------------------------- | --------- | ------------------------ | -------------- | ---------------------------------------------------------------------- |
+| `/auth/v1/verify` | auth | `/verify` | Open | Email verification |
+| `/auth/v1/callback` | auth | `/callback` | Open | OAuth callback |
+| `/auth/v1/authorize` | auth | `/authorize` | Open | OAuth authorize |
+| `/auth/v1/.well-known/jwks.json` | auth | `/.well-known/jwks.json` | Open | JWKS for third-party verification |
+| `/.well-known/oauth-authorization-server` | auth | - | Open | OAuth 2.0 Authorization Server Metadata (RFC 8414) |
+| `/auth/v1/sso/saml/acs` | auth | `/sso/saml/acs` | Open | SAML assertion consumer |
+| `/auth/v1/sso/saml/metadata` | auth | `/sso/saml/metadata` | Open | SAML metadata |
+| `/functions/v1/` | functions | `/` | Bypass | Edge Functions runtime performs its own JWT verification; 150s timeout |
+| `/storage/v1/` | storage | `/` | Bypass | Storage performs its own authorization |
+| `/auth/v1/` | auth | `/` | API key | Protected Auth endpoints |
+| `/rest/v1/` | rest | `/` | API key | PostgREST Open API root (requires secret key) |
+| `/rest/v1/*` | rest | `/` | API key | PostgREST data endpoints |
+| `/graphql/v1` | rest | `/rpc/graphql` | API key | pg_graphql (adds `Content-Profile: graphql_public`) |
+| `/realtime/v1/api/tenants` | realtime | - | Denied | Realtime management API (blocked by default) |
+| `/realtime/v1/api/openapi` | realtime | - | Denied | Realtime OpenAPI spec (blocked by default) |
+| `/realtime/v1/api` | realtime | `/api` | API key | Realtime REST API (broadcast, channels, ping) |
+| `/realtime/v1/` | realtime | `/socket/` | API key | Realtime WebSocket |
+| `/pg/` | meta | `/` | API key | postgres-meta / Studio database operations (requires secret key) |
+| `/api/mcp` | studio | - | Denied | MCP endpoint (blocked by default via RBAC DENY) |
+| `/mcp` | studio | `/api/mcp` | Denied | MCP endpoint (blocked by default via RBAC DENY) |
+| `/` (catch-all) | studio | - | Basic auth | Dashboard; strips inbound `Authorization` header |
@@ -170,8 +171,9 @@ The protected routes (`/auth/v1/`, `/rest/v1/`, `/graphql/v1`, `/realtime/v1/api
A Lua filter rejects missing or invalid keys with HTTP `401 Unauthorized`. An RBAC filter then applies finer-grained rules:
-- `/pg/` - only `service_role` keys (`sb_secret_*` or legacy `SERVICE_ROLE_KEY`) are allowed (HTTP `403` otherwise).
-- All other protected routes - any valid configured key is allowed.
+- `/pg/` - only `service_role` keys (`sb_secret_*` or legacy `SERVICE_ROLE_KEY`) are allowed
+- `/rest/v1/` (exact path, OpenAPI schema root) - only `service_role` keys are allowed
+- `/rest/v1/` and other Data API paths remain accessible to all valid keys
### Opaque key translation
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx b/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx
index cfec33dc403..7ab4da3df8b 100644
--- a/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx
+++ b/apps/docs/content/guides/self-hosting/self-hosted-functions.mdx
@@ -199,7 +199,7 @@ For more details, see:
### 400 "missing function name in request"
-The request URL must include the function name after `/functions/v1/`. For example, `/functions/v1/hello` — not just `/functions/v1/`.
+The request URL must include the function name after `/functions/v1/`. For example, `/functions/v1/hello`.
### 500 error on invocation
@@ -230,7 +230,7 @@ docker compose restart functions --no-deps
- Recreate the functions container after changing configuration
- Check that the variable name matches exactly (case-sensitive)
-Use the following command to recreate the container, not just `restart`:
+Use the following command to recreate the container:
```sh
docker compose up -d --force-recreate --no-deps functions
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx b/apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx
index b730eb9e9c1..505a4aa3f44 100644
--- a/apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx
+++ b/apps/docs/content/guides/self-hosting/self-hosted-oauth.mdx
@@ -11,7 +11,7 @@ This guide covers the **server-side configuration** required to enable social lo
You need:
- A working self-hosted Supabase installation. See [Self-Hosting with Docker](/docs/guides/self-hosting/docker).
-- `API_EXTERNAL_URL` set to the publicly reachable URL of your Supabase instance (e.g., `https://`).
+- `API_EXTERNAL_URL` set to the publicly reachable URL of your Supabase instance (e.g., `https:///auth/v1`).
@@ -79,7 +79,7 @@ auth:
GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED}
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_SECRET}
- GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
+ GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/callback
```
@@ -149,7 +149,7 @@ auth:
GOTRUE_EXTERNAL_GOOGLE_ENABLED: ${GOOGLE_ENABLED}
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOTRUE_EXTERNAL_GOOGLE_SECRET: ${GOOGLE_SECRET}
- GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
+ GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: ${API_EXTERNAL_URL}/callback
```
@@ -178,7 +178,7 @@ auth:
GOTRUE_EXTERNAL_GITHUB_ENABLED: ${GITHUB_ENABLED}
GOTRUE_EXTERNAL_GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GOTRUE_EXTERNAL_GITHUB_SECRET: ${GITHUB_SECRET}
- GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
+ GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI: ${API_EXTERNAL_URL}/callback
```
@@ -213,7 +213,7 @@ auth:
GOTRUE_EXTERNAL_AZURE_ENABLED: ${AZURE_ENABLED}
GOTRUE_EXTERNAL_AZURE_CLIENT_ID: ${AZURE_CLIENT_ID}
GOTRUE_EXTERNAL_AZURE_SECRET: ${AZURE_SECRET}
- GOTRUE_EXTERNAL_AZURE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
+ GOTRUE_EXTERNAL_AZURE_REDIRECT_URI: ${API_EXTERNAL_URL}/callback
## Optional: uncomment for tenant-specific Azure login
# GOTRUE_EXTERNAL_AZURE_URL: ${AZURE_URL}
```
@@ -241,7 +241,7 @@ auth:
GOTRUE_EXTERNAL_APPLE_ENABLED: ${APPLE_ENABLED}
GOTRUE_EXTERNAL_APPLE_CLIENT_ID: ${APPLE_CLIENT_ID}
GOTRUE_EXTERNAL_APPLE_SECRET: ${APPLE_SECRET}
- GOTRUE_EXTERNAL_APPLE_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
+ GOTRUE_EXTERNAL_APPLE_REDIRECT_URI: ${API_EXTERNAL_URL}/callback
```
@@ -280,7 +280,7 @@ auth:
GOTRUE_EXTERNAL_KEYCLOAK_ENABLED: ${KEYCLOAK_ENABLED}
GOTRUE_EXTERNAL_KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID}
GOTRUE_EXTERNAL_KEYCLOAK_SECRET: ${KEYCLOAK_SECRET}
- GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: ${API_EXTERNAL_URL}/auth/v1/callback
+ GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI: ${API_EXTERNAL_URL}/callback
GOTRUE_EXTERNAL_KEYCLOAK_URL: ${KEYCLOAK_URL}
```
@@ -404,7 +404,7 @@ Run `docker compose exec auth env | grep GOTRUE_EXTERNAL` to verify the variable
After a successful OAuth login, the Auth service redirects to `SITE_URL` or a URL from `ADDITIONAL_REDIRECT_URLS`. Ensure:
-- `SITE_URL` in `.env` is set to your application's URL
+- `SITE_URL` in `.env` is set to your **application's URL**
- If your app uses a different redirect URL, add it to `ADDITIONAL_REDIRECT_URLS` (comma-separated)
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
@@ -443,7 +443,7 @@ docker compose logs auth
Common causes:
- Missing required environment variable (e.g., `CLIENT_ID` or `SECRET` is empty)
-- Invalid `API_EXTERNAL_URL` (must be a valid URL with protocol)
+- Invalid `API_EXTERNAL_URL` (must be a valid URL - including protocol and ending with `/auth/v1`)
## Environment variable reference
@@ -454,7 +454,7 @@ All OAuth-related environment variables for the `auth` service in `docker-compos
| `GOTRUE_EXTERNAL_*_ENABLED` | Enable the provider (`true`/`false`) | Yes |
| `GOTRUE_EXTERNAL_*_CLIENT_ID` | OAuth client ID from the provider | Yes |
| `GOTRUE_EXTERNAL_*_SECRET` | OAuth client secret from the provider | Yes |
-| `GOTRUE_EXTERNAL_*_REDIRECT_URI` | Callback URL: `${API_EXTERNAL_URL}/auth/v1/callback` | Yes |
+| `GOTRUE_EXTERNAL_*_REDIRECT_URI` | Callback URL: `${API_EXTERNAL_URL}/callback` | Yes |
| `GOTRUE_SITE_URL` | Default redirect URL after authentication (set via `SITE_URL` in `.env`) | Yes |
## Additional resources
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx b/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx
index 6f140fb0565..5e2d658cd42 100644
--- a/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx
+++ b/apps/docs/content/guides/self-hosting/self-hosted-phone-mfa.mdx
@@ -170,7 +170,7 @@ MFA_MAX_ENROLLED_FACTORS=5
## Troubleshooting
-### OTP expires too quickly
+### OTP expires too soon
The default `SMS_OTP_EXP` is 60 seconds. Increase it in `.env`:
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx b/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx
index cefb46b8b26..d94763573a1 100644
--- a/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx
+++ b/apps/docs/content/guides/self-hosting/self-hosted-proxy-https.mdx
@@ -45,10 +45,16 @@ Update the URL configuration in your `.env` file to use your HTTPS domain:
```sh name=.env
SUPABASE_PUBLIC_URL=https://
-API_EXTERNAL_URL=https://
-SITE_URL=https://
+API_EXTERNAL_URL=https:///auth/v1
+SITE_URL=https://
```
+
+
+`` is the URL of your own frontend application (where users land after signing in) - not your Supabase instance. It's often a different domain, and possibly a different service entirely, from `` used above.
+
+
+
For Nginx, change the following to your domain name and a **valid** email address:
```sh name=.env
@@ -167,8 +173,8 @@ Edit your `.env` file to use HTTPS with the Kong HTTPS port:
```sh name=.env
SUPABASE_PUBLIC_URL=https://:8443
-API_EXTERNAL_URL=https://:8443
-SITE_URL=https://:8443
+API_EXTERNAL_URL=https://:8443/auth/v1
+SITE_URL=https://
```
### Step 4: Restart and verify
@@ -205,8 +211,8 @@ If Realtime subscriptions fail to connect:
If OAuth redirects fail with a callback URL error:
-- Verify `API_EXTERNAL_URL` in `.env` is set to your HTTPS URL
-- Verify the callback URL registered with your OAuth provider matches `API_EXTERNAL_URL` followed by `/auth/v1/callback`
+- Verify `API_EXTERNAL_URL` in `.env` is set to your HTTPS URL + `/auth/v1`
+- Verify the callback URL registered with your OAuth provider matches `API_EXTERNAL_URL` followed by `/callback`
- After changing `API_EXTERNAL_URL`, restart all services with `docker compose down && docker compose up -d`
### Mixed content warnings
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-s3.mdx b/apps/docs/content/guides/self-hosting/self-hosted-s3.mdx
index 4ab676e5b57..75ee55e20ad 100644
--- a/apps/docs/content/guides/self-hosting/self-hosted-s3.mdx
+++ b/apps/docs/content/guides/self-hosting/self-hosted-s3.mdx
@@ -137,7 +137,7 @@ For AWS S3, you do not need `GLOBAL_S3_ENDPOINT` or `GLOBAL_S3_FORCE_PATH_STYLE`
### S3-compatible providers
{/* supa-mdx-lint-disable-next-line Rule003Spelling */}
-Use the same configuration as MinIO, but point to your provider's endpoint, e.g.:
+Use the same configuration as MinIO, replacing the endpoint, bucket name, region, and AWS credentials with the values provided by your S3-compatible provider, for example:
```yaml name=docker-compose.yml
storage:
@@ -146,6 +146,10 @@ storage:
STORAGE_BACKEND: s3
GLOBAL_S3_BUCKET: your-bucket-name
GLOBAL_S3_ENDPOINT: https://your-account-id.r2.cloudflarestorage.com
+ GLOBAL_S3_FORCE_PATH_STYLE: 'true'
+ AWS_ACCESS_KEY_ID: your-access-key-id
+ AWS_SECRET_ACCESS_KEY: your-secret-access-key
+ REGION: your-region
```
## Verify
@@ -207,7 +211,7 @@ Setting a bucket to "Public" only allows unauthenticated **downloads**. Uploads
### Upload URLs point to localhost
-If uploads from a browser fail (CORS or mixed content errors), check that `API_EXTERNAL_URL` and `SUPABASE_PUBLIC_URL` in your `.env` file match your actual domain and protocol - not `http://localhost:8000`.
+If uploads from a browser fail (CORS or mixed content errors), check that `SUPABASE_PUBLIC_URL` in your `.env` file matches your actual domain and protocol - not `http://localhost:8000`.
### Additional resources
diff --git a/apps/docs/content/guides/self-hosting/self-hosted-saml-sso.mdx b/apps/docs/content/guides/self-hosting/self-hosted-saml-sso.mdx
index ec2d8b0e322..35749d03dd3 100644
--- a/apps/docs/content/guides/self-hosting/self-hosted-saml-sso.mdx
+++ b/apps/docs/content/guides/self-hosting/self-hosted-saml-sso.mdx
@@ -23,7 +23,7 @@ You need:
- Open SSL installed (for key generation)
- Your IdP's SAML metadata URL or metadata XML
- The `SERVICE_ROLE_KEY` from your `.env` file (needed for admin API calls)
-- `API_EXTERNAL_URL` set to the publicly-accessible URL of your Supabase Auth service (e.g., `https://`). This URL is used as the SAML Service Provider entity ID and for constructing the ACS endpoint URL
+- `API_EXTERNAL_URL` set to the publicly-accessible URL of your Supabase Auth service (e.g., `https:///auth/v1`). Used as the base for constructing the SAML Service Provider entity ID and ACS endpoint URL
## How SAML SSO works in Supabase
@@ -37,7 +37,7 @@ The login flow works as follows:
1. Your app calls `POST /auth/v1/sso` with a domain or provider_id
2. Auth generates a SAML `AuthnRequest` and returns a redirect URL to the IdP
3. The user authenticates at the IdP
-4. The IdP POSTs a SAML Response to `POST /sso/saml/acs`
+4. The IdP POSTs a SAML Response to `POST /auth/v1/sso/saml/acs`
5. Auth validates the assertion, creates or links the user, and issues a session
6. The user is redirected back to your app with session tokens
@@ -89,10 +89,6 @@ SAML_PRIVATE_KEY=
# Optional: how long relay state tokens remain valid (default: 2m0s)
# SAML_RELAY_STATE_VALIDITY_PERIOD=2m0s
-# Optional: override the SAML entity ID / ACS base URL
-# Defaults to API_EXTERNAL_URL if not set
-# SAML_EXTERNAL_URL=https://supabase.example.com:8000
-
# Optional: rate limit on the ACS endpoint (requests per second, default: 15)
# SAML_RATE_LIMIT_ASSERTION=15
```
@@ -111,7 +107,6 @@ auth:
GOTRUE_SAML_PRIVATE_KEY: ${SAML_PRIVATE_KEY}
# GOTRUE_SAML_ALLOW_ENCRYPTED_ASSERTIONS: ${SAML_ALLOW_ENCRYPTED_ASSERTIONS}
# GOTRUE_SAML_RELAY_STATE_VALIDITY_PERIOD: ${SAML_RELAY_STATE_VALIDITY_PERIOD}
- # GOTRUE_SAML_EXTERNAL_URL: ${SAML_EXTERNAL_URL}
# GOTRUE_SAML_RATE_LIMIT_ASSERTION: ${SAML_RATE_LIMIT_ASSERTION}
```
@@ -137,7 +132,7 @@ Once SAML is enabled, your Supabase instance exposes service provider (SP) metad
Verify it using `curl`:
```sh
-curl http:///sso/saml/metadata
+curl http:///auth/v1/sso/saml/metadata
```
This returns an XML document containing your SP entity ID, ACS endpoint URL, and signing certificate. You will need to provide this to your IdP.
@@ -304,6 +299,21 @@ On the IdP side, create a new SAML application and configure it with your SP det
+
+
+**Auth0:**
+
+- Dashboard → Applications → Applications → Create Application → Regular Web Application
+- Settings tab → Advanced Settings → Endpoints tab
+- Copy the **SAML Metadata URL** (this is the IdP metadata URL for Supabase)
+- Settings tab → Advanced Settings → Add-ons → SAML2 Web App
+- Enable SAML2 Web App add-on
+- Settings tab in the add-on:
+ - Application Callback URL (ACS): `{API_EXTERNAL_URL}/sso/saml/acs`
+- Enable the add-on
+
+
+
## Attribute mapping
@@ -528,7 +538,6 @@ The response should include `app_metadata.provider: "sso:saml"` and any mapped a
| `SAML_PRIVATE_KEY` | - | Base64-encoded PKCS#1 RSA private key (min 2048-bit). Used to sign SAML requests and optionally decrypt assertions. |
| `SAML_ALLOW_ENCRYPTED_ASSERTIONS` | `false` | Accept encrypted SAML assertions from IdPs |
| `SAML_RELAY_STATE_VALIDITY_PERIOD` | `2m0s` | How long relay state tokens remain valid. Increase if users on slow networks time out during the IdP redirect. |
-| `SAML_EXTERNAL_URL` | `API_EXTERNAL_URL` | Override the base URL used for the SAML entity ID and ACS endpoint. Only needed if the SAML endpoints are served on a different URL than the rest of the Auth API. |
| `SAML_RATE_LIMIT_ASSERTION` | `15` | Maximum ACS requests per second. Protects against assertion replay floods. |
{/* supa-mdx-lint-enable Rule003Spelling */}
@@ -560,8 +569,8 @@ base64 -w 0 -i pk_rsa1.der
### IdP cannot reach the ACS endpoint
-- Verify `API_EXTERNAL_URL` is set to a URL the IdP can reach (not `localhost` unless testing locally)
-- Check that the API gateway routes for `/sso/saml/acs` and `/sso/saml/metadata` are configured as open (no `key-auth` plugin).
+- Verify `API_EXTERNAL_URL` is set to a URL containing `/auth/v1` the IdP can reach (not set to `localhost` unless testing locally)
+- Check that the API gateway routes for `/auth/v1/sso/saml/acs` and `/auth/v1/sso/saml/metadata` are configured as open (no `key-auth` plugin).
- Check the Auth container logs: `docker compose logs auth`
### "No SSO provider found for this domain"
diff --git a/apps/docs/content/guides/storage.mdx b/apps/docs/content/guides/storage.mdx
index 81643f11439..a918edf83a1 100644
--- a/apps/docs/content/guides/storage.mdx
+++ b/apps/docs/content/guides/storage.mdx
@@ -17,91 +17,8 @@ Supabase Storage is a robust, scalable solution for managing files of any size w
- **Fine-grained Access Control** - Manage file permissions with row-level security and custom policies
- **Multiple Bucket Types** - Specialized storage solutions for different use cases
-## Storage bucket types
+
-Supabase Storage offers different bucket types optimized for specific use cases:
+
-### Files buckets
-
-Store and serve traditional files including images, videos, documents, and general-purpose content. Ideal for user-generated content, media libraries, and asset management.
-
-**Use cases:** Images, videos, documents, PDFs, archives
-
-**Features:**
-
-- Global CDN delivery
-- Image optimization and transformation
-- Row-level security integration
-- Direct URL access for files
-
-[Learn more about Files Buckets](/docs/guides/storage/quickstart)
-
-### Analytics buckets
-
-Purpose-built for storing and analyzing data in open table formats like Apache Iceberg. Perfect for time-series data, logs, and large-scale analytical workloads.
-
-**Use cases:** Data lakes, analytics pipelines, ETL operations, historical data analysis
-
-**Features:**
-
-- Apache Iceberg table format support
-- SQL-accessible via Postgres foreign tables
-- Partitioned data organization
-- Efficient data querying and transformation
-
-[Learn more about Analytics Buckets](/docs/guides/storage/analytics/introduction)
-
-### Vector buckets
-
-Specialized storage for vector embeddings and similarity search operations. Designed for AI and ML applications requiring semantic search capabilities.
-
-**Use cases:** AI-powered search, semantic similarity matching, embedding storage, RAG systems
-
-**Features:**
-
-- Optimized vector indexing (HNSW, Flat)
-- Multiple distance metrics (cosine, euclidean, L2)
-- Metadata filtering for vectors
-- Similarity search queries
-
-[Learn more about Vector Buckets](/docs/guides/storage/vector/introduction)
-
-## Examples
-
-Check out all of the Storage [templates and examples](https://github.com/supabase/supabase/tree/master/examples/storage) in our GitHub repository.
-
-
-
-
-
- Use Uppy to upload files to Supabase Storage using the TUS protocol (resumable uploads).
-
-
-
-
-
-## Resources
-
-Find the source code and documentation in the Supabase GitHub repository.
-
-
-
-
- View the source code.
-
-
-
-
-
- See the Swagger Documentation for Supabase Storage.
-
-
-
-
+
diff --git a/apps/docs/content/guides/storage/analytics/creating-analytics-buckets.mdx b/apps/docs/content/guides/storage/analytics/creating-analytics-buckets.mdx
index f9a6c7b1fe2..2680ec95879 100644
--- a/apps/docs/content/guides/storage/analytics/creating-analytics-buckets.mdx
+++ b/apps/docs/content/guides/storage/analytics/creating-analytics-buckets.mdx
@@ -72,7 +72,12 @@ print('Analytics bucket created:', response)
4. Select **Analytics Bucket** as the bucket type.
5. Click **Create**.
-
+
## Next steps
diff --git a/apps/docs/content/guides/storage/cdn/purge-cdn-cache.mdx b/apps/docs/content/guides/storage/cdn/purge-cdn-cache.mdx
new file mode 100644
index 00000000000..81627a48985
--- /dev/null
+++ b/apps/docs/content/guides/storage/cdn/purge-cdn-cache.mdx
@@ -0,0 +1,120 @@
+---
+id: 'storage-cdn-purge-cache'
+title: 'Purge CDN Cache'
+description: 'Learn how to manually purge Supabase Storage CDN cache.'
+sidebar_label: 'CDN'
+---
+
+With Smart CDN enabled, Supabase Storage automatically invalidates the cache when files are updated or deleted. However, there are scenarios where you may need to manually purge the CDN cache for specific objects or entire buckets. The cache purge API allows you to immediately queue cache content invalidation across all CDN edge nodes.
+
+Manual cache purging is useful when you need to ensure that updates are propagated as soon as possible, or when you want to clear the cache for debugging purposes. Once purged, the next request for that object will be served from the origin server, and the CDN cache will be repopulated.
+
+
+
+Cache purging requires the **secret key**. The server rejects calls made with the legacy anon key or a user JWT. Never expose your secret key in client-side code.
+
+
+
+
+
+CDN cache purge is available for [Pro Plan and above](/pricing).
+
+
+
+## Purge a single object
+
+You can purge the CDN cache for a specific file by providing the exact path to the object. This operation does not support wildcards or recursion. You must specify the complete path of the file you want to invalidate.
+
+
+
+
+```javascript
+import { createClient } from '@supabase/supabase-js'
+
+// Create Supabase client with secret key
+const supabase = createClient('your_project_url', 'your_secret_key')
+
+// Purge cache for a single object
+async function purgeCachedObject() {
+ const { data, error } = await supabase.storage
+ .from('bucket_name')
+ .purgeCache('folder_name/file_name.png')
+
+ if (error) {
+ // Handle error
+ } else {
+ // Handle success
+ console.log(data.message) // 'success'
+ }
+}
+```
+
+
+
+
+```bash
+curl -X DELETE "https://{your_project_ref}.supabase.co/storage/v1/cdn/bucket_name/folder_name/file_name.png" \
+ -H "apikey: {your_secret_key}"
+
+# If using legacy jwt keys use this header: Authorization: Bearer {your_service_role_jwt}
+```
+
+
+
+
+## Purge an entire bucket
+
+For scenarios where you need to invalidate all cached objects in a bucket, you can purge the entire bucket's cache. This is useful when performing bulk updates or major changes to your storage bucket.
+
+
+
+
+```javascript
+import { createClient } from '@supabase/supabase-js'
+
+// Create Supabase client with secret key
+const supabase = createClient('your_project_url', 'your_secret_key')
+
+// Purge cache for an entire bucket
+async function purgeBucketCache() {
+ const { data, error } = await supabase.storage.purgeBucketCache('bucket_name')
+
+ if (error) {
+ // Handle error
+ } else {
+ // Handle success
+ console.log(data.message) // 'success'
+ }
+}
+```
+
+
+
+
+```bash
+curl -X DELETE "https://{your_project_ref}.supabase.co/storage/v1/cdn/bucket_name" \
+ -H "apikey: {your_secret_key}"
+
+# If using legacy jwt keys use this header: Authorization: Bearer {your_service_role_jwt}
+```
+
+
+
+
+## Cache propagation
+
+After purging the cache, it can take **up to 60 seconds** for the invalidation to propagate across all CDN edge nodes worldwide. During this time, some users may still receive cached content depending on which edge node they are routed to.
+
+Keep in mind that purging the CDN cache does not affect browser caches. If users have the asset cached locally in their browser, they will continue to see the cached version until the browser cache expires based on the `cacheControl` value set during upload.
diff --git a/apps/docs/content/guides/storage/cdn/smart-cdn.mdx b/apps/docs/content/guides/storage/cdn/smart-cdn.mdx
index df408ce497e..802b481d9e7 100644
--- a/apps/docs/content/guides/storage/cdn/smart-cdn.mdx
+++ b/apps/docs/content/guides/storage/cdn/smart-cdn.mdx
@@ -39,7 +39,7 @@ For instance, you can use a URL like `/storage/v1/object/sign/profile-pictures/c
## Signed URLs and CDN caching
-Signed URLs are the primary way to serve assets from private buckets to end users. With Smart CDN enabled, signed URL responses are cached at the CDN edge, just like any other storage request.
+Signed URLs are the primary way to serve assets from private buckets to end users. With Smart CDN enabled, signed URL responses are cached at the CDN edge, like any other storage request.
Unlike public bucket URLs, each signed URL contains a unique token query parameter (`?token=...`). Smart CDN treats each unique token as a separate cache key, meaning the first request with any given signed URL results in a cache miss, and only subsequent requests using that exact same URL will receive a cache hit. Two different signed URLs for the same object, even if generated seconds apart, each maintain their own independent cache entry.
diff --git a/apps/docs/content/guides/storage/quickstart.mdx b/apps/docs/content/guides/storage/quickstart.mdx
index 8a8f781e640..18175bee714 100644
--- a/apps/docs/content/guides/storage/quickstart.mdx
+++ b/apps/docs/content/guides/storage/quickstart.mdx
@@ -19,7 +19,7 @@ Files can be any sort of media file. This includes images, GIFs, and videos. It
### Folders
-Folders are a way to organize your files (just like on your computer). There is no right or wrong way to organize your files. You can store them in whichever folder structure suits your project.
+Folders are a way to organize your files (like on your computer). There is no right or wrong way to organize your files. You can store them in whichever folder structure suits your project.
### Buckets
diff --git a/apps/docs/content/guides/storage/s3/authentication.mdx b/apps/docs/content/guides/storage/s3/authentication.mdx
index 879f3f11149..294e0b24c53 100644
--- a/apps/docs/content/guides/storage/s3/authentication.mdx
+++ b/apps/docs/content/guides/storage/s3/authentication.mdx
@@ -23,7 +23,12 @@ To authenticate with S3, generate a pair of credentials (Access Key ID and Secre
This is all the information you need to connect to Supabase Storage using any S3-compatible service.
-
+
diff --git a/apps/docs/content/guides/storage/schema/custom-roles.mdx b/apps/docs/content/guides/storage/schema/custom-roles.mdx
index 5edc3802f40..889e257d4bb 100644
--- a/apps/docs/content/guides/storage/schema/custom-roles.mdx
+++ b/apps/docs/content/guides/storage/schema/custom-roles.mdx
@@ -39,7 +39,7 @@ using (
## Test the policy
To impersonate the `manager` role, you will need a valid JWT token with the `manager` role.
-You can quickly create one using the `jsonwebtoken` library in Node.js.
+You can create one using the `jsonwebtoken` library in Node.js.
diff --git a/apps/docs/content/guides/storage/security/ownership.mdx b/apps/docs/content/guides/storage/security/ownership.mdx
index 10f724d6841..dd0d6aa8ae2 100644
--- a/apps/docs/content/guides/storage/security/ownership.mdx
+++ b/apps/docs/content/guides/storage/security/ownership.mdx
@@ -36,4 +36,4 @@ using (
);
```
-The use of RLS policies is just one way to enforce access control. You can also implement access control in your server code by following the same pattern.
+The use of RLS policies is one way to enforce access control. You can also implement access control in your server code by following the same pattern.
diff --git a/apps/docs/content/guides/telemetry/log-drains.mdx b/apps/docs/content/guides/telemetry/log-drains.mdx
index a00f0519eff..f1f5f8d54e7 100644
--- a/apps/docs/content/guides/telemetry/log-drains.mdx
+++ b/apps/docs/content/guides/telemetry/log-drains.mdx
@@ -38,7 +38,6 @@ Unsigned requests to HTTP endpoints are temporary and all requests will signed i
Log Drains](/dashboard/projec
- Under URL, set it to your edge function URL `https://[PROJECT REF].supabase.co/functions/v1/hello-world`
- Under Headers, set the `Authorization: Bearer [PUBLISHABLE KEY]`
-
+
@@ -148,7 +147,6 @@ To setup Datadog log drain, generate a Datadog API key [here](https://app.datado
-
## Additional resources
diff --git a/apps/docs/content/guides/telemetry/metrics/grafana-cloud.mdx b/apps/docs/content/guides/telemetry/metrics/grafana-cloud.mdx
index a31a8e4b06e..39d96d066ba 100644
--- a/apps/docs/content/guides/telemetry/metrics/grafana-cloud.mdx
+++ b/apps/docs/content/guides/telemetry/metrics/grafana-cloud.mdx
@@ -44,10 +44,11 @@ If you prefer to reuse an existing Grafana Agent deployment, configure an [integ
This dashboard includes 200+ charts grouped by CPU, IO, connections, replication, WAL, and bloat indicators.
-
## 4. Configure alerts (optional)
diff --git a/apps/docs/content/guides/telemetry/metrics/grafana-self-hosted.mdx b/apps/docs/content/guides/telemetry/metrics/grafana-self-hosted.mdx
index 5e10dbd38a0..917ecfbc9e8 100644
--- a/apps/docs/content/guides/telemetry/metrics/grafana-self-hosted.mdx
+++ b/apps/docs/content/guides/telemetry/metrics/grafana-self-hosted.mdx
@@ -63,10 +63,11 @@ Install Grafana (Docker image, Helm chart, or packages) and connect it to Promet
You now have over 200 production-ready panels covering CPU, IO, WAL, replication, index bloat, and query throughput.
-
## 4. Configure alerting
diff --git a/apps/docs/content/guides/telemetry/metrics/vendor-agnostic.mdx b/apps/docs/content/guides/telemetry/metrics/vendor-agnostic.mdx
index 69d1291e8ec..12cb2746b9d 100644
--- a/apps/docs/content/guides/telemetry/metrics/vendor-agnostic.mdx
+++ b/apps/docs/content/guides/telemetry/metrics/vendor-agnostic.mdx
@@ -51,10 +51,11 @@ No matter which collector you use, you need to hit the Metrics API once per minu
- For other tools, group metrics by categories (CPU, IO, WAL, replication, connections) and recreate the visualizations that matter most to your team.
- Tag or relabel series with `project`, `env`, or `team` labels to make multi-project views easier.
-
## 4. Alerts and automation
diff --git a/apps/docs/content/navigation.references.ts b/apps/docs/content/navigation.references.ts
index dc4f4c137a0..92c56d6b60c 100644
--- a/apps/docs/content/navigation.references.ts
+++ b/apps/docs/content/navigation.references.ts
@@ -30,6 +30,20 @@ export const REFERENCES = {
},
},
},
+ server: {
+ type: 'sdk',
+ name: 'Server',
+ library: '@supabase/server',
+ libPath: 'server',
+ versions: ['v1'],
+ typeSpec: true,
+ icon: 'reference-javascript',
+ meta: {
+ v1: {
+ libId: 'reference_server_v1',
+ },
+ },
+ },
dart: {
type: 'sdk',
name: 'Flutter',
@@ -39,8 +53,10 @@ export const REFERENCES = {
icon: 'reference-dart',
meta: {
v2: {
+ // Dart v2 is driven by the new reference pipeline
+ // (`scripts/build-reference-content.ts` + `spec/reference/dart/v2/`).
+ // It intentionally has no `specFile`, so the legacy YAML loader skips it.
libId: 'reference_dart_v2',
- specFile: 'supabase_dart_v2',
},
v1: {
libId: 'reference_dart_v1',
diff --git a/apps/docs/content/troubleshooting/auth-error-500-database-error-querying-schema-eb6b44.mdx b/apps/docs/content/troubleshooting/auth-error-500-database-error-querying-schema-eb6b44.mdx
new file mode 100644
index 00000000000..8c73b31039b
--- /dev/null
+++ b/apps/docs/content/troubleshooting/auth-error-500-database-error-querying-schema-eb6b44.mdx
@@ -0,0 +1,26 @@
+---
+title = "Auth error: '500: Database error querying schema'"
+date_created = "2026-07-02T20:04:03+00:00"
+topics = [ "auth", "database" ]
+keywords = []
+[[errors]]
+http_status_code = 500
+message = "Database error querying schema"
+
+---
+
+If you observe the error message `500: Database error querying schema` in your Auth logs, it typically indicates that the Supabase Auth server has encountered `NULL` values in the `auth.users` table where a valid string or empty string is expected.
+
+**Why Does This Happen?**
+This issue usually occurs following manual SQL inserts or updates to the `auth.users` table. The Auth service expects specific columns to contain data or an empty string; when it encounters a `NULL` value instead, it triggers a validation error.
+
+**How to Resolve This:**
+Identify the column containing the `NULL` values (such as `email`) and execute the following command in the [SQL Editor](/dashboard/project/_/sql/new) to convert them to empty strings:
+
+```sql
+UPDATE auth.users
+SET = ''
+WHERE IS NULL;
+```
+
+Replace `` with the specific column causing the error. To prevent this from recurring, ensure that any future manual modifications to the `auth` schema adhere to the service's requirements.
diff --git a/apps/docs/content/troubleshooting/cant-access-supabase-project-lovable-cloud.mdx b/apps/docs/content/troubleshooting/cant-access-supabase-project-lovable-cloud.mdx
index b090af0c2ab..1fc223b9c90 100644
--- a/apps/docs/content/troubleshooting/cant-access-supabase-project-lovable-cloud.mdx
+++ b/apps/docs/content/troubleshooting/cant-access-supabase-project-lovable-cloud.mdx
@@ -54,7 +54,6 @@ For more information, read [the Lovable Cloud FAQ](https://docs.lovable.dev/feat
', '
npx supabase secrets list
```
-For security reasons, it is not advised to log secrets, but you can log a truncated version just for the reassurance that they're being updated:
+For security reasons, it is not advised to log secrets, but you can log a truncated version for the reassurance that they're being updated:
```typescript
//logs the function call and the secrets
diff --git a/apps/docs/content/troubleshooting/interpreting-supabase-grafana-io-charts-MUynDR.mdx b/apps/docs/content/troubleshooting/interpreting-supabase-grafana-io-charts-MUynDR.mdx
index bb66f23d10a..cdfc1ed59a0 100644
--- a/apps/docs/content/troubleshooting/interpreting-supabase-grafana-io-charts-MUynDR.mdx
+++ b/apps/docs/content/troubleshooting/interpreting-supabase-grafana-io-charts-MUynDR.mdx
@@ -64,7 +64,7 @@ Other useful Supabase Grafana guides:
### Esoteric factors
**Webhooks**:
-Supabase webhooks use the pg_net extension to handle requests. The `net.http_request_queue` table isn't indexed to keep write costs low. However, if you upload millions of rows to a webhook-enabled table too quickly, it can significantly increase the read costs for the extension.
+Supabase webhooks use the pg_net extension to handle requests. The `net.http_request_queue` table isn't indexed to keep write costs low. However, if you upload millions of rows to a webhook-enabled table in rapid succession, it can significantly increase the read costs for the extension.
To check if reads are becoming expensive, run:
diff --git a/apps/docs/content/troubleshooting/keeping-free-projects-after-pro-upgrade-Kf9Xm2.mdx b/apps/docs/content/troubleshooting/keeping-free-projects-after-pro-upgrade-Kf9Xm2.mdx
index 642457a9adc..1ded427c860 100644
--- a/apps/docs/content/troubleshooting/keeping-free-projects-after-pro-upgrade-Kf9Xm2.mdx
+++ b/apps/docs/content/troubleshooting/keeping-free-projects-after-pro-upgrade-Kf9Xm2.mdx
@@ -8,7 +8,7 @@ database_id = "449482fb-6e21-4a25-99ee-1ce6fffbc975"
## Can you keep your 2 free projects after upgrading to pro?
-Yes! You still get 2 free projects after you upgrade to Pro. They just need to be in a **separate** Free Plan organization.
+Yes. After upgrading to Pro, you still get 2 free projects. They must be in a separate Free Plan organization.
## Why do free projects need to be in a separate organization?
diff --git a/apps/docs/content/troubleshooting/not-receiving-auth-emails-from-the-supabase-project-OFSNzw.mdx b/apps/docs/content/troubleshooting/not-receiving-auth-emails-from-the-supabase-project-OFSNzw.mdx
index eb6a4880fce..6b5ae533118 100644
--- a/apps/docs/content/troubleshooting/not-receiving-auth-emails-from-the-supabase-project-OFSNzw.mdx
+++ b/apps/docs/content/troubleshooting/not-receiving-auth-emails-from-the-supabase-project-OFSNzw.mdx
@@ -9,7 +9,7 @@ database_id = "98e3b318-ad9a-41f5-ada0-44b61970ab4f"
**We strongly recommend configuring your own custom SMTP provider** in your projects to avoid running into issues with sending emails from your project. The built-in email provider is for demonstration purposes only and offers a very low rate limit. You can read more details about the rate limits [here](/docs/guides/platform/going-into-prod#auth-rate-limits).
-If you're testing your Supabase projects, consider configuring an email testing tool like [Mailtrap](https://mailtrap.io/) to test your emails. It's an email sandbox tool that helps debug emails without actually sending an email to an end user.
+If you're testing your Supabase projects, consider configuring an email testing tool like [Mailtrap](https://mailtrap.io/) to test your emails. It's an email sandbox tool that helps debug emails without sending an email to an end user.
As the initial step in debugging not-delivered emails, check your project's [Auth logs](/dashboard/project/_/logs/auth-logs) for errors that can occur when handing over to the email providers. In general, handover issues can occur due to misconfiguration of the custom SMTP provider.
diff --git a/apps/docs/content/troubleshooting/pausing-pro-projects-vNL-2a.mdx b/apps/docs/content/troubleshooting/pausing-pro-projects-vNL-2a.mdx
index f783efb49a7..0dbb540a781 100644
--- a/apps/docs/content/troubleshooting/pausing-pro-projects-vNL-2a.mdx
+++ b/apps/docs/content/troubleshooting/pausing-pro-projects-vNL-2a.mdx
@@ -14,7 +14,7 @@ Pro-Projects at the moment cannot be paused. However, [You are allowed to have t
If a project is under 500MB, you can [transfer it to be under a free organization](/docs/guides/platform/project-transfer). Afterwards, you can initiate a pause.
-Alternatively, you can download a [daily backup](/dashboard/project/_/database/backups/scheduled) of just your database for archiving. You can also manually download a .SQL file of your database and storage buckets by following this [guide](/docs/guides/platform/migrating-within-supabase/backup-restore).
+Alternatively, you can download a [daily backup](/dashboard/project/_/database/backups/scheduled) of only your database for archiving. You can also manually download a .SQL file of your database and storage buckets by following this [guide](/docs/guides/platform/migrating-within-supabase/backup-restore).
You can also download your storage buckets with the [Supabase CLI:](/docs/guides/cli/getting-started?queryGroups=platform&platform=npx)
diff --git a/apps/docs/content/troubleshooting/postgrest-error-400-column-example_tableexample_column-does-not-exist-when-using-or-operators-46ff23.mdx b/apps/docs/content/troubleshooting/postgrest-error-400-column-example_tableexample_column-does-not-exist-when-using-or-operators-46ff23.mdx
index 27d6f72b96a..e132244871a 100644
--- a/apps/docs/content/troubleshooting/postgrest-error-400-column-example_tableexample_column-does-not-exist-when-using-or-operators-46ff23.mdx
+++ b/apps/docs/content/troubleshooting/postgrest-error-400-column-example_tableexample_column-does-not-exist-when-using-or-operators-46ff23.mdx
@@ -17,7 +17,7 @@ The bug is triggered when an `or()` filter is included in a mutation request. Po
## How to confirm
1. **Reproduce the asymmetry** — run the same filter as a `GET` request. If it succeeds but the `PATCH`/`POST`/`DELETE` fails with the same column reference, the bug is the likely cause.
-2. **Check your PostgREST version** — go to [Project Settings > Infrastructure](/dashboard/project/_/settings/infrastructure) and note the PostgreSQL version. PostgREST 14.1 and earlier are affected; 14.4+ includes the fix.
+2. **Check your PostgREST version** — go to [Project Settings > Infrastructure](/dashboard/project/_/settings/infrastructure) and note the Postgres version. PostgREST 14.1 and earlier are affected; 14.4+ includes the fix.
3. **Check Postgres logs** — if you have logging enabled, you should see `column "example_column" does not exist` errors correlating with the timestamps of the failed mutation requests.
## Resolution
diff --git a/apps/docs/content/troubleshooting/realtime-warn-sending-broadcast-message.mdx b/apps/docs/content/troubleshooting/realtime-warn-sending-broadcast-message.mdx
index bbdf7013e0a..6188422ac4f 100644
--- a/apps/docs/content/troubleshooting/realtime-warn-sending-broadcast-message.mdx
+++ b/apps/docs/content/troubleshooting/realtime-warn-sending-broadcast-message.mdx
@@ -44,7 +44,7 @@ Calling `realtime.send` / `realtime.send_binary`, the broadcast REST endpoint, a
## How to avoid it
-- Connect a client before broadcasting from the database. A live WebSocket connection both creates the partitions and starts the consumer that actually receives the message.
+- Connect a client before broadcasting from the database. A live WebSocket connection both creates the partitions and starts the consumer that receives the message.
- If you broadcast from the database on a schedule, make sure at least one subscriber is connected when you send. Otherwise the messages have no destination.
## When it is a real problem
diff --git a/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx b/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx
index 1d983d900bb..7954a056dfb 100644
--- a/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx
+++ b/apps/docs/content/troubleshooting/resolving-42p01-relation-does-not-exist-error-W4_9-V.mdx
@@ -48,9 +48,9 @@ rename to table_name;
---
-### Cause 3: Table or function actually does not exist
+### Cause 3: Table or function does not exist
-One may have never made the table or dropped it deliberately or accidentally. This can be quickly checked with the following query:
+One may have never made the table or dropped it deliberately or accidentally. This can be checked with the following query:
```sql
-- For tables
diff --git a/apps/docs/content/troubleshooting/resolving-500-status-authentication-errors-7bU5U8.mdx b/apps/docs/content/troubleshooting/resolving-500-status-authentication-errors-7bU5U8.mdx
index b9b532be608..344f13d04ab 100644
--- a/apps/docs/content/troubleshooting/resolving-500-status-authentication-errors-7bU5U8.mdx
+++ b/apps/docs/content/troubleshooting/resolving-500-status-authentication-errors-7bU5U8.mdx
@@ -106,7 +106,7 @@ Alternatively, you can run the SQL script in this [GitHub Gist](https://gist.git
#### Trigger related:
-If errors reference a database function, this indicates a trigger error on one of the auth tables (likely auth.users). If you do not want to keep the trigger/function, you can just quickly drop it, otherwise, continue reading to know how to fix the issue:
+If errors reference a database function, this indicates a trigger error on one of the auth tables (likely auth.users). If you do not want to keep the trigger/function, you can drop it, otherwise, continue reading to know how to fix the issue:
```sql
-- delete the trigger with the following SQL:
diff --git a/apps/docs/content/troubleshooting/restore-project-after-90-days-pause.mdx b/apps/docs/content/troubleshooting/restore-project-after-90-days-pause.mdx
index 3c91adbfe70..6e7a7b47a8e 100644
--- a/apps/docs/content/troubleshooting/restore-project-after-90-days-pause.mdx
+++ b/apps/docs/content/troubleshooting/restore-project-after-90-days-pause.mdx
@@ -75,7 +75,6 @@ chmod +x sync_supabase_config.sh
-Have you ever accidentally committed a service key to a public repo? Or maybe rotating keys is just something you regularly do for security compliance.
+Have you ever accidentally committed a service key to a public repo? Or maybe rotating keys is something you regularly do for security compliance.
Whatever the reason, here's how to rotate the keys for your Supabase project.
If you haven’t migrated to asymmetric JWT signing keys:
diff --git a/apps/docs/content/troubleshooting/running-explain-analyze-on-functions.mdx b/apps/docs/content/troubleshooting/running-explain-analyze-on-functions.mdx
index 8aa25f3420a..35ec1fc84e9 100644
--- a/apps/docs/content/troubleshooting/running-explain-analyze-on-functions.mdx
+++ b/apps/docs/content/troubleshooting/running-explain-analyze-on-functions.mdx
@@ -8,7 +8,7 @@ database_id = "1d62cace-c0f6-47a0-8690-002a797da33b"
sdk = [ "rpc" ]
---
-Sometimes it can help to look at Postgres query plans inside a function. The problem is that running [`EXPLAIN ANALYZE`](https://www.depesz.com/2013/04/16/explaining-the-unexplainable/) on a function usually just shows a [function scan](https://pganalyze.com/docs/explain/scan-nodes/function-scan) or result node, which gives little insight into how the queries actually perform.
+Sometimes it can help to look at Postgres query plans inside a function. The problem is that running [`EXPLAIN ANALYZE`](https://www.depesz.com/2013/04/16/explaining-the-unexplainable/) on a function usually shows a [function scan](https://pganalyze.com/docs/explain/scan-nodes/function-scan) or result node, which gives little insight into how the queries perform.
[`auto_explain`](https://www.postgresql.org/docs/current/auto-explain.html) is a pre-installed module that is able to log query plans for queries within functions.
diff --git a/apps/docs/content/troubleshooting/security-of-anonymous-sign-ins-iOrGCL.mdx b/apps/docs/content/troubleshooting/security-of-anonymous-sign-ins-iOrGCL.mdx
index d524a22352c..ba1a957e518 100644
--- a/apps/docs/content/troubleshooting/security-of-anonymous-sign-ins-iOrGCL.mdx
+++ b/apps/docs/content/troubleshooting/security-of-anonymous-sign-ins-iOrGCL.mdx
@@ -13,7 +13,7 @@ We want to clarify and provide reassurance on this topic.
Enabling anonymous sign-ins on your project does not reduce its security. Here's why:
-- Same as Regular Users: Anonymous users function just like regular users within your project. They have unique user IDs and their own records in the authentication tables.
+- Same as Regular Users: Anonymous users function like regular users within your project. They have unique user IDs and their own records in the authentication tables.
- Security Policies: All role-based security policies (RLS) applicable to regular users also apply to anonymous users.
- Identity Verification Measures: Even though anonymous users do not initially provide an email or phone number, the security of your project remains robust. But to prevent misuse, we recommend implementing additional security measure such as [CAPTCHA](/docs/guides/auth/auth-captcha): to ensure that interactions are genuinely human.
diff --git a/apps/docs/content/troubleshooting/soft-deletes-with-supabase-js.mdx b/apps/docs/content/troubleshooting/soft-deletes-with-supabase-js.mdx
index 915c6f9339d..21610977255 100644
--- a/apps/docs/content/troubleshooting/soft-deletes-with-supabase-js.mdx
+++ b/apps/docs/content/troubleshooting/soft-deletes-with-supabase-js.mdx
@@ -75,7 +75,7 @@ This effectively un-deletes the record.
## Benefits of using views for soft deletes
-- **Cleaner Queries:** No need to add `WHERE deleted_at IS NULL` to every query. Just query the view (`active_items`).
+- **Cleaner Queries:** No need to add `WHERE deleted_at IS NULL` to every query. Query the view (`active_items`) instead.
- **Separation of Concerns:** Views abstract the logic of filtering deleted records from your application code.
- **Efficiency:** Postgres handles the filtering in the view, reducing the complexity in your app.
diff --git a/apps/docs/content/troubleshooting/steps-to-improve-query-performance-with-indexes-q8PoC9.mdx b/apps/docs/content/troubleshooting/steps-to-improve-query-performance-with-indexes-q8PoC9.mdx
index d159b35512b..e724c1893a9 100644
--- a/apps/docs/content/troubleshooting/steps-to-improve-query-performance-with-indexes-q8PoC9.mdx
+++ b/apps/docs/content/troubleshooting/steps-to-improve-query-performance-with-indexes-q8PoC9.mdx
@@ -43,7 +43,7 @@ select
from pg_statio_user_tables;
```
-If the cache hit rate is relatively low, it often means that you need to increase your memory capacity. The second metric that is often inspected is index usage. Indexes are data structures that allow Postgres to search for information quickly - think of them like you would think of an index at the back of a book. Instead of scanning every page (or row), you can use an index to find the contents you need quickly. For a better understanding of how Postgres decides on whether to use an index or not, check out this [explainer](https://github.com/orgs/supabase/discussions/26959).
+If the cache hit rate is relatively low, it often means that you need to increase your memory capacity. The second metric that is often inspected is index usage. Indexes are data structures that allow Postgres to search for information efficiently - think of them like you would think of an index at the back of a book. Instead of scanning every page (or row), you can use an index to find the contents you need efficiently. For a better understanding of how Postgres decides on whether to use an index or not, check out this [explainer](https://github.com/orgs/supabase/discussions/26959).
The index hit rate (how often an index is used) can usually be improved moderately.
@@ -59,7 +59,7 @@ where seq_scan + idx_scan > 0
order by n_live_tup desc;
```
-A lot of the [queries for inspecting performance](/docs/reference/cli/supabase-inspect-db) are actually pre-bundled as part of the [Supabase CLI](/docs/guides/cli/getting-started). For instance, there is a command for testing which indexes of yours are unnecessary and are needlessly taking up space:
+A lot of the [queries for inspecting performance](/docs/reference/cli/supabase-inspect-db) are pre-bundled as part of the [Supabase CLI](/docs/guides/cli/getting-started). For instance, there is a command for testing which indexes of yours are unnecessary and are needlessly taking up space:
```bash
npx supabase login
diff --git a/apps/docs/content/troubleshooting/storage-error-database-schema-is-incompatible-when-uploading-files-988baa.mdx b/apps/docs/content/troubleshooting/storage-error-database-schema-is-incompatible-when-uploading-files-988baa.mdx
new file mode 100644
index 00000000000..8436a468a2c
--- /dev/null
+++ b/apps/docs/content/troubleshooting/storage-error-database-schema-is-incompatible-when-uploading-files-988baa.mdx
@@ -0,0 +1,25 @@
+---
+title = "RLS policy causes infinite recursion"
+date_created = "2026-06-29T03:04:33+00:00"
+topics = [ "database", "storage" ]
+keywords = ["infinite recursion", "RLS"]
+---
+
+If your Storage or database requests encounter an error and the corresponding Postgres logs show '_infinite recursion detected in policy for relation "table_name"_', it is because of infinite recursion in your Row-Level Security (RLS) policies.
+
+**Why Does This Happen?**
+
+This error indicates a circular dependency between RLS policies. It occurs when an RLS policy queries a table with RLS enabled, and that table's policies end up triggering the original policy again.
+
+This can happen when:
+
+- A policy queries the same table.
+- A policy queries another table whose RLS policies eventually reference the original table, creating a circular dependency.
+
+Postgres detects the recursive policy evaluation and terminates the query to prevent infinite recursion. This error can also occur during Storage operations if a Storage RLS policy references a table with self-referencing or mutually recursive RLS policies.
+
+**How to Resolve This Issue:**
+
+Option 1: Update the table's RLS policies to ensure they do not directly or indirectly create a circular dependency.
+
+Option 2: Wrap the permission check in a SECURITY DEFINER function. This executes with the privileges of the user who created the function — if created with a role like postgres, it bypasses RLS on the target table and breaks the recursion. See [Use Security Definer Functions](/docs/guides/database/postgres/row-level-security#use-security-definer-functions) for details.
diff --git a/apps/docs/content/troubleshooting/supabase-grafana-memory-charts.mdx b/apps/docs/content/troubleshooting/supabase-grafana-memory-charts.mdx
index 3f7239eeeb5..4106f66cfba 100644
--- a/apps/docs/content/troubleshooting/supabase-grafana-memory-charts.mdx
+++ b/apps/docs/content/troubleshooting/supabase-grafana-memory-charts.mdx
@@ -14,7 +14,7 @@ Here are examples of unhealthy memory usage:

- **Yellow**: represents active memory
-- **Red**: represents SWAP, which is disk storage that the system treats as if it were actually memory
+- **Red**: represents SWAP, which is disk storage that the system treats as if it were memory
- **Green**: it is unclaimed (the system will always leave some memory unclaimed)
- **Blue**: it is cached data and a buffer
diff --git a/apps/docs/content/troubleshooting/supavisor-and-connection-terminology-explained-9pr_ZO.mdx b/apps/docs/content/troubleshooting/supavisor-and-connection-terminology-explained-9pr_ZO.mdx
index 04a60dc9c16..ad0f4472c28 100644
--- a/apps/docs/content/troubleshooting/supavisor-and-connection-terminology-explained-9pr_ZO.mdx
+++ b/apps/docs/content/troubleshooting/supavisor-and-connection-terminology-explained-9pr_ZO.mdx
@@ -7,7 +7,7 @@ keywords = [ "connections", "pooler", "transaction", "session", "pgbouncer" ]
database_id = "e008e056-453d-44b5-8a8a-27c2d70681fe"
---
-I'll be the first to admit that the official naming conventions in the Postgres community can be a bit confusing, so here's a basic rundown. It's a bit long, so feel free to just jump to the portion relevant to you:
+I'll be the first to admit that the official naming conventions in the Postgres community can be a bit confusing, so here's a basic rundown. It's a bit long, so feel free to jump to the portion relevant to you:
## Clients:
diff --git a/apps/docs/content/troubleshooting/supavisor-faq-YyP5tI.mdx b/apps/docs/content/troubleshooting/supavisor-faq-YyP5tI.mdx
index 5680737403d..0bf72e502c8 100644
--- a/apps/docs/content/troubleshooting/supavisor-faq-YyP5tI.mdx
+++ b/apps/docs/content/troubleshooting/supavisor-faq-YyP5tI.mdx
@@ -81,11 +81,11 @@ When clients connect to either Postgres or Supavisor, they do so with the Postgr
### What are "client connections"?
In summary, they have nothing to do with front-end clients. They are the amount of backend-server connections that can connect to a serverside pooler.
-Imagine a chess tournament with 60 boards. Each board represents a connection in a database. When a player sits down at a board, it's like a client connecting to the database. They can take their time with their moves or just sit there, not making any.
+Imagine a chess tournament with 60 boards. Each board represents a connection in a database. When a player sits down at a board, it's like a client connecting to the database. They can take their time with their moves or sit there, not making any.
But when the tournament fills up and all the boards are taken, new players are turned away, and told to check back at a later time to see if a table becomes available.
-Now, imagine the tournament organizers decide to expand the venue to house 200 people without adding more tables. Even when all boards are occupied, players don't have to leave. They can wait in the wings, and the moment a board opens up, someone from the waiting area can take their place. Likewise, if someone ends their game, but wants to play again, they can just go back to the waiting area. The waiting area represents the "Max Client Connections". Ultimately, the additional capacity provided by the pooler ensures fewer people are turned away from the "tournament".
+Now, imagine the tournament organizers decide to expand the venue to house 200 people without adding more tables. Even when all boards are occupied, players don't have to leave. They can wait in the wings, and the moment a board opens up, someone from the waiting area can take their place. Likewise, if someone ends their game, but wants to play again, they can go back to the waiting area. The waiting area represents the "Max Client Connections". Ultimately, the additional capacity provided by the pooler ensures fewer people are turned away from the "tournament".
### In the context of Supavisor, what does "pool size" mean?
@@ -93,14 +93,14 @@ Now, imagine the tournament organizers decide to expand the venue to house 200 p
### What is the "user+db+mode" combination?
-Postgres is not actually a database. It is a Relational Database Management System (RDMS). Within it, you can spawn Postgres databases. In Supabase, it is a common pattern to just use the default database called `postgres`, but you could create more:
+Postgres is not a database. It is a Relational Database Management System (RDMS). Within it, you can spawn Postgres databases. In Supabase, it is a common pattern to use the default database called `postgres`, but you could create more:
```sql
CREATE DATABASE postgres;
CREATE DATABASE another_database;
```
-Similarly, a database can have many database users, but most people just rely on the default user `postgres`.
+Similarly, a database can have many database users, but most people rely on the default user `postgres`.
```sql
CREATE USER postgres WITH PASSWORD 'super-secret-password';
diff --git a/apps/docs/content/troubleshooting/tracking-postgres-role-activity-to-specific-dashboard-users-8d3715.mdx b/apps/docs/content/troubleshooting/tracking-postgres-role-activity-to-specific-dashboard-users-8d3715.mdx
index 325b647cac5..f679efeeaab 100644
--- a/apps/docs/content/troubleshooting/tracking-postgres-role-activity-to-specific-dashboard-users-8d3715.mdx
+++ b/apps/docs/content/troubleshooting/tracking-postgres-role-activity-to-specific-dashboard-users-8d3715.mdx
@@ -37,10 +37,10 @@ Notice that the log includes:
- The full statement
- The timestamp
-- A `user` field, which is actually the Supabase user UUID
+- A `user` field, which is the Supabase user UUID
- The source (dashboard)
-That UUID corresponds to the team member who logged in via the Supabase Dashboard and executed queries in the [SQL Editor](/dashboard/project/_/sql/new). But at this point, it's just an ID - not yet a name or email.
+That UUID corresponds to the team member who logged in via the Supabase Dashboard and executed queries in the [SQL Editor](/dashboard/project/_/sql/new). But at this point, it's only an ID - not yet a name or email.
### **Mapping UUIDs to team members**
diff --git a/apps/docs/content/troubleshooting/understanding-postgresql-explain-output-Un9dqX.mdx b/apps/docs/content/troubleshooting/understanding-postgresql-explain-output-Un9dqX.mdx
index 45c7760eca4..97cbe55b88e 100644
--- a/apps/docs/content/troubleshooting/understanding-postgresql-explain-output-Un9dqX.mdx
+++ b/apps/docs/content/troubleshooting/understanding-postgresql-explain-output-Un9dqX.mdx
@@ -45,9 +45,9 @@ const { data, error } = await supabase
**- Seq Scan:** A sequential scan that reads all rows from a table. This is often seen in the absence of indexes that can be used for the query.
-**- Index Scan:** Uses an index to find rows quickly. This indicates that the query is able to use an index to efficiently locate data.
+**- Index Scan:** Uses an index to find rows efficiently. This indicates that the query is able to use an index to efficiently locate data.
-**- Bitmap Heap Scan:** Uses a bitmap index to find rows quickly and then retrieves the actual rows from the table. This type of scan is efficient when retrieving a moderate number of rows.
+**- Bitmap Heap Scan:** Uses a bitmap index to find rows efficiently and then retrieves the actual rows from the table. This type of scan is efficient when retrieving a moderate number of rows.
### 2. Cost
@@ -70,7 +70,7 @@ const { data, error } = await supabase
### 6. Execution time
-**Execution Time: 0.069 ms:** measures how long it took to actually execute the query, including retrieving the data, performing any sorts, joins, or other operations defined in the execution plan, and returning the final results. This time is measured in milliseconds.
+**Execution Time: 0.069 ms:** measures how long it took to execute the query, including retrieving the data, performing any sorts, joins, or other operations defined in the execution plan, and returning the final results. This time is measured in milliseconds.

@@ -78,7 +78,7 @@ const { data, error } = await supabase
When running EXPLAIN ANALYZE, additional information is provided, including:
-- **Actual time:** Shows the time actually spent executing the scan and retrieving rows. This is split into the time to retrieve the first row (first) and the time to retrieve all rows (last).
+- **Actual time:** Shows the time spent executing the scan and retrieving rows. This is split into the time to retrieve the first row (first) and the time to retrieve all rows (last).
- **Rows removed by filter:** Indicates how many rows were excluded due to not meeting the filter conditions.
@@ -135,25 +135,25 @@ On top of that, you have to multiply the cost and the time with the number of
### Common nodes in Postgres explain output
-| Node Type | Description |
-| --------------------- | ---------------------------------------------------------------------------- |
-| **Seq Scan** | Scans each row of a table sequentially, often used without suitable indexes. |
-| **Index Scan** | Uses an index to quickly find rows, efficient for small fractions of rows. |
-| **Index Only Scan** | Retrieves all needed data from the index itself, without visiting the table. |
-| **Bitmap Heap Scan** | Uses a bitmap of row locations to efficiently retrieve rows from the table. |
-| **Bitmap Index Scan** | Builds a bitmap by scanning the index to efficiently locate rows. |
-| **`Tid` Scan** | Fetches rows directly using tuple identifiers, used in sub-selects. |
-| **Nested Loop** | Joins two tables by scanning the first and then the second for each row. |
-| **Merge Join** | Joins two pre-sorted tables, efficient for large datasets. |
-| **Hash Join** | Uses a hash table to perform joins, often faster for larger datasets. |
-| **Aggregate** | Performs aggregation calculations like `SUM`, `COUNT`, etc. |
-| **Sort** | Sorts rows based on specified criteria, required for certain operations. |
-| **Limit** | Returns a specified number of rows quickly, used with `LIMIT` clause. |
-| **CTE Scan** | Scans a Common Table Expression, used for WITH clauses. |
-| **Materialize** | Materializes the result of a subquery or node to reuse without re-running. |
-| **Subquery Scan** | Executes and provides the results of a subquery to the outer query. |
-| **Foreign Scan** | Fetches data from foreign data sources outside the local database. |
-| **Function Scan** | Retrieves results from a set-returning function. |
+| Node Type | Description |
+| --------------------- | ------------------------------------------------------------------------------ |
+| **Seq Scan** | Scans each row of a table sequentially, often used without suitable indexes. |
+| **Index Scan** | Uses an index to efficiently find rows, efficient for small fractions of rows. |
+| **Index Only Scan** | Retrieves all needed data from the index itself, without visiting the table. |
+| **Bitmap Heap Scan** | Uses a bitmap of row locations to efficiently retrieve rows from the table. |
+| **Bitmap Index Scan** | Builds a bitmap by scanning the index to efficiently locate rows. |
+| **`Tid` Scan** | Fetches rows directly using tuple identifiers, used in sub-selects. |
+| **Nested Loop** | Joins two tables by scanning the first and then the second for each row. |
+| **Merge Join** | Joins two pre-sorted tables, efficient for large datasets. |
+| **Hash Join** | Uses a hash table to perform joins, often faster for larger datasets. |
+| **Aggregate** | Performs aggregation calculations like `SUM`, `COUNT`, etc. |
+| **Sort** | Sorts rows based on specified criteria, required for certain operations. |
+| **Limit** | Returns a specified number of rows efficiently, used with `LIMIT` clause. |
+| **CTE Scan** | Scans a Common Table Expression, used for WITH clauses. |
+| **Materialize** | Materializes the result of a subquery or node to reuse without re-running. |
+| **Subquery Scan** | Executes and provides the results of a subquery to the outer query. |
+| **Foreign Scan** | Fetches data from foreign data sources outside the local database. |
+| **Function Scan** | Retrieves results from a set-returning function. |
### What to focus on in explain analyze output
@@ -182,11 +182,11 @@ Determining whether 100 milliseconds for e.g is noteworthy in the context of ide
**Overall Query Execution Time:**
-If the total execution time of a query significantly exceeds 100 milliseconds, then this particular step may not represent the main bottleneck. For instance, in queries that take several seconds to execute, a component that consumes just 100 milliseconds may not be the critical target for optimization efforts.
+If the total execution time of a query significantly exceeds 100 milliseconds, then this particular step may not represent the main bottleneck. For instance, in queries that take several seconds to execute, a component that consumes only 100 milliseconds may not be the critical target for optimization efforts.
**Complexity and Scale of the Query:**
-For complex queries that involve multiple joins, subqueries, or aggregation functions, an operation that takes 100 milliseconds might actually reflect good efficiency. Conversely, for simpler queries or operations expected to be quick (such as fetching a few rows from a well-indexed table), 100 milliseconds could suggest a lack of efficiency.
+For complex queries that involve multiple joins, subqueries, or aggregation functions, an operation that takes 100 milliseconds might reflect good efficiency. Conversely, for simpler queries or operations expected to be quick (such as fetching a few rows from a well-indexed table), 100 milliseconds could suggest a lack of efficiency.
So, the acceptable performance threshold can vary by application. For real-time systems or high-frequency trading platforms, even a few milliseconds can be critical, whereas for batch processing or data warehousing, longer execution times might be acceptable.
diff --git a/apps/docs/content/troubleshooting/understanding-postgresql-logging-levels-and-how-they-impact-your-project-KXiJRm.mdx b/apps/docs/content/troubleshooting/understanding-postgresql-logging-levels-and-how-they-impact-your-project-KXiJRm.mdx
index d162dd29360..a88e2e726b2 100644
--- a/apps/docs/content/troubleshooting/understanding-postgresql-logging-levels-and-how-they-impact-your-project-KXiJRm.mdx
+++ b/apps/docs/content/troubleshooting/understanding-postgresql-logging-levels-and-how-they-impact-your-project-KXiJRm.mdx
@@ -7,7 +7,7 @@ keywords = [ "logging", "disk", "i/o", "lockups" ]
database_id = "10186830-8cce-4f10-8cb9-7cbf39310763"
---
-Since each Supabase project uses Postgres as its underlying database engine, it’s common to adjust logging settings for various reasons—whether for debugging issues, monitoring database performance, or auditing actions. However, modifying logging levels improperly can lead to an excessive amount of log data being generated, which can quickly fill up your disk space and cause significant performance degradation or even system failure.
+Since each Supabase project uses Postgres as its underlying database engine, it’s common to adjust logging settings for various reasons—whether for debugging issues, monitoring database performance, or auditing actions. However, modifying logging levels improperly can lead to an excessive amount of log data being generated, which can fill up your disk space and cause significant performance degradation or even system failure.
### 1. Overview of Postgres logging levels
@@ -41,7 +41,7 @@ The default log level is set to **WARNING** through the log_min_messages setting
### 2. How high log levels can affect your database
-When users alter a high level of log settings, the database can start generating an overwhelming number of log entries. This can quickly escalate to issues such as:
+When users alter a high level of log settings, the database can start generating an overwhelming number of log entries. This can escalate to issues such as:
- Disk Space Exhaustion: Log files can grow exponentially if verbose levels like DEBUG, INFO, or NOTICE are enabled for long periods. And running out of disk space due to log bloat can cause your database to stop accepting writes and slow down query performance.
diff --git a/apps/docs/content/troubleshooting/understanding-the-usage-summary-on-the-dashboard-D7Gnle.mdx b/apps/docs/content/troubleshooting/understanding-the-usage-summary-on-the-dashboard-D7Gnle.mdx
index 6a49c389fe4..5457e9280aa 100644
--- a/apps/docs/content/troubleshooting/understanding-the-usage-summary-on-the-dashboard-D7Gnle.mdx
+++ b/apps/docs/content/troubleshooting/understanding-the-usage-summary-on-the-dashboard-D7Gnle.mdx
@@ -30,7 +30,7 @@ Using the database size as an example, suppose we have the following projects un
If the user decides to delete Projects A and B during the current billing cycle, the average database usage will be 2 GB for the current billing cycle, even though only a 0.5 GB database is currently active.
-As of now, even if your project was just active for a few days, we take the average database size for those active days, which may lead to high usage when creating and deleting many projects within a billing cycle. We're working on a better billing model to cover these cases.
+As of now, even if your project was only active for a few days, we take the average database size for those active days, which may lead to high usage when creating and deleting many projects within a billing cycle. We're working on a better billing model to cover these cases.
## Edge Functions example
diff --git a/apps/docs/content/troubleshooting/webhook-debugging-guide-M8sk47.mdx b/apps/docs/content/troubleshooting/webhook-debugging-guide-M8sk47.mdx
index bfa806ef67f..bf32e5ee9b0 100644
--- a/apps/docs/content/troubleshooting/webhook-debugging-guide-M8sk47.mdx
+++ b/apps/docs/content/troubleshooting/webhook-debugging-guide-M8sk47.mdx
@@ -30,7 +30,7 @@ Otherwise, it is necessary to fast reboot your instance in the [Dashboard's Sett
Using `pg_net` in triggers on most tables is fine; however, do not add triggers to the `net._http_response` or `net.http_request_queue` tables.
-The `net` tables are special and if triggers on them fail or call a pg_net function (`http_get`, `http_post`, `http_delete`), it can lead to an infinite loop. This warning is irrelevant to most projects, but it's worth specifying just in case.
+The `net` tables are special and if triggers on them fail or call a pg_net function (`http_get`, `http_post`, `http_delete`), it can lead to an infinite loop. This warning is irrelevant to most projects, but it's worth specifying in case.
**3. Check for timeout errors**
@@ -56,7 +56,7 @@ body := '{"key1": "value", "key2": 5}'::jsonb
) as request_id;
```
-Postman will then respond with the same payload. This is just a test to confirm that requests are being properly formatted and going through.
+Postman will then respond with the same payload. This is a test to confirm that requests are being properly formatted and going through.
You can then view the request in the `net._http_response table` in the [Table Editor](/dashboard/project/_/editor) or with the following SQL:
diff --git a/apps/docs/content/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx b/apps/docs/content/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx
index 6191835cbca..5e2c4be8278 100644
--- a/apps/docs/content/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx
+++ b/apps/docs/content/troubleshooting/why-is-my-select-returning-an-empty-data-array-and-i-have-data-in-the-table-xvOPgx.mdx
@@ -9,9 +9,9 @@ database_id = "6fa68831-6db8-4836-a1b4-e9e51296d529"
Usually this means you have RLS (row level security) enabled and no policy, or do not meet the policy. It can also mean you have a filter and have no rows matching that.
-If you have RLS enabled you can test quickly by disable RLS on the table. If your query works then you have no policies or do not meet them.
+If you have RLS enabled you can test by disabling RLS on the table. If your query works then you have no policies or do not meet them.
-If you have policies that depend on having a signed in user (TO set to `authenticated` or using `auth.uid()`) then you can check by setting TO as `anon` and setting your policy to just `TRUE`. If that works then you don't have a signed in user (with a JWT in the authorization header) when you make the call.
+If you have policies that depend on having a signed in user (TO set to `authenticated` or using `auth.uid()`) then you can check by setting TO as `anon` and setting your policy to `TRUE`. If that works then you don't have a signed in user (with a JWT in the authorization header) when you make the call.
For more information on RLS see https://supabase.com/docs/guides/auth/row-level-security
diff --git a/apps/docs/data/content-listings/auth.data.ts b/apps/docs/data/content-listings/auth.data.ts
new file mode 100644
index 00000000000..e70d1cd5836
--- /dev/null
+++ b/apps/docs/data/content-listings/auth.data.ts
@@ -0,0 +1,102 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+export const authGetStarted: ContentListingGroup = {
+ id: 'auth-get-started',
+ heading: 'Get started',
+ description: "Start here if you're new to Supabase Auth:",
+ type: 'grid',
+ items: [
+ {
+ title: 'Auth with email and password',
+ href: '/guides/auth/passwords',
+ description: 'Sign up and sign in users with email and password.',
+ },
+ {
+ title: 'Server-side rendering',
+ href: '/guides/auth/server-side',
+ description: 'Create a Supabase client for SSR frameworks like Next.js and SvelteKit.',
+ },
+ {
+ title: 'Row Level Security',
+ href: '/guides/database/postgres/row-level-security',
+ description: 'Use RLS policies to authorize data access from the client.',
+ },
+ ],
+}
+
+export const authPricing: ContentListingGroup = {
+ id: 'auth-pricing',
+ heading: 'Pricing',
+ description:
+ 'Charges apply to Monthly Active Users (MAU), Monthly Active Third-Party Users (Third-Party MAU), and Monthly Active SSO Users (SSO MAU) and Advanced MFA Add-ons. For a detailed breakdown of how these charges are calculated, refer to the following pages.',
+ type: 'list',
+ items: [
+ {
+ title: 'Pricing MAU',
+ href: '/guides/platform/manage-your-usage/monthly-active-users',
+ description: 'How MAU usage is measured and billed.',
+ },
+ {
+ title: 'Pricing Third-Party MAU',
+ href: '/guides/platform/manage-your-usage/monthly-active-users-third-party',
+ description: 'How third-party auth MAU is measured and billed.',
+ },
+ {
+ title: 'Pricing SSO MAU',
+ href: '/guides/platform/manage-your-usage/monthly-active-users-sso',
+ description: 'How SSO MAU usage is measured and billed.',
+ },
+ {
+ title: 'Advanced MFA - Phone',
+ href: '/guides/platform/manage-your-usage/advanced-mfa-phone',
+ description: 'How Advanced MFA Phone add-on usage is measured and billed.',
+ },
+ ],
+}
+
+export const authNextSteps: ContentListingGroup = {
+ id: 'auth-next-steps',
+ heading: 'Next steps',
+ description:
+ "Once you've covered the basics, these guides help with other use cases and features:",
+ type: 'grid',
+ columns: 4,
+ items: [
+ {
+ title: 'Email (Magic link or OTP)',
+ href: '/guides/auth/auth-email-passwordless',
+ description:
+ 'Sign up and sign in users with a Magic Link or email OTP instead of a password.',
+ },
+ {
+ title: 'Enterprise SSO',
+ href: '/guides/auth/enterprise-sso',
+ description: 'Add Single Sign-On for enterprise applications with SAML 2.0.',
+ },
+ {
+ title: 'User sessions',
+ href: '/guides/auth/sessions',
+ description: 'Control session lifetime, refresh tokens, and multi-device sign-in behavior.',
+ },
+ {
+ title: 'Third-party auth',
+ href: '/guides/auth/third-party/overview',
+ description: 'Use Clerk, Auth0, Firebase Auth, Cognito, or WorkOS JWTs with Supabase APIs.',
+ },
+ {
+ title: 'Multi-factor authentication',
+ href: '/guides/auth/auth-mfa',
+ description: 'Add a second factor to user sign-in with TOTP or phone.',
+ },
+ {
+ title: 'JWTs',
+ href: '/guides/auth/jwts',
+ description: 'Understand how Supabase Auth issues and validates JWTs.',
+ },
+ {
+ title: 'Auth Hooks',
+ href: '/guides/auth/auth-hooks',
+ description: 'Customize Auth behavior with Postgres functions at key lifecycle points.',
+ },
+ ],
+}
diff --git a/apps/docs/data/content-listings/database.data.ts b/apps/docs/data/content-listings/database.data.ts
new file mode 100644
index 00000000000..32961aed8fb
--- /dev/null
+++ b/apps/docs/data/content-listings/database.data.ts
@@ -0,0 +1,90 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+export const databaseGetStarted: ContentListingGroup = {
+ id: 'database-get-started',
+ heading: 'Get started',
+ description: "If you're new to the database section, these are the pages to read first:",
+ type: 'grid',
+ columns: 2,
+ items: [
+ {
+ title: 'Connect to your database',
+ href: '/guides/database/connecting-to-postgres',
+ description:
+ 'Connection strings, the Supavisor connection pooler, and when to use direct, transaction, or session mode.',
+ },
+ {
+ title: 'Tables and data',
+ href: '/guides/database/tables',
+ description: 'Create tables and relationships, and edit rows from the Dashboard.',
+ },
+ {
+ title: 'Import data',
+ href: '/guides/database/import-data',
+ description: 'Load existing data from CSV files, `pg_dump`, or another Postgres database.',
+ },
+ {
+ title: 'Secure your data',
+ href: '/guides/database/secure-data',
+ description:
+ 'Row Level Security (RLS) is how Supabase makes the database safe to query directly from the client. Read this before exposing any table to your app.',
+ },
+ {
+ title: 'Extensions',
+ href: '/guides/database/extensions',
+ description:
+ 'Add Postgres extensions from the Dashboard, including `pgvector` for embeddings, `PostGIS` for geospatial data, and `pg_cron` for scheduled jobs.',
+ },
+ {
+ title: 'Run SQL commands',
+ href: '/dashboard/project/_/sql',
+ description: "Use the Dashboard's SQL Editor for ad-hoc queries and saved snippets.",
+ },
+ ],
+}
+
+export const databaseNextSteps: ContentListingGroup = {
+ id: 'database-next-steps',
+ heading: 'Next steps',
+ description:
+ "Once you've covered the basics, these guides help with other use cases and features:",
+ type: 'grid',
+ items: [
+ {
+ title: 'Database functions',
+ href: '/guides/database/functions',
+ description: 'Run logic inside the database in response to inserts, updates, or deletes.',
+ },
+ {
+ title: 'Triggers',
+ href: '/guides/database/postgres/triggers',
+ description: 'Run logic inside the database in response to inserts, updates, or deletes.',
+ },
+ {
+ title: 'Database webhooks',
+ href: '/guides/database/webhooks',
+ description: 'Send row changes to an external HTTP endpoint.',
+ },
+ {
+ title: 'Replication and read replicas',
+ href: '/guides/database/replication',
+ description: 'Stream changes to other systems or read from a geographically closer replica.',
+ },
+ {
+ title: 'Backups',
+ href: '/guides/platform/backups',
+ description:
+ 'Daily backups on every project, with point-in-time recovery on paid plans. Backups cover the database itself; objects stored through the Storage API are not included.',
+ },
+ {
+ title: 'Query performance and optimization',
+ href: '/guides/database/query-optimization',
+ description: 'Indexes, the query planner, and tools for finding slow queries.',
+ },
+ {
+ title: 'Roles and permissions',
+ href: '/guides/database/postgres/roles',
+ description: 'The Postgres roles Supabase ships with and how to add your own.',
+ },
+ ],
+}
diff --git a/apps/docs/data/content-listings/functions.data.ts b/apps/docs/data/content-listings/functions.data.ts
new file mode 100644
index 00000000000..bb69d870704
--- /dev/null
+++ b/apps/docs/data/content-listings/functions.data.ts
@@ -0,0 +1,211 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+export const functionsGetStarted: ContentListingGroup = {
+ id: 'functions-get-started',
+ heading: 'Get started',
+ type: 'grid',
+ columns: 2,
+ items: [
+ {
+ title: 'Edge Functions quickstart',
+ href: '/guides/functions/quickstart',
+ description: 'Create, test, and deploy your first Edge Function with the Supabase CLI.',
+ },
+ ],
+}
+
+export const functionsExamplesSupabase: ContentListingGroup = {
+ id: 'functions-examples-supabase',
+ heading: 'Supabase integration',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'With supabase-js',
+ href: '/guides/functions/auth',
+ description: 'Use the Supabase client inside your Edge Function.',
+ },
+ {
+ title: 'Connect to Postgres',
+ href: '/guides/functions/connect-to-postgres',
+ description: 'Connect to Postgres from Edge Functions.',
+ },
+ {
+ title: 'Type-Safe SQL with Kysely',
+ href: '/guides/functions/kysely-postgres',
+ description:
+ 'Combine Kysely with Deno Postgres for a convenient developer experience when interacting directly with your Postgres database.',
+ },
+ {
+ title: 'With CORS headers',
+ href: '/guides/functions/cors',
+ description: 'Send CORS headers for invoking from the browser.',
+ },
+ {
+ title: 'Building a RESTful Service API',
+ href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/restful-tasks/index.ts',
+ icon: '/docs/img/icons/github-icon',
+ description:
+ 'Learn how to use HTTP methods and paths to build a RESTful service for managing tasks.',
+ },
+ {
+ title: 'Oak Server Middleware',
+ href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/oak-server',
+ icon: '/docs/img/icons/github-icon',
+ description: 'Route requests with Oak server middleware.',
+ },
+ {
+ title: 'Web Stream',
+ href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/streams',
+ icon: '/docs/img/icons/github-icon',
+ description: 'Stream Server-Sent Events from Edge Functions.',
+ },
+ {
+ title: 'Get User Location',
+ href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/location',
+ icon: '/docs/img/icons/github-icon',
+ description: "Get user location data from user's IP address.",
+ },
+ {
+ title: 'Working with Supabase Storage',
+ href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/read-storage/index.ts',
+ icon: '/docs/img/icons/github-icon',
+ description: 'Read a file from Supabase Storage.',
+ },
+ {
+ title: 'Upload File',
+ href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/file-upload-storage',
+ icon: '/docs/img/icons/github-icon',
+ description: 'Process multipart/form-data.',
+ },
+ ],
+}
+
+export const functionsExamplesWebhooksPayments: ContentListingGroup = {
+ id: 'functions-examples-webhooks-payments',
+ heading: 'Webhooks & payments',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'Stripe Webhooks',
+ href: '/guides/functions/examples/stripe-webhooks',
+ description: 'Handle signed Stripe webhooks with Edge Functions.',
+ },
+ {
+ title: 'React Native with Stripe',
+ href: 'https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions',
+ icon: '/docs/img/icons/github-icon',
+ description: 'Use Supabase and Stripe in a React Native app with Expo.',
+ },
+ {
+ title: 'Flutter with Stripe',
+ href: 'https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions',
+ icon: '/docs/img/icons/github-icon',
+ description: 'Use Supabase and Stripe in a Flutter app.',
+ },
+ ],
+}
+
+export const functionsExamplesAiMedia: ContentListingGroup = {
+ id: 'functions-examples-ai-media',
+ heading: 'AI & media',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'Hugging Face',
+ href: '/guides/ai/examples/huggingface-image-captioning',
+ description: 'Access 100,000+ Machine Learning models.',
+ },
+ {
+ title: 'OpenAI',
+ href: '/guides/ai/examples/openai',
+ description: 'Use OpenAI in Edge Functions.',
+ },
+ {
+ title: 'Amazon Bedrock',
+ href: '/guides/functions/examples/amazon-bedrock-image-generator',
+ description: 'Generate images with Amazon Bedrock in Edge Functions.',
+ },
+ {
+ title: 'Open Graph Image Generation',
+ href: '/guides/functions/examples/og-image',
+ description: 'Generate Open Graph images with Deno and Supabase Edge Functions.',
+ },
+ {
+ title: 'OG Image Generation & Storage CDN Caching',
+ href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/og-image-with-storage-cdn',
+ icon: '/docs/img/icons/github-icon',
+ description: 'Cache generated images with Supabase Storage CDN.',
+ },
+ {
+ title: 'Puppeteer',
+ href: '/guides/functions/examples/screenshots',
+ description: 'Generate screenshots with Puppeteer.',
+ },
+ ],
+}
+
+export const functionsExamplesMessaging: ContentListingGroup = {
+ id: 'functions-examples-messaging',
+ heading: 'Bots & email',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'Send emails',
+ href: '/guides/functions/examples/send-emails',
+ description: 'Send emails in Edge Functions with Resend.',
+ },
+ {
+ title: 'Discord Bot',
+ href: '/guides/functions/examples/discord-bot',
+ description: 'Build a slash command Discord bot with Edge Functions.',
+ },
+ {
+ title: 'Telegram Bot',
+ href: '/guides/functions/examples/telegram-bot',
+ description: 'Build a Telegram bot with Edge Functions.',
+ },
+ {
+ title: 'Slack Bot Mention Edge Function',
+ href: '/guides/functions/examples/slack-bot-mention',
+ description: 'Handle Slack mentions in a Slack bot Edge Function.',
+ },
+ ],
+}
+
+export const functionsExamplesOperations: ContentListingGroup = {
+ id: 'functions-examples-operations',
+ heading: 'Operations & security',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'Monitoring with Sentry',
+ href: '/guides/functions/examples/sentry-monitoring',
+ description: 'Monitor Edge Functions with the Sentry Deno SDK.',
+ },
+ {
+ title: 'GitHub Actions',
+ href: '/guides/functions/examples/github-actions',
+ description: 'Deploy Edge Functions with GitHub Actions.',
+ },
+ {
+ title: 'Upstash Redis',
+ href: '/guides/functions/examples/upstash-redis',
+ description: 'Build an Edge Functions Counter with Upstash Redis.',
+ },
+ {
+ title: 'Rate Limiting',
+ href: '/guides/functions/examples/rate-limiting',
+ description: 'Rate-limit Edge Functions with Upstash Redis.',
+ },
+ {
+ title: 'Cloudflare Turnstile',
+ href: '/guides/functions/examples/cloudflare-turnstile',
+ description: 'Protect forms with Cloudflare Turnstile.',
+ },
+ ],
+}
diff --git a/apps/docs/data/content-listings/getting-started.data.ts b/apps/docs/data/content-listings/getting-started.data.ts
new file mode 100644
index 00000000000..33d36e08e8b
--- /dev/null
+++ b/apps/docs/data/content-listings/getting-started.data.ts
@@ -0,0 +1,23 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+export const gettingStartedGetStarted: ContentListingGroup = {
+ id: 'getting-started-overview',
+ type: 'grid',
+ items: [
+ {
+ title: 'Build with AI tools',
+ href: '/guides/ai-tools',
+ description: 'Develop with Supabase AI-first using plugins, MCP, and skills.',
+ },
+ {
+ title: 'API Keys',
+ href: '/guides/getting-started/api-keys',
+ description: 'Learn about the different API keys in Supabase and how to use them.',
+ },
+ {
+ title: 'Local Development',
+ href: '/guides/local-development',
+ description: 'Use the Supabase CLI to develop locally and collaborate between teams.',
+ },
+ ],
+}
diff --git a/apps/docs/data/content-listings/index.ts b/apps/docs/data/content-listings/index.ts
new file mode 100644
index 00000000000..b5ac8a84d09
--- /dev/null
+++ b/apps/docs/data/content-listings/index.ts
@@ -0,0 +1,40 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+import { authGetStarted, authNextSteps, authPricing } from './auth.data'
+import { databaseGetStarted, databaseNextSteps } from './database.data'
+import {
+ functionsExamplesAiMedia,
+ functionsExamplesMessaging,
+ functionsExamplesOperations,
+ functionsExamplesSupabase,
+ functionsExamplesWebhooksPayments,
+ functionsGetStarted,
+} from './functions.data'
+import { gettingStartedGetStarted } from './getting-started.data'
+import { realtimeExamples, realtimeGetStarted, realtimeResources } from './realtime.data'
+import { storageExamples, storageGetStarted, storageResources } from './storage.data'
+
+const ALL_GROUPS: readonly ContentListingGroup[] = [
+ authGetStarted,
+ authPricing,
+ authNextSteps,
+ databaseGetStarted,
+ databaseNextSteps,
+ functionsGetStarted,
+ functionsExamplesSupabase,
+ functionsExamplesWebhooksPayments,
+ functionsExamplesAiMedia,
+ functionsExamplesMessaging,
+ functionsExamplesOperations,
+ gettingStartedGetStarted,
+ realtimeGetStarted,
+ realtimeExamples,
+ realtimeResources,
+ storageGetStarted,
+ storageExamples,
+ storageResources,
+]
+
+export const CONTENT_LISTINGS: Readonly> = Object.fromEntries(
+ ALL_GROUPS.map((group) => [group.id, group])
+)
diff --git a/apps/docs/data/content-listings/realtime.data.ts b/apps/docs/data/content-listings/realtime.data.ts
new file mode 100644
index 00000000000..0e0e85b2270
--- /dev/null
+++ b/apps/docs/data/content-listings/realtime.data.ts
@@ -0,0 +1,66 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+export const realtimeGetStarted: ContentListingGroup = {
+ id: 'realtime-get-started',
+ heading: 'Get started',
+ type: 'grid',
+ columns: 2,
+ items: [
+ {
+ title: 'Getting Started',
+ href: '/guides/realtime/getting_started',
+ description: 'Set up Realtime in your project and send your first message.',
+ },
+ ],
+}
+
+export const realtimeExamples: ContentListingGroup = {
+ id: 'realtime-examples',
+ heading: 'Examples',
+ type: 'grid',
+ columns: 2,
+ items: [
+ {
+ title: 'Multiplayer.dev',
+ href: 'https://multiplayer.dev',
+ description:
+ 'Showcase application displaying cursor movements and chat messages using Broadcast.',
+ },
+ {
+ title: 'Chat',
+ href: 'https://supabase.com/ui/docs/nextjs/realtime-chat',
+ description: 'Supabase UI chat component using Broadcast to send message between users.',
+ },
+ {
+ title: 'Avatar Stack',
+ href: 'https://supabase.com/ui/docs/nextjs/realtime-avatar-stack',
+ description: 'Supabase UI avatar stack component using Presence to track connected users.',
+ },
+ {
+ title: 'Realtime Cursor',
+ href: 'https://supabase.com/ui/docs/nextjs/realtime-cursor',
+ description:
+ "Supabase UI realtime cursor component using Broadcast to share users' cursors to build collaborative applications.",
+ },
+ ],
+}
+
+export const realtimeResources: ContentListingGroup = {
+ id: 'realtime-resources',
+ heading: 'Resources',
+ description: 'Find the source code and documentation in the Supabase GitHub repository:',
+ type: 'grid',
+ columns: 2,
+ items: [
+ {
+ title: 'Supabase Realtime',
+ href: 'https://github.com/supabase/realtime',
+ description: 'View the source code.',
+ },
+ {
+ title: 'Realtime: Multiplayer Edition',
+ href: 'https://supabase.com/blog/supabase-realtime-multiplayer-general-availability',
+ description: 'Read more about Supabase Realtime.',
+ },
+ ],
+}
diff --git a/apps/docs/data/content-listings/storage.data.ts b/apps/docs/data/content-listings/storage.data.ts
new file mode 100644
index 00000000000..9180ea08d49
--- /dev/null
+++ b/apps/docs/data/content-listings/storage.data.ts
@@ -0,0 +1,72 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+export const storageGetStarted: ContentListingGroup = {
+ id: 'storage-get-started',
+ heading: 'Get started',
+ description: 'Choose the bucket type that fits your use case:',
+ type: 'grid',
+ items: [
+ {
+ title: 'Files buckets',
+ href: '/guides/storage/quickstart',
+ description:
+ 'Store and serve images, videos, documents, and general-purpose files with direct URL access and row-level security.',
+ },
+ {
+ title: 'Analytics buckets',
+ href: '/guides/storage/analytics/introduction',
+ description:
+ 'Store data in Apache Iceberg tables for data lakes, logs, and Supabase Pipelines. Query from Postgres via foreign tables with partitioning.',
+ },
+ {
+ title: 'Vector buckets',
+ href: '/guides/storage/vector/introduction',
+ description:
+ 'Store embeddings and run similarity search for semantic matching, AI, and RAG. Use HNSW indexing, distance metrics, and metadata filtering.',
+ },
+ ],
+}
+
+export const storageExamples: ContentListingGroup = {
+ id: 'storage-examples',
+ heading: 'Examples',
+ description: 'Working sample projects for common Storage integration patterns:',
+ type: 'grid',
+ columns: 2,
+ items: [
+ {
+ title: 'Storage templates and examples',
+ href: 'https://github.com/supabase/supabase/tree/master/examples/storage',
+ description:
+ 'Sample projects for resumable uploads, signed upload URLs, and serving map tiles from private buckets.',
+ },
+ {
+ title: 'Resumable Uploads with Uppy',
+ href: 'https://github.com/supabase/supabase/tree/master/examples/storage/resumable-upload-uppy',
+ icon: '/docs/img/icons/github-icon',
+ description:
+ 'Upload large files with pause-and-resume support using Uppy and the TUS protocol.',
+ },
+ ],
+}
+
+export const storageResources: ContentListingGroup = {
+ id: 'storage-resources',
+ heading: 'Resources',
+ description: 'Source code and REST API reference for the Storage service:',
+ type: 'grid',
+ columns: 2,
+ items: [
+ {
+ title: 'Supabase Storage API',
+ href: 'https://github.com/supabase/storage-api',
+ description: 'Amazon S3-compatible object storage service that stores metadata in Postgres.',
+ },
+ {
+ title: 'OpenAPI Spec',
+ href: 'https://supabase.github.io/storage/',
+ description:
+ 'Interactive reference for Storage REST endpoints, request parameters, and response schemas.',
+ },
+ ],
+}
diff --git a/apps/docs/docs/ref/server/installing.mdx b/apps/docs/docs/ref/server/installing.mdx
new file mode 100644
index 00000000000..0f0df8a9b91
--- /dev/null
+++ b/apps/docs/docs/ref/server/installing.mdx
@@ -0,0 +1,84 @@
+---
+id: installing
+title: Installing
+slug: installing
+---
+
+### Install as a package
+
+
+
+
+ Install `@supabase/server` via your package manager.
+
+
+
+
+
+
+
+
+ ```sh Terminal
+ npm install @supabase/server
+ ```
+
+
+
+
+ ```sh Terminal
+ yarn add @supabase/server
+ ```
+
+
+
+
+ ```sh Terminal
+ pnpm add @supabase/server
+ ```
+
+
+
+
+
+
+
+### Use via JSR (Deno / Bun)
+
+
+
+
+ `@supabase/server` is also published to [JSR](https://jsr.io/@supabase/server) for Deno and Bun environments.
+
+
+
+
+
+
+
+
+ ```sh Terminal
+ deno add jsr:@supabase/server
+ ```
+
+
+
+
+ ```sh Terminal
+ bunx jsr add @supabase/server
+ ```
+
+
+
+
+
+
diff --git a/apps/docs/docs/ref/server/introduction.mdx b/apps/docs/docs/ref/server/introduction.mdx
new file mode 100644
index 00000000000..eaacb0c51dd
--- /dev/null
+++ b/apps/docs/docs/ref/server/introduction.mdx
@@ -0,0 +1,8 @@
+---
+id: introduction
+title: Introduction
+---
+
+`@supabase/server` is a framework-agnostic library for authenticating requests in server-side JavaScript environments. It verifies JWTs, resolves Supabase API keys, and creates pre-configured Supabase clients — exposing everything through a single `SupabaseContext` that is identical regardless of which adapter or primitive produced it.
+
+Adapters for Hono, H3, Elysia, and NestJS are included. You can also compose the lower-level primitives directly for custom frameworks or edge runtimes.
diff --git a/apps/docs/features/docs/GuidesMdx.client.tsx b/apps/docs/features/docs/GuidesMdx.client.tsx
index 546e4342e01..2ab13db5fb4 100644
--- a/apps/docs/features/docs/GuidesMdx.client.tsx
+++ b/apps/docs/features/docs/GuidesMdx.client.tsx
@@ -1,7 +1,8 @@
'use client'
import { useEffect, useState, type PropsWithChildren } from 'react'
-import { AnchorProvider } from 'ui-patterns'
+import { AnchorProvider } from 'ui-patterns/Toc'
+
import { TocAnchorsContext, useSubscribeTocRerender } from './GuidesMdx.state'
interface TOCHeader {
diff --git a/apps/docs/features/docs/GuidesMdx.template.tsx b/apps/docs/features/docs/GuidesMdx.template.tsx
index c3d5a5e2c3c..6ccbf687218 100644
--- a/apps/docs/features/docs/GuidesMdx.template.tsx
+++ b/apps/docs/features/docs/GuidesMdx.template.tsx
@@ -82,7 +82,7 @@ const GuideTemplate = ({
'relative',
'transition-all ease-out',
'duration-100',
- 'col-span-12 md:col-span-9'
+ 'col-span-12 md:col-span-8'
)}
>
{breadcrumbJsonLd && (
@@ -91,21 +91,22 @@ const GuideTemplate = ({
dangerouslySetInnerHTML={{ __html: breadcrumbJsonLd }}
/>
)}
-
+
-
- {meta?.title || 'Supabase Docs'}
-
- {meta?.subtitle && (
-
- {meta.subtitle}
-
- )}
-
+
+
+ {meta?.title || 'Supabase Docs'}
+
+ {meta?.subtitle && (
+
+ {meta.subtitle}
+
+ )}
+
{content && (
x} />
@@ -120,7 +121,7 @@ const GuideTemplate = ({
className={cn(
'w-fit',
'flex items-center gap-1',
- 'text-sm text-scale-1000 hover:text-scale-1200',
+ 'text-sm text-tertiary-foreground hover:text-foreground',
'transition-colors'
)}
target="_blank"
@@ -136,16 +137,17 @@ const GuideTemplate = ({
hideToc={hideToc}
className={cn(
'hidden md:flex',
- 'col-span-3 self-start',
+ 'md:col-span-3 md:col-start-10',
+ 'self-start',
'sticky',
/**
* --header-height: height of nav
* 1px: height of nav border
- * 2rem: content padding
+ * 3rem: content padding
*/
- 'top-[calc(var(--header-height)+1px+2rem)]',
- // 3rem accounts for 2rem of top padding + 1rem of extra breathing room
- 'max-h-[calc(100vh-var(--header-height)-3rem)]'
+ 'top-[calc(var(--header-height)+1px+3rem)]',
+ // 4rem accounts for 3rem of top padding + 1rem of extra breathing room
+ 'max-h-[calc(100vh-var(--header-height)-4rem)]'
)}
/>
diff --git a/apps/docs/features/docs/MdxBase.shared.tsx b/apps/docs/features/docs/MdxBase.shared.tsx
index b47ebf114e8..7bd5a9d1b12 100644
--- a/apps/docs/features/docs/MdxBase.shared.tsx
+++ b/apps/docs/features/docs/MdxBase.shared.tsx
@@ -5,8 +5,11 @@ import AuthProviders from '~/components/AuthProviders'
import { AuthSmsProviderConfig } from '~/components/AuthSmsProviderConfig'
import ButtonCard from '~/components/ButtonCard'
import { ComputeDiskLimitsTable } from '~/components/ComputeDiskLimitsTable'
+import { CustomContent } from '~/components/CustomContent'
+import { ContentListings } from '~/components/ContentListings'
import { Extensions } from '~/components/Extensions'
import Image, { type ImageProps } from '~/components/Image'
+import { McpCiConfigBlock } from '~/components/McpCiConfigBlock'
import { Mermaid } from '~/components/Mermaid'
import { MetricsStackCards } from '~/components/MetricsStackCards'
import { NavData } from '~/components/NavData'
@@ -26,6 +29,7 @@ import { ShowUntil } from '~/features/ui/ShowUntil'
import { TabPanel, Tabs } from '~/features/ui/Tabs'
import { ArrowDown, Check, X } from 'lucide-react'
import Link from 'next/link'
+import { type ComponentPropsWithoutRef } from 'react'
import { Badge, Button } from 'ui'
import { Admonition, type AdmonitionProps } from 'ui-patterns/admonition'
import { GlassPanel } from 'ui-patterns/GlassPanel'
@@ -74,6 +78,8 @@ const components = {
CodeSampleDummy,
CodeSampleWrapper,
ComputeDiskLimitsTable,
+ CustomContent,
+ ContentListings,
ErrorCodes,
Extensions,
GlassPanel,
@@ -83,6 +89,7 @@ const components = {
IconX: X,
Image: (props: ImageProps) => ,
Link,
+ McpCiConfigBlock,
McpConfigPanel,
Mermaid,
MetricsStackCards,
@@ -100,17 +107,17 @@ const components = {
TabPanel,
InfoTooltip,
a: MdxAnchor,
- h2: (props: any) => (
+ h2: (props: ComponentPropsWithoutRef<'h2'>) => (
{props.children}
),
- h3: (props: any) => (
+ h3: (props: ComponentPropsWithoutRef<'h3'>) => (
{props.children}
),
- h4: (props: any) => (
+ h4: (props: ComponentPropsWithoutRef<'h4'>) => (
{props.children}
diff --git a/apps/docs/features/docs/Reference.constants.ts b/apps/docs/features/docs/Reference.constants.ts
index f9133fa6cab..ec3e861fb90 100644
--- a/apps/docs/features/docs/Reference.constants.ts
+++ b/apps/docs/features/docs/Reference.constants.ts
@@ -16,4 +16,4 @@
* not listed here keeps reading from the legacy `features/docs/generated/`
* outputs.
*/
-export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set(['javascript-v2'])
+export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set(['javascript-v2', 'dart-v2', 'server-v1'])
diff --git a/apps/docs/features/docs/Reference.sections.tsx b/apps/docs/features/docs/Reference.sections.tsx
index 0a98ec56b68..98654285c45 100644
--- a/apps/docs/features/docs/Reference.sections.tsx
+++ b/apps/docs/features/docs/Reference.sections.tsx
@@ -26,14 +26,7 @@ import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock'
import { isFeatureEnabled } from 'common'
import { Fragment } from 'react'
import ReactMarkdown from 'react-markdown'
-import {
- Badge,
- cn,
- Tabs_Shadcn_,
- TabsContent_Shadcn_,
- TabsList_Shadcn_,
- TabsTrigger_Shadcn_,
-} from 'ui'
+import { Badge, cn, Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
import { type IApiEndPoint } from './Reference.api.utils'
import { RefInternalLink } from './Reference.navigation.client'
@@ -234,10 +227,10 @@ async function CliCommandSection({ link, section }: CliCommandSectionProps) {
{'examples' in command &&
Array.isArray(command.examples) &&
command.examples.length > 0 && (
-
-
+
+
{command.examples.map((example) => (
-
{example.name}
-
+
))}
-
+
{command.examples.map((example) => (
-
+
{example.code}
Response
{example.response}
-
+
))}
-
+
)}
@@ -536,10 +529,10 @@ async function FunctionSection({
if (examples.length === 0) return null
return (
-
-
+
+
{examples.map((example) => (
-
{example.name}
-
+
))}
-
+
{examples.map((example) => (
-
+
{'data' in example && !!example.data?.sql && (
@@ -573,9 +566,9 @@ async function FunctionSection({
/>
)}
-
+
))}
-
+
)
})()}
diff --git a/apps/docs/features/docs/Reference.typeSpec.ts b/apps/docs/features/docs/Reference.typeSpec.ts
index 9bd84b6f56e..0c8e389e77b 100644
--- a/apps/docs/features/docs/Reference.typeSpec.ts
+++ b/apps/docs/features/docs/Reference.typeSpec.ts
@@ -186,6 +186,7 @@ export const KIND_CONSTRUCTOR = 512
export const KIND_PROPERTY = 1024
export const KIND_METHOD = 2048
export const KIND_TYPE_LITERAL = 65536
+export const KIND_TYPE_ALIAS = 2097152
/**
*
@@ -259,6 +260,25 @@ export function normalizeComment(
}
}
+ // Surface @deprecated so deprecated-only symbols (e.g. type aliases whose
+ // only doc content is a @deprecated tag) still render a description instead
+ // of an empty card. Inline {@link X} parts fall back to their text. Angle
+ // brackets are HTML-escaped because the note is rendered via MDX, which would
+ // otherwise parse a generic like `overrideTypes` as a JSX tag and throw.
+ if ('blockTags' in original && Array.isArray(original.blockTags)) {
+ const deprecatedTag = original.blockTags.find((t) => t.tag === '@deprecated')
+ if (deprecatedTag) {
+ const detail = deprecatedTag.content
+ .map((p) => p.text)
+ .join('')
+ .trim()
+ .replace(//g, '>')
+ const note = detail ? `**Deprecated.** ${detail}` : '**Deprecated.**'
+ comment.text = comment.text ? `${note}\n\n${comment.text}` : note
+ }
+ }
+
// Extract @example tags from blockTags
if ('blockTags' in original && Array.isArray(original.blockTags)) {
const exampleTags = original.blockTags.filter((t) => t.tag === '@example')
diff --git a/apps/docs/features/docs/Troubleshooting.ui.client.tsx b/apps/docs/features/docs/Troubleshooting.ui.client.tsx
index 6d1eace163e..c64191fd98f 100644
--- a/apps/docs/features/docs/Troubleshooting.ui.client.tsx
+++ b/apps/docs/features/docs/Troubleshooting.ui.client.tsx
@@ -6,7 +6,7 @@ import { useQueryStates } from 'nuqs'
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Button_Shadcn_, cn, Collapsible, CollapsibleContent, CollapsibleTrigger, Input } from 'ui'
import { MultiSelector } from 'ui-patterns/multi-select'
-import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
+import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { type ITroubleshootingMetadata } from './Troubleshooting.utils'
import {
diff --git a/apps/docs/features/ui/Accordion.tsx b/apps/docs/features/ui/Accordion.tsx
index 05e39f7616c..2efc6ed9bd4 100644
--- a/apps/docs/features/ui/Accordion.tsx
+++ b/apps/docs/features/ui/Accordion.tsx
@@ -1,6 +1,6 @@
'use client'
-import React, { ComponentProps, useState } from 'react'
+import React, { useState } from 'react'
import {
AccordionContent,
AccordionTrigger,
@@ -12,16 +12,13 @@ import {
type Type = 'default' | 'bordered'
type Align = 'left' | 'right'
-type BaseAccordionProps = ComponentProps
-
export interface AccordionProps {
children?: React.ReactNode
className?: string
defaultActiveId?: (string | number)[]
onChange?: (item: string | string[]) => void
- openBehaviour: 'single' | 'multiple'
type?: Type
- defaultValue?: string | string[] | undefined
+ defaultValue?: string[] | undefined
justified?: Boolean
chevronAlign?: Align
}
@@ -30,17 +27,15 @@ export function Accordion({
children,
className,
onChange,
- openBehaviour = 'multiple',
defaultValue = undefined,
// All props below are ignored but kept to avoid impacting all docs pages
- type = 'default',
- justified = false,
- chevronAlign = 'right',
+ // type = 'default',
+ // justified = false,
+ // chevronAlign = 'right',
}: AccordionProps) {
return (
- // @ts-expect-error: This is because the Radix component has 2 interfaces discriminated by its type prop. Safe to ignore
(null)
const { resolvedTheme } = useTheme()
const sendTelemetryEvent = useSendTelemetryEvent()
+ const { mcpServers } = getCustomContent(['mcp:servers'])
const isPlatform = selectedPlatform === 'hosted'
const project = isPlatform ? selectedProject : null
@@ -326,6 +328,8 @@ export function McpConfigPanel() {
projectRef={project?.ref}
theme={resolvedTheme as 'light' | 'dark'}
isPlatform={isPlatform}
+ platformUrl={mcpServers?.remote}
+ nonPlatformUrl={mcpServers?.local}
onCopyCallback={handleCopy}
onInstallCallback={handleInstall}
onClientSelect={setSelectedClient}
diff --git a/apps/docs/features/ui/Tabs.tsx b/apps/docs/features/ui/Tabs.tsx
index f0f3f8529d8..7f839928083 100644
--- a/apps/docs/features/ui/Tabs.tsx
+++ b/apps/docs/features/ui/Tabs.tsx
@@ -1,43 +1,273 @@
'use client'
-import { useCallback, type ComponentPropsWithoutRef, type PropsWithChildren } from 'react'
-import { Tabs as TabsPrimitive, type TabsProps } from 'ui'
-import { withQueryParams, withSticky, type QueryParamsProps } from 'ui-patterns/ComplexTabs'
-import { useTocRerenderTrigger } from '~/features/docs/GuidesMdx.state'
+import { cva } from 'class-variance-authority'
+import { Tabs as TabsPrimitive } from 'radix-ui'
+import {
+ Children,
+ isValidElement,
+ ReactElement,
+ ReactNode,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+ type KeyboardEvent,
+} from 'react'
+import { cn } from 'ui'
-const TabsWithStickyAndQueryParams = withSticky>(
- withQueryParams(TabsPrimitive)
+import { useTocRerenderTrigger } from '../docs/GuidesMdx.state'
+import { useStickyTabs, UseStickyTabsOptions } from './useStickyTabs'
+import { useTabsWithQueryParams, UseTabsWithQueryParamsOptions } from './useTabsWithQueryParams'
+
+export interface TabsProps {
+ children: ReactNode
+ type?: 'pills' | 'underlined' | 'cards' | 'rounded-pills'
+ defaultActiveId?: string
+ activeId?: string
+ size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
+ block?: boolean
+ onChange?: (id: string) => void
+ onClick?: any
+ scrollable?: boolean
+ wrappable?: boolean
+ addOnBefore?: React.ReactNode
+ addOnAfter?: React.ReactNode
+ listClassNames?: string
+ baseClassNames?: string
+}
+
+export const tabsListVariants = cva(cn('flex'), {
+ variants: {
+ type: {
+ pills: 'space-x-1',
+ underlined: 'items-center border-b border-secondary',
+ cards: '',
+ 'rounded-pills': 'flex-wrap gap-2',
+ },
+ scrollable: {
+ true: 'overflow-auto whitespace-nowrap no-scrollbar mask-fadeout-right',
+ },
+ wrappable: {
+ true: 'flex-wrap',
+ },
+ },
+})
+
+export const tabsTriggerListVariants = cva(
+ cn(
+ 'relative cursor-pointer flex items-center space-x-2 text-center transition focus:outline-hidden focus-visible:ring-3 focus-visible:ring-foreground-muted focus-visible:border-foreground-muted'
+ ),
+ {
+ variants: {
+ type: {
+ pills: 'shadow-xs rounded-sm border',
+ underlined: 'text-foreground-lighter',
+ cards: '',
+ 'rounded-pills': 'shadow-xs rounded-full',
+ },
+ size: {
+ tiny: 'text-xs px-2.5 py-1',
+ small: 'text-base md:text-sm leading-4 px-3 py-2',
+ medium: 'text-base md:text-sm px-4 py-2',
+ large: 'text-base px-4 py-2',
+ xlarge: 'text-base px-6 py-3',
+ },
+ block: {
+ true: 'w-full flex items-center justify-center',
+ },
+ isActive: {
+ false: 'hover:text-foreground',
+ },
+ },
+ compoundVariants: [
+ {
+ type: 'pills',
+ isActive: true,
+ className: 'bg-selection text-foreground border-stronger',
+ },
+ {
+ type: 'pills',
+ isActive: false,
+ className:
+ 'bg-background border-strong hover:border-foreground-muted text-foreground-muted',
+ },
+ {
+ type: 'underlined',
+ isActive: true,
+ className: '!text-foreground border-b-2 border-foreground',
+ },
+ {
+ type: 'rounded-pills',
+ isActive: true,
+ className: 'bg-foreground text-background border-foreground',
+ },
+ {
+ type: 'rounded-pills',
+ isActive: false,
+ className:
+ 'bg-surface-200 hover:bg-surface-300 hover:border-foreground-lighter text-foreground-lighter',
+ },
+ ],
+ }
)
-const TabPanel = TabsPrimitive.Panel
-const Tabs = ({
+const isString = (maybeStr: unknown): maybeStr is string => typeof maybeStr === 'string'
+
+export const Tabs = ({
+ defaultActiveId,
+ activeId,
+ type = 'pills',
+ size = 'tiny',
+ block,
onChange,
+ onClick,
+ scrollable,
+ wrappable,
+ addOnBefore,
+ addOnAfter,
+ listClassNames,
+ baseClassNames,
+ children,
+ queryGroup,
stickyTabList,
- ...props
-}: ComponentPropsWithoutRef) => {
+}: TabsProps &
+ Pick & {
+ stickyTabList?: UseStickyTabsOptions
+ }) => {
+ const childrenArr: ReactElement[] = []
+ const tabIds: string[] = []
+ Children.forEach(children, (child) => {
+ if (isValidElement(child) && isString(child.props.id)) {
+ childrenArr.push(child)
+ tabIds.push(child.props.id)
+ }
+ })
+ const { queryTab, onTabSelected: onTabSelectedForQuery } = useTabsWithQueryParams({
+ tabIds,
+ queryGroup,
+ })
+
+ const sanitizedStickyTabList = useMemo(
+ () =>
+ stickyTabList != undefined
+ ? {
+ ...stickyTabList,
+ // Magic number is the height of tab list + paragraph margin, worth getting
+ // rid of this?
+ scrollMarginTop:
+ stickyTabList.scrollMarginTop || 'calc(var(--header-height) + 43px + 20px)',
+ }
+ : undefined,
+ [stickyTabList]
+ )
+ const {
+ observedRef,
+ stickyRef,
+ onTabSelected: onTabSelectedForSticky,
+ } = useStickyTabs(sanitizedStickyTabList)
const rerenderToc = useTocRerenderTrigger()
- const onChangeInternal = useCallback(
- (...args: Parameters) => {
- rerenderToc()
- onChange?.(...args)
- },
- [rerenderToc, onChange]
+
+ const [activeTab, setActiveTab] = useState(
+ queryTab ??
+ activeId ??
+ defaultActiveId ??
+ // if no defaultActiveId is set use the first panel
+ childrenArr?.[0]?.props?.id
)
- if (stickyTabList && !stickyTabList.scrollMarginTop) {
- // Magic number is the height of tab list + paragraph margin, worth getting
- // rid of this?
- stickyTabList.scrollMarginTop = 'calc(var(--header-height) + 43px + 20px)'
- }
+ useEffect(() => {
+ // If we have a queryTab, Tabs is controller by URL params
+ if (queryTab && queryTab !== activeTab) {
+ setActiveTab(queryTab)
+ return
+ }
+ if (activeId && activeId !== activeTab) {
+ setActiveTab(activeId)
+ }
+ }, [activeId, activeTab, queryTab])
+ const onTabClick = useCallback(
+ (id: string) => {
+ if (id !== activeTab) {
+ setActiveTab(id)
+ onTabSelectedForSticky()
+ onTabSelectedForQuery(id)
+ rerenderToc()
+ onChange?.(id)
+ onClick?.(id)
+ }
+ },
+ [
+ activeTab,
+ onTabSelectedForSticky,
+ onTabSelectedForQuery,
+ rerenderToc,
+ onChange,
+ onClick,
+ setActiveTab,
+ ]
+ )
return (
-
+
+
+ {addOnBefore}
+ {childrenArr.map((tab) => {
+ const isActive = activeTab === tab.props.id
+
+ return (
+ ) => {
+ if (e.key === 'Enter') {
+ e.preventDefault()
+ onTabClick(tab.props.id)
+ }
+ }}
+ onClick={() => onTabClick(tab.props.id)}
+ key={`${tab.props.id}-tab-button`}
+ value={tab.props.id}
+ className={tabsTriggerListVariants({ type, isActive, size, block })}
+ >
+ {tab.props.icon}
+ {tab.props.label}
+ {tab.props.iconRight}
+
+ )
+ })}
+ {addOnAfter}
+
+ {childrenArr}
+
)
}
-export { TabPanel, Tabs }
+interface TabPanelProps {
+ children: ReactNode
+ id: string
+ label?: string
+ icon?: React.ReactNode
+ iconRight?: React.ReactNode
+ className?: string
+}
+
+export const TabPanel = ({ children, id, className }: TabPanelProps) => {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/docs/features/ui/guide/Guide.tsx b/apps/docs/features/ui/guide/Guide.tsx
index 9fa9b8c31c3..6c586e9297b 100644
--- a/apps/docs/features/ui/guide/Guide.tsx
+++ b/apps/docs/features/ui/guide/Guide.tsx
@@ -1,11 +1,10 @@
'use client'
-import { createContext, useContext, type ReactNode } from 'react'
-import { cn } from 'ui'
-
import GuidesTableOfContents from '~/components/GuidesSidebar'
import { TocAnchorsProvider } from '~/features/docs/GuidesMdx.client'
import { type GuideFrontmatter } from '~/lib/docs'
+import { createContext, useContext, type ReactNode } from 'react'
+import { cn } from 'ui'
interface GuideContextValue {
meta?: GuideFrontmatter
@@ -39,7 +38,7 @@ export function Guide({ meta, children, className }: GuideProps) {
'relative',
'transition-all ease-out',
'duration-100',
- hideToc ? 'col-span-12' : 'col-span-12 md:col-span-9'
+ hideToc ? 'col-span-12' : 'col-span-12 md:col-span-8'
)}
>
{children}
@@ -49,16 +48,17 @@ export function Guide({ meta, children, className }: GuideProps) {
video={meta?.tocVideo}
className={cn(
'hidden md:flex',
- 'col-span-3 self-start',
+ 'md:col-span-3 md:col-start-10',
+ 'self-start',
'sticky',
/**
* --header-height: height of nav
* 1px: height of nav border
- * 2rem: content padding
+ * 3rem: content padding
*/
- 'top-[calc(var(--header-height)+1px+2rem)]',
- // 3rem accounts for 2rem of top padding + 1rem of extra breathing room
- 'max-h-[calc(100vh-var(--header-height)-3rem)]'
+ 'top-[calc(var(--header-height)+1px+3rem)]',
+ // 4rem accounts for 3rem of top padding + 1rem of extra breathing room
+ 'max-h-[calc(100vh-var(--header-height)-4rem)]'
)}
/>
)}
diff --git a/apps/docs/features/ui/guide/GuideArticle.tsx b/apps/docs/features/ui/guide/GuideArticle.tsx
index 4b4d57d9421..b23cce3aaab 100644
--- a/apps/docs/features/ui/guide/GuideArticle.tsx
+++ b/apps/docs/features/ui/guide/GuideArticle.tsx
@@ -1,8 +1,7 @@
+import Breadcrumbs from '~/components/Breadcrumbs'
import { type ReactNode } from 'react'
import { cn } from 'ui'
-import Breadcrumbs from '~/components/Breadcrumbs'
-
interface GuideArticleProps {
children: ReactNode
className?: string
@@ -11,7 +10,7 @@ interface GuideArticleProps {
export function GuideArticle({ children, className }: GuideArticleProps) {
return (
<>
-
+
-
+
+
{meta?.title || 'Supabase Docs'}
{meta?.subtitle && (
-
+
{meta.subtitle}
-
+
)}
-
)
}
diff --git a/apps/docs/features/ui/useStickyTabs.tsx b/apps/docs/features/ui/useStickyTabs.tsx
new file mode 100644
index 00000000000..d657271a9aa
--- /dev/null
+++ b/apps/docs/features/ui/useStickyTabs.tsx
@@ -0,0 +1,45 @@
+'use client'
+
+import { useCallback, useMemo, type FC } from 'react'
+
+import { useSticky } from './useStickyTabs.utils'
+
+export type UseStickyTabsOptions = { scrollMarginTop?: string; style?: CSSStyleDeclaration }
+
+export const useStickyTabs = (options?: UseStickyTabsOptions) => {
+ const enabled = !!options
+ const { scrollMarginTop, style } = options || {}
+ const { inView, observedRef, stickyRef } = useSticky({
+ enabled,
+ style,
+ })
+
+ const onTabSelected = useCallback(() => {
+ if (enabled && inView && stickyRef.current) {
+ let elem = stickyRef.current as Element | null
+ while (elem && !elem.matches('[role="tabpanel"][data-state="active"]')) {
+ elem = elem.nextElementSibling
+ }
+ if (!elem) return
+
+ const top = elem.getBoundingClientRect().top
+ ;(elem as HTMLElement).style.scrollMarginTop = scrollMarginTop || '0px'
+ if (top < 0) {
+ elem.scrollIntoView({
+ behavior: window.matchMedia('(prefers-reduced-motion: no-preference)').matches
+ ? 'smooth'
+ : 'instant',
+ })
+ }
+ }
+ }, [enabled, inView, scrollMarginTop, stickyRef])
+
+ return useMemo(
+ () => ({
+ observedRef,
+ stickyRef,
+ onTabSelected,
+ }),
+ [observedRef, stickyRef, onTabSelected]
+ )
+}
diff --git a/packages/ui-patterns/src/ComplexTabs/withSticky.utils.ts b/apps/docs/features/ui/useStickyTabs.utils.ts
similarity index 100%
rename from packages/ui-patterns/src/ComplexTabs/withSticky.utils.ts
rename to apps/docs/features/ui/useStickyTabs.utils.ts
diff --git a/apps/docs/features/ui/useTabsWithQueryParams.tsx b/apps/docs/features/ui/useTabsWithQueryParams.tsx
new file mode 100644
index 00000000000..8d45d0dff17
--- /dev/null
+++ b/apps/docs/features/ui/useTabsWithQueryParams.tsx
@@ -0,0 +1,103 @@
+'use client'
+
+import { useSearchParamsShallow } from 'common'
+import { xor } from 'lodash-es'
+import { useCallback, useEffect, useMemo, useRef } from 'react'
+
+const LOCAL_STORAGE_KEY = 'supabase.ui-patterns.ComplexTabs.withQueryParams.v0'
+
+export interface UseTabsWithQueryParamsOptions {
+ tabIds: string[]
+ queryGroup?: string
+}
+
+/**
+ * Wraps the basic `Tabs` component from the `ui` library so it stores
+ * selection state in query params.
+ */
+export const useTabsWithQueryParams = ({ tabIds, queryGroup }: UseTabsWithQueryParamsOptions) => {
+ // Store in ref to avoid stale data in later timeout
+ const tabIdsRef = useRef(tabIds)
+ tabIdsRef.current = tabIds
+
+ // Store in ref to avoid stale data in later timeout
+ const queryGroupRef = useRef(queryGroup)
+ queryGroupRef.current = queryGroup
+
+ const searchParams = useSearchParamsShallow()
+ const queryTabMaybe = queryGroupRef.current && searchParams.get(queryGroupRef.current)
+ const queryTab =
+ queryTabMaybe && tabIdsRef.current.includes(queryTabMaybe) ? queryTabMaybe : undefined
+
+ const onTabSelected = useCallback(
+ (id: string) => {
+ if (queryGroupRef.current) {
+ if (!searchParams.getAll('queryGroups').includes(queryGroupRef.current)) {
+ searchParams.append('queryGroups', queryGroupRef.current)
+ }
+ searchParams.set(queryGroupRef.current, id)
+ }
+ },
+ [searchParams]
+ )
+
+ const checkedLocalStorage = useRef(false)
+ useEffect(() => {
+ let timeout: number | undefined
+ if (!checkedLocalStorage.current) {
+ // Timeout to avoid something (I think the router) overwriting it
+ timeout = window.setTimeout(() => {
+ if (
+ queryGroupRef.current &&
+ !new URLSearchParams(window.location.search).has(queryGroupRef.current)
+ ) {
+ try {
+ const storedValues = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '')
+ if (storedValues === null || typeof storedValues !== 'object') return
+
+ let storedValue: any = null
+ let maxDiff = tabIdsRef.current.length
+ Object.entries(storedValues).forEach(([key, value]) => {
+ const arr = key.split(',')
+ const diff = xor(arr, tabIdsRef.current)
+ if (diff.length < maxDiff) {
+ maxDiff = diff.length
+ storedValue = value
+ }
+ })
+
+ if (storedValue && tabIdsRef.current.includes(storedValue)) {
+ onTabSelected(storedValue)
+ }
+ } catch {
+ // ignore
+ }
+ }
+ }, 300)
+
+ checkedLocalStorage.current = true
+ }
+
+ if (queryGroupRef.current && queryTab) {
+ let updatedValues: Record = {}
+ try {
+ const oldValues = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '')
+ if (oldValues && typeof oldValues === 'object') {
+ updatedValues = oldValues
+ }
+ } catch {
+ // ignore
+ }
+
+ updatedValues[tabIdsRef.current.sort().join(',')] = queryTab
+
+ try {
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(updatedValues))
+ } catch {
+ // ignore
+ }
+ }
+ }, [queryTab, onTabSelected])
+
+ return useMemo(() => ({ queryTab, onTabSelected }), [queryTab, onTabSelected])
+}
diff --git a/apps/docs/fonts/index.ts b/apps/docs/fonts/index.ts
new file mode 100644
index 00000000000..77c27d95a13
--- /dev/null
+++ b/apps/docs/fonts/index.ts
@@ -0,0 +1,15 @@
+import { Inter, Manrope } from 'next/font/google'
+
+export const manrope = Manrope({
+ variable: '--font-manrope',
+ display: 'swap',
+ fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'],
+ subsets: ['latin'],
+})
+
+export const inter = Inter({
+ variable: '--font-inter',
+ display: 'swap',
+ fallback: ['system-ui', 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'],
+ subsets: ['latin'],
+})
diff --git a/apps/docs/internals/generate-guides-markdown.ts b/apps/docs/internals/generate-guides-markdown.ts
index 39abdcba099..5bdca3e1c73 100644
--- a/apps/docs/internals/generate-guides-markdown.ts
+++ b/apps/docs/internals/generate-guides-markdown.ts
@@ -1,6 +1,5 @@
import fs from 'node:fs/promises'
import path from 'node:path'
-import { globby } from 'globby'
import matter from 'gray-matter'
import type { Content, Parent, Root } from 'mdast'
import { fromMarkdown } from 'mdast-util-from-markdown'
@@ -10,13 +9,21 @@ import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx'
import { toMarkdown } from 'mdast-util-to-markdown'
import { gfm } from 'micromark-extension-gfm'
import { mdxjs } from 'micromark-extension-mdxjs'
+import { parse as parseToml } from 'smol-toml'
+import { mcpConfigPanelMarkdown as McpConfigPanel } from 'ui-patterns/McpUrlBuilder/McpConfigPanel.md'
import { addBaseUrlPrefix } from './internal-links'
+import { AccordionItem } from './markdown-schema/Accordion'
import { Admonition } from './markdown-schema/Admonition'
import { AuthProviders } from './markdown-schema/AuthProviders'
import { ComputeDiskLimitsTable } from './markdown-schema/ComputeDiskLimitsTable'
+import { ContentListings } from './markdown-schema/ContentListings'
+import { CustomContent } from './markdown-schema/CustomContent'
import { ErrorCodes } from './markdown-schema/ErrorCodes'
+import { IconCheck, IconX } from './markdown-schema/Icons'
+import { Image } from './markdown-schema/Image'
import { Link } from './markdown-schema/Link'
+import { McpCiConfigBlock } from './markdown-schema/McpCiConfigBlock'
import { MetricsStackCards } from './markdown-schema/MetricsStackCards'
import { NavData } from './markdown-schema/NavData'
import { Panel } from './markdown-schema/Panel'
@@ -26,8 +33,18 @@ import { RegionsList, SmartRegionsList } from './markdown-schema/RegionsList'
import { SharedData } from './markdown-schema/SharedData'
import { StepHike } from './markdown-schema/StepHike'
import { TabPanel } from './markdown-schema/TabPanel'
+import {
+ collectMarkdownSources,
+ type FrontmatterFormat,
+ type MarkdownSource,
+} from './markdown-sources'
const PARTIALS_DIR = path.join(process.cwd(), 'content', '_partials')
+// The set of guide slugs that have a generated markdown (.md) variant. middleware.ts
+// imports this to decide whether a bot request for /guides/ can be served
+// markdown. It lives under the gitignored public/markdown/ output dir and is
+// regenerated by build:guides-markdown, which turbo runs ahead of build/typecheck/lint.
+const MANIFEST_PATH = path.join(process.cwd(), 'public', 'markdown', 'manifest.json')
type JsxNode = MdxJsxFlowElement | MdxJsxTextElement
type Props = Record
@@ -137,11 +154,18 @@ function applySchema(parent: Parent, schema: ComponentSchema): void {
* component not listed is unwrapped (children are kept, wrapper is dropped).
*/
const SCHEMA: ComponentSchema = {
+ AccordionItem,
Admonition,
+ IconCheck,
+ IconX,
+ Image,
AuthProviders,
ComputeDiskLimitsTable,
+ CustomContent,
ErrorCodes,
Link,
+ McpCiConfigBlock,
+ McpConfigPanel,
Price,
GlassPanel: Panel,
IconPanel: Panel,
@@ -151,14 +175,19 @@ const SCHEMA: ComponentSchema = {
...StepHike,
TabPanel,
MetricsStackCards,
+ ContentListings,
NavData,
SharedData,
}
-async function generateOne(filePath: string): Promise {
- const raw = await fs.readFile(filePath, 'utf8')
- const { content, data } = matter(raw)
+function parseFrontmatter(raw: string, frontmatter: FrontmatterFormat) {
+ if (frontmatter === 'toml') {
+ return matter(raw, { language: 'toml', engines: { toml: parseToml } })
+ }
+ return matter(raw)
+}
+async function transformBody(content: string, data: Record): Promise {
const tree = parseMdx(content)
await inlinePartials(tree)
addBaseUrlPrefix(tree)
@@ -166,43 +195,42 @@ async function generateOne(filePath: string): Promise {
const body = serializeMdx(tree)
const headerParts: string[] = []
- if (data.title) headerParts.push(`# ${data.title}`)
+ if (data.title) headerParts.push(`# ${String(data.title)}`)
if (data.subtitle) headerParts.push(String(data.subtitle))
- // Add description only when differs from subtitle.
if (data.description && String(data.description) !== String(data.subtitle))
headerParts.push(String(data.description))
const header = headerParts.join('\n\n')
- return header ? `${header}\n\n${body}` : body
+ let output = header ? `${header}\n\n${body}` : body
+
+ return output
+}
+
+async function generateOne(sourceFile: string, frontmatter: FrontmatterFormat): Promise {
+ const raw = await fs.readFile(sourceFile, 'utf8')
+ const { content, data } = parseFrontmatter(raw, frontmatter)
+ return transformBody(content, data)
+}
+
+function renderManifest(sources: MarkdownSource[]): string {
+ const slugs = [...new Set(sources.map((source) => source.slug))].sort()
+ return `${JSON.stringify(slugs, null, 2)}\n`
}
async function generate() {
- const files = await globby(['content/guides/**/!(_)*.mdx'])
- let warnings = 0
+ const sources = await collectMarkdownSources()
await Promise.all(
- files.map(async (filePath) => {
- // content/guides/ai/vector-columns.mdx → public/markdown/guides/ai/vector-columns.md
- // Placing under public/markdown/ ensures the file is served at /docs/guides/...
- // matching the URL of the rendered page.
- const outPath = filePath
- .replace(/^content\/guides\//, 'public/markdown/guides/')
- .replace(/\.mdx$/, '.md')
-
+ sources.map(async ({ sourceFile, outPath, frontmatter }) => {
let output: string
try {
- output = await generateOne(filePath)
+ output = await generateOne(sourceFile, frontmatter)
} catch (err) {
- warnings++
- console.warn(
- `[warn] Failed to process ${filePath}: ${err instanceof Error ? err.message : err}`
+ throw new Error(
+ `Failed to process ${sourceFile}: ${err instanceof Error ? err.message : err}`,
+ { cause: err }
)
- try {
- output = await fs.readFile(filePath, 'utf8')
- } catch {
- output = ``
- }
}
await fs.mkdir(path.dirname(outPath), { recursive: true })
@@ -210,8 +238,15 @@ async function generate() {
})
)
- const summary = warnings ? ` (${warnings} with warnings)` : ''
- console.log(`Generated ${files.length} markdown files under public/markdown/guides/${summary}`)
+ await fs.mkdir(path.dirname(MANIFEST_PATH), { recursive: true })
+ await fs.writeFile(MANIFEST_PATH, renderManifest(sources))
+
+ console.log(
+ `Generated ${sources.length} markdown files under public/markdown/guides/ and updated public/markdown/manifest.json`
+ )
}
-generate()
+generate().catch((error) => {
+ console.error(error)
+ process.exit(1)
+})
diff --git a/apps/docs/internals/generate-reference-markdown.ts b/apps/docs/internals/generate-reference-markdown.ts
index b5470a73325..21e5cbfe593 100644
--- a/apps/docs/internals/generate-reference-markdown.ts
+++ b/apps/docs/internals/generate-reference-markdown.ts
@@ -99,12 +99,11 @@ const REFERENCES: Ref[] = [
contentDir: path.join(process.cwd(), 'content/reference/javascript/v2'),
},
{
- kind: 'sdk-legacy',
+ kind: 'sdk-new',
title: 'Dart Client Library Reference',
outFile: 'dart.md',
mdxDir: path.join(MDX_ROOT, 'dart'),
- sectionsPath: path.join(GENERATED, 'dart.v2.sections.json'),
- functionsPath: path.join(GENERATED, 'dart.v2.functions.json'),
+ contentDir: path.join(process.cwd(), 'content/reference/dart/v2'),
feature: 'sdk:dart',
},
{
diff --git a/apps/docs/internals/markdown-schema/Accordion.ts b/apps/docs/internals/markdown-schema/Accordion.ts
new file mode 100644
index 00000000000..6daa2be7f84
--- /dev/null
+++ b/apps/docs/internals/markdown-schema/Accordion.ts
@@ -0,0 +1,10 @@
+export const AccordionItem = ({
+ props,
+ children,
+}: {
+ props: Record
+ children: string
+}): string => {
+ const header = String(props.header ?? '').trim()
+ return header ? `**${header}**\n\n${children}` : children
+}
diff --git a/apps/docs/internals/markdown-schema/ContentListings.ts b/apps/docs/internals/markdown-schema/ContentListings.ts
new file mode 100644
index 00000000000..299b23c06ec
--- /dev/null
+++ b/apps/docs/internals/markdown-schema/ContentListings.ts
@@ -0,0 +1,51 @@
+import { withDocsBasePath } from '~/internals/internal-links'
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+import { getContentListingById, isExternalContentListingHref } from '~/lib/content-listings.utils'
+
+import { getInternalLinkBaseUrl } from '../internal-links'
+
+const HEADING_MARKDOWN: Record<'h2' | 'h3' | 'h4', string> = {
+ h2: '##',
+ h3: '###',
+ h4: '####',
+}
+
+export function serializeContentListingGroupToMarkdown(
+ group: ContentListingGroup,
+ linkBaseUrl: string
+): string {
+ const lines: string[] = []
+ if (group.heading) {
+ const level = group.headingLevel ?? 'h2'
+ lines.push(`${HEADING_MARKDOWN[level]} ${group.heading}`)
+ lines.push('')
+ }
+
+ if (group.description) {
+ lines.push(group.description)
+ lines.push('')
+ }
+
+ for (const item of group.items) {
+ const href = isExternalContentListingHref(item.href)
+ ? item.href
+ : `${linkBaseUrl}${withDocsBasePath(item.href)}`
+ lines.push(`- **[${item.title}](${href}):** ${item.description}`)
+ }
+
+ return lines.join('\n')
+}
+
+/**
+ * Markdown export handler for ` `. Looks up the
+ * group by id in the same data registry the React component uses.
+ */
+export const ContentListings = ({ props }: { props: Record }): string => {
+ const id = typeof props.id === 'string' ? props.id : ''
+ if (!id) return ''
+
+ const group = getContentListingById(id)
+ if (!group) return ''
+
+ return serializeContentListingGroupToMarkdown(group, getInternalLinkBaseUrl())
+}
diff --git a/apps/docs/internals/markdown-schema/CustomContent.ts b/apps/docs/internals/markdown-schema/CustomContent.ts
new file mode 100644
index 00000000000..d710384f772
--- /dev/null
+++ b/apps/docs/internals/markdown-schema/CustomContent.ts
@@ -0,0 +1,24 @@
+import { resolveSharedDataPath } from '~/components/SharedData.utils'
+import {
+ getCustomContent,
+ type CustomContent as CustomContentKey,
+} from '~/lib/custom-content/getCustomContent'
+
+export const CustomContent = ({
+ props,
+ children,
+}: {
+ props: Record
+ children: string
+}): string => {
+ const key = String(props.data ?? '') as CustomContentKey
+ const result = getCustomContent([key])
+ const value = Object.values(result)[0]
+ if (value == null) return ''
+
+ const path = children.trim()
+ if (!path) return typeof value === 'string' ? value : JSON.stringify(value)
+
+ const resolved = resolveSharedDataPath(value, path)
+ return resolved != null ? String(resolved) : ''
+}
diff --git a/apps/docs/internals/markdown-schema/Icons.ts b/apps/docs/internals/markdown-schema/Icons.ts
new file mode 100644
index 00000000000..fe77b3f0a6e
--- /dev/null
+++ b/apps/docs/internals/markdown-schema/Icons.ts
@@ -0,0 +1,2 @@
+export const IconCheck = (): string => '✅'
+export const IconX = (): string => '❌'
diff --git a/apps/docs/internals/markdown-schema/Image.ts b/apps/docs/internals/markdown-schema/Image.ts
new file mode 100644
index 00000000000..07c65b8281f
--- /dev/null
+++ b/apps/docs/internals/markdown-schema/Image.ts
@@ -0,0 +1,21 @@
+import { getInternalLinkBaseUrl, withDocsBasePath } from '../internal-links'
+
+export const Image = ({ props }: { props: Record }): string => {
+ const alt = String(props.alt ?? '')
+ const rawSrc = props.src as string
+
+ // propsFrom() always stores attribute values as strings: either the literal
+ // string value for `src="/..."`, or the raw JS expression for `src={{ dark, light }}`.
+ const srcStr = String(rawSrc ?? '')
+ let src = String(props.src)
+
+ if (!srcStr.startsWith('/')) {
+ // Object expression form: `src={{ dark: '/...', light: '/...' }}`
+ const match =
+ srcStr.match(/dark:\s*['"]([^'"]+)['"]/) ?? srcStr.match(/light:\s*['"]([^'"]+)['"]/)
+ src = match ? match[1] : ''
+ }
+
+ const baseUrl = getInternalLinkBaseUrl()
+ return `})`
+}
diff --git a/apps/docs/internals/markdown-schema/McpCiConfigBlock.ts b/apps/docs/internals/markdown-schema/McpCiConfigBlock.ts
new file mode 100644
index 00000000000..4d969af3bb2
--- /dev/null
+++ b/apps/docs/internals/markdown-schema/McpCiConfigBlock.ts
@@ -0,0 +1,9 @@
+import { buildMcpCiConfig } from '~/components/McpCiConfigBlock.utils'
+import { getCustomContent } from '~/lib/custom-content/getCustomContent'
+
+export const McpCiConfigBlock = (): string => {
+ const { mcpServers } = getCustomContent(['mcp:servers'])
+ const config = buildMcpCiConfig(mcpServers?.remote)
+
+ return '```json\n' + JSON.stringify(config, null, 2) + '\n```'
+}
diff --git a/apps/docs/internals/markdown-schema/Panel.ts b/apps/docs/internals/markdown-schema/Panel.ts
index 2962e24d403..30012844614 100644
--- a/apps/docs/internals/markdown-schema/Panel.ts
+++ b/apps/docs/internals/markdown-schema/Panel.ts
@@ -1,3 +1,14 @@
-export const Panel = ({ props }: { props: Record }): string => {
- return String(props.title)
+export const Panel = ({
+ props,
+ children,
+}: {
+ props: Record
+ children: string
+}): string => {
+ const title = String(props.title ?? '')
+ const description = children.replace(/\s+/g, ' ').trim()
+
+ if (title && description) return `${title}: ${description}`
+
+ return title || description
}
diff --git a/apps/docs/internals/markdown-schema/RealtimeLimitsEstimator.ts b/apps/docs/internals/markdown-schema/RealtimeLimitsEstimator.ts
index 7d07d8dc8a4..eda16e8c79e 100644
--- a/apps/docs/internals/markdown-schema/RealtimeLimitsEstimator.ts
+++ b/apps/docs/internals/markdown-schema/RealtimeLimitsEstimator.ts
@@ -6,7 +6,6 @@ import throughputTable from '../../data/realtime/throughput.json'
type Row = {
computeAddOn: string
- filters: boolean
rls: boolean
concurrency: number
maxDBChanges: number
@@ -27,7 +26,7 @@ ${dividerRow}
${rows
.map(
(l) =>
- `| ${l.filters ? 'Yes' : 'No'} | ${l.rls ? 'Yes' : 'No'} | ${l.concurrency.toLocaleString()} | ${l.maxDBChanges} | ${l.maxMessagesPerClient} | ${l.totalMessagesPerSecond.toLocaleString()} | ${l.p95Latency}ms |`
+ `| ${l.rls ? 'Yes' : 'No'} | ${l.concurrency.toLocaleString()} | ${l.maxDBChanges} | ${l.maxMessagesPerClient} | ${l.totalMessagesPerSecond.toLocaleString()} | ${l.p95Latency}ms |`
)
.join('\n')}`
}
diff --git a/apps/docs/internals/markdown-sources.test.ts b/apps/docs/internals/markdown-sources.test.ts
new file mode 100644
index 00000000000..0db0b32dc11
--- /dev/null
+++ b/apps/docs/internals/markdown-sources.test.ts
@@ -0,0 +1,21 @@
+import { describe, expect, it } from 'vitest'
+
+import { guideSlug, troubleshootingSlug } from './markdown-sources'
+
+describe('guideSlug', () => {
+ it('strips the content/guides prefix and .mdx extension, keeping nested path', () => {
+ expect(guideSlug('content/guides/ai/vector-columns.mdx')).toBe('ai/vector-columns')
+ })
+
+ it('handles a top-level guide', () => {
+ expect(guideSlug('content/guides/getting-started.mdx')).toBe('getting-started')
+ })
+})
+
+describe('troubleshootingSlug', () => {
+ it('flattens to a troubleshooting/ slug', () => {
+ expect(troubleshootingSlug('content/troubleshooting/all-about-egress-a_Sg_e.mdx')).toBe(
+ 'troubleshooting/all-about-egress-a_Sg_e'
+ )
+ })
+})
diff --git a/apps/docs/internals/markdown-sources.ts b/apps/docs/internals/markdown-sources.ts
new file mode 100644
index 00000000000..808d29d7210
--- /dev/null
+++ b/apps/docs/internals/markdown-sources.ts
@@ -0,0 +1,42 @@
+import path from 'node:path'
+import { globby } from 'globby'
+
+export type FrontmatterFormat = 'yaml' | 'toml'
+
+export interface MarkdownSource {
+ sourceFile: string
+ slug: string
+ outPath: string
+ frontmatter: FrontmatterFormat
+}
+
+const OUTPUT_ROOT = 'public/markdown/guides'
+const GUIDES_GLOB = 'content/guides/**/!(_)*.mdx'
+const TROUBLESHOOTING_GLOB = 'content/troubleshooting/!(_)*.mdx'
+
+export function guideSlug(sourceFile: string): string {
+ return sourceFile.replace(/^content\/guides\//, '').replace(/\.mdx$/, '')
+}
+
+export function troubleshootingSlug(sourceFile: string): string {
+ return `troubleshooting/${path.basename(sourceFile, '.mdx')}`
+}
+
+export async function collectMarkdownSources(): Promise {
+ const [guideFiles, troubleshootingFiles] = await Promise.all([
+ globby([GUIDES_GLOB]),
+ globby([TROUBLESHOOTING_GLOB]),
+ ])
+
+ const guides: MarkdownSource[] = guideFiles.map((sourceFile) => {
+ const slug = guideSlug(sourceFile)
+ return { sourceFile, slug, outPath: `${OUTPUT_ROOT}/${slug}.md`, frontmatter: 'yaml' }
+ })
+
+ const troubleshooting: MarkdownSource[] = troubleshootingFiles.map((sourceFile) => {
+ const slug = troubleshootingSlug(sourceFile)
+ return { sourceFile, slug, outPath: `${OUTPUT_ROOT}/${slug}.md`, frontmatter: 'toml' }
+ })
+
+ return [...guides, ...troubleshooting]
+}
diff --git a/apps/docs/layouts/DefaultLayout.tsx b/apps/docs/layouts/DefaultLayout.tsx
index 4e6ccfaff51..ddc0f386a5c 100644
--- a/apps/docs/layouts/DefaultLayout.tsx
+++ b/apps/docs/layouts/DefaultLayout.tsx
@@ -4,4 +4,4 @@ import { cn } from 'ui'
export const LayoutMainContent: FC> = ({
className,
children,
-}) => {children}
+}) => {children}
diff --git a/apps/docs/layouts/MainSkeleton.tsx b/apps/docs/layouts/MainSkeleton.tsx
index 906fc2b879c..7a1c841759c 100644
--- a/apps/docs/layouts/MainSkeleton.tsx
+++ b/apps/docs/layouts/MainSkeleton.tsx
@@ -105,6 +105,10 @@ const levelsData = {
icon: 'integrations',
name: 'Integrations',
},
+ reference_server_v1: {
+ icon: 'reference-javascript',
+ name: 'Server Reference v1.0',
+ },
reference_javascript_v1: {
icon: 'reference-javascript',
name: 'JavaScript Reference v1.0',
@@ -283,8 +287,9 @@ const Container = memo(function Container({
+export type ContentListingGroup = z.infer
diff --git a/apps/docs/lib/content-listings.test.ts b/apps/docs/lib/content-listings.test.ts
new file mode 100644
index 00000000000..61a067ad726
--- /dev/null
+++ b/apps/docs/lib/content-listings.test.ts
@@ -0,0 +1,166 @@
+import { storageGetStarted } from '~/data/content-listings/storage.data'
+import {
+ ContentListings as ContentListingsMarkdownHandler,
+ serializeContentListingGroupToMarkdown,
+} from '~/internals/markdown-schema/ContentListings'
+import { isExternalContentListingHref } from '~/lib/content-listings.utils'
+import { describe, expect, it } from 'vitest'
+
+describe('isExternalContentListingHref', () => {
+ it('treats protocol-relative URLs as external', () => {
+ expect(isExternalContentListingHref('//example.com/path')).toBe(true)
+ })
+
+ it('treats absolute http(s) URLs as external', () => {
+ expect(isExternalContentListingHref('https://github.com/supabase/storage-api')).toBe(true)
+ })
+
+ it('treats internal guide paths as not external', () => {
+ expect(isExternalContentListingHref('/guides/storage/quickstart')).toBe(false)
+ })
+})
+
+describe('serializeContentListingGroupToMarkdown', () => {
+ it('renders grouped items with absolute URLs and descriptions', () => {
+ const markdown = serializeContentListingGroupToMarkdown(
+ {
+ id: 'get-started',
+ heading: 'Get started',
+ description: 'Read these first.',
+ items: [
+ {
+ title: 'Connect to your database',
+ href: '/guides/database/connecting-to-postgres',
+ description: 'Connection strings and pooler modes.',
+ },
+ ],
+ },
+ 'https://supabase.com'
+ )
+
+ expect(markdown).toContain('## Get started')
+ expect(markdown).toContain('Read these first.')
+ expect(markdown).toContain(
+ '**[Connect to your database](https://supabase.com/docs/guides/database/connecting-to-postgres):** Connection strings and pooler modes.'
+ )
+ })
+
+ it('preserves external hrefs in markdown export', () => {
+ const markdown = serializeContentListingGroupToMarkdown(
+ {
+ id: 'resources',
+ items: [
+ {
+ title: 'Storage API',
+ href: 'https://github.com/supabase/storage-api',
+ description: 'View the source code.',
+ },
+ ],
+ },
+ 'https://supabase.com'
+ )
+
+ expect(markdown).toContain(
+ '**[Storage API](https://github.com/supabase/storage-api):** View the source code.'
+ )
+ })
+
+ it('respects heading-level in markdown export', () => {
+ const markdown = serializeContentListingGroupToMarkdown(
+ {
+ id: 'get-started',
+ heading: 'Get started',
+ headingLevel: 'h3',
+ items: [
+ {
+ title: 'Connect',
+ href: '/guides/database/connecting-to-postgres',
+ description: 'Connection strings.',
+ },
+ ],
+ },
+ ''
+ )
+
+ expect(markdown).toContain('### Get started')
+ })
+
+ it('renders heading and description only once', () => {
+ const markdown = serializeContentListingGroupToMarkdown(
+ {
+ id: 'get-started',
+ heading: 'Get started',
+ description: 'Read these first.',
+ items: [
+ {
+ title: 'Connect',
+ href: '/guides/database/connecting-to-postgres',
+ description: 'Connection strings.',
+ },
+ ],
+ },
+ ''
+ )
+
+ expect(markdown.match(/^## Get started$/gm)).toHaveLength(1)
+ expect(markdown.match(/^Read these first\.$/gm)).toHaveLength(1)
+ expect(markdown).toBe(
+ '## Get started\n\nRead these first.\n\n- **[Connect](/docs/guides/database/connecting-to-postgres):** Connection strings.'
+ )
+ })
+
+ it('omits heading line when heading is not set', () => {
+ const markdown = serializeContentListingGroupToMarkdown(
+ {
+ id: 'get-started',
+ items: [
+ {
+ title: 'Connect',
+ href: '/guides/database/connecting-to-postgres',
+ description: 'Connection strings.',
+ },
+ ],
+ },
+ ''
+ )
+
+ expect(markdown).not.toMatch(/^#+\s/m)
+ expect(markdown).toContain('**[Connect]')
+ })
+})
+
+describe('ContentListings markdown handler', () => {
+ it('serializes the group looked up by id', () => {
+ const markdown = ContentListingsMarkdownHandler({ props: { id: 'storage-get-started' } })
+
+ expect(markdown).toContain('## Get started')
+ expect(markdown).toContain('Files buckets')
+ expect(markdown).toContain('/guides/storage/quickstart')
+ })
+
+ it('returns empty string when id is missing or unknown', () => {
+ expect(ContentListingsMarkdownHandler({ props: {} })).toBe('')
+ expect(ContentListingsMarkdownHandler({ props: { id: 'nonexistent-id' } })).toBe('')
+ })
+
+ it('matches direct serializeContentListingGroupToMarkdown for the same data', () => {
+ const handler = ContentListingsMarkdownHandler({ props: { id: 'storage-get-started' } })
+ const direct = serializeContentListingGroupToMarkdown(storageGetStarted, '')
+ expect(handler).toBe(direct)
+ })
+})
+
+describe('TelemetryEvent union', () => {
+ it('includes docs_content_listing_clicked', () => {
+ const event = {
+ action: 'docs_content_listing_clicked' as const,
+ properties: {
+ targetPath: '/guides/storage',
+ linkTitle: 'Storage',
+ },
+ }
+
+ const _typeCheck: import('common/telemetry-constants').TelemetryEvent = event
+ expect(_typeCheck.action).toBe('docs_content_listing_clicked')
+ })
+})
diff --git a/apps/docs/lib/content-listings.utils.ts b/apps/docs/lib/content-listings.utils.ts
new file mode 100644
index 00000000000..10c73b4d4f1
--- /dev/null
+++ b/apps/docs/lib/content-listings.utils.ts
@@ -0,0 +1,16 @@
+import { CONTENT_LISTINGS } from '~/data/content-listings'
+
+import type { ContentListingGroup } from './content-listings.schema'
+
+/** Label for telemetry — prefers heading, falls back to id. */
+export function getContentListingGroupLabel(group: ContentListingGroup): string {
+ return group.heading ?? group.id
+}
+
+export function isExternalContentListingHref(href: string): boolean {
+ return /^https?:\/\//i.test(href) || href.startsWith('//')
+}
+
+export function getContentListingById(id: string): ContentListingGroup | undefined {
+ return CONTENT_LISTINGS[id]
+}
diff --git a/apps/docs/lib/content-listings.zod.mjs b/apps/docs/lib/content-listings.zod.mjs
new file mode 100644
index 00000000000..4480879288e
--- /dev/null
+++ b/apps/docs/lib/content-listings.zod.mjs
@@ -0,0 +1,24 @@
+import { z } from 'zod'
+
+export const contentListingItemSchema = z.object({
+ title: z.string().min(1),
+ href: z.string().min(1),
+ description: z.string().min(1),
+ icon: z.string().min(1).optional(),
+})
+
+export const contentListingGroupTypeSchema = z.enum(['list', 'grid'])
+
+export const contentListingGridColumnsSchema = z.union([z.literal(2), z.literal(3), z.literal(4)])
+
+export const contentListingHeadingLevelSchema = z.enum(['h2', 'h3', 'h4'])
+
+export const contentListingGroupSchema = z.object({
+ id: z.string().min(1),
+ heading: z.string().min(1).optional(),
+ headingLevel: contentListingHeadingLevelSchema.optional(),
+ description: z.string().optional(),
+ type: contentListingGroupTypeSchema.optional(),
+ columns: contentListingGridColumnsSchema.optional(),
+ items: z.array(contentListingItemSchema).min(1),
+})
diff --git a/apps/docs/lib/custom-content/CustomContent.types.ts b/apps/docs/lib/custom-content/CustomContent.types.ts
index a936dfac862..455e38578ed 100644
--- a/apps/docs/lib/custom-content/CustomContent.types.ts
+++ b/apps/docs/lib/custom-content/CustomContent.types.ts
@@ -3,6 +3,10 @@ export type CustomContentTypes = {
metadataApplicationName: string
metadataTitle: string
cliProfile: string
+ mcpServers: {
+ local: string
+ remote: string
+ }
navigationLogo: {
light: string
dark: string
diff --git a/apps/docs/lib/custom-content/custom-content.json b/apps/docs/lib/custom-content/custom-content.json
index 54504e90117..2849811e6c1 100644
--- a/apps/docs/lib/custom-content/custom-content.json
+++ b/apps/docs/lib/custom-content/custom-content.json
@@ -5,5 +5,9 @@
"navigation:logo": {
"light": "/docs/supabase-light.svg",
"dark": "/docs/supabase-dark.svg"
+ },
+ "mcp:servers": {
+ "local": "http://localhost:54321/mcp",
+ "remote": "https://mcp.supabase.com/mcp"
}
}
diff --git a/apps/docs/middleware.ts b/apps/docs/middleware.ts
index 42817c237c7..4bbeeed8a65 100644
--- a/apps/docs/middleware.ts
+++ b/apps/docs/middleware.ts
@@ -1,11 +1,13 @@
import { clientSdkIds } from '~/content/navigation.references'
import { BASE_PATH } from '~/lib/constants'
+import MARKDOWN_SLUGS from '~/public/markdown/manifest.json'
import { negotiateMarkdown } from 'common/markdown-negotiation'
import { isbot } from 'isbot'
import { NextResponse, type NextRequest } from 'next/server'
const REFERENCE_PATH = `${BASE_PATH ?? ''}/reference`
const GUIDES_PATH = `${BASE_PATH ?? ''}/guides`
+const GUIDES_MARKDOWN_SLUGS = new Set(MARKDOWN_SLUGS)
export function middleware(request: NextRequest) {
const url = new URL(request.url)
@@ -19,7 +21,7 @@ export function middleware(request: NextRequest) {
acceptHeader: request.headers.get('accept') ?? '',
userAgent: request.headers.get('user-agent') ?? '',
},
- { hasMarkdownVariant: true, isMarkdownSuffix: isMdSuffix }
+ { hasMarkdownVariant: GUIDES_MARKDOWN_SLUGS.has(slug), isMarkdownSuffix: isMdSuffix }
)
if (decision === 'not-acceptable') {
diff --git a/apps/docs/package.json b/apps/docs/package.json
index 1a16180205f..11a8b9a54ff 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -15,12 +15,15 @@
"clean": "rimraf .next .turbo node_modules features/docs/generated examples __generated__",
"codegen:examples": "shx cp -r ../../examples ./examples",
"codegen:graphql": "tsx --conditions=react-server ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts",
- "codegen:references:new:ensure": "test -f spec/reference/javascript/v2/supabase.json || (cd spec && make download.tsdoc.v2)",
+ "codegen:references:new:ensure": "(test -f spec/reference/javascript/v2/supabase.json || (cd spec && make download.tsdoc.v2)) && (test -f spec/reference/server/v1/server.json || (cd spec && make download.server.v1))",
+ "codegen:references:dart": "tsx scripts/generate-dart-reference.ts",
+ "precodegen:references:new": "pnpm run codegen:references:new:ensure && pnpm run codegen:references:dart",
"codegen:references:new": "pnpm run codegen:references:new:ensure && tsx scripts/build-reference-content.ts",
"codegen:references:legacy": "tsx features/docs/Reference.generated.script.ts",
"codegen:references": "pnpm codegen:references:legacy && pnpm codegen:references:new",
"codemod:frontmatter": "node ./scripts/codemod/mdx-meta.mjs && prettier --cache --write \"content/**/*.mdx\"",
- "dev": "concurrently --kill-others \"next dev --port 3001\" \"pnpm run dev:watch:troubleshooting\"",
+ "dev": "run-p --race dev:next dev:watch:troubleshooting",
+ "dev:next": "next dev --port 3001",
"dev:secrets:pull": "AWS_PROFILE=supa-dev node ../../scripts/getSecrets.js -n local/docs",
"dev:watch:troubleshooting": "node ./scripts/troubleshooting/watch.mjs",
"embeddings": "tsx --conditions=react-server scripts/search/generate-embeddings.ts",
@@ -33,12 +36,15 @@
"lint:mdx": "supa-mdx-lint content --config ../../supa-mdx-lint.config.toml",
"postbuild": "pnpm run build:sitemap && ./../../scripts/upload-static-assets.sh",
"prebuild": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples && pnpm run build:markdown && pnpm run build:gz-archive",
- "predev": "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 && pnpm run build:markdown",
"preembeddings": "pnpm run codegen:references",
"preinstall": "npx only-allow pnpm",
"presync": "pnpm run codegen:graphql",
"pretest": "pnpm run codegen:examples",
- "pretypecheck": "pnpm run codegen:graphql && next typegen",
+ "pretest:local": "pnpm run build:guides-markdown",
+ "pretest:local:unwatch": "pnpm run build:guides-markdown",
+ "pretest:smoke": "pnpm run build:guides-markdown",
+ "pretypecheck": "pnpm run codegen:graphql && pnpm run build:markdown && next typegen",
"start": "next start",
"sync": "tsx --conditions=react-server ./resources/rootSync.ts",
"test": "pnpm supabase start && pnpm run test:local && pnpm supabase stop",
@@ -67,6 +73,7 @@
"acorn-typescript": "^1.4.13",
"ai-commands": "workspace:*",
"beautiful-mermaid": "^1.1.3",
+ "class-variance-authority": "^0.7.1",
"common": "workspace:*",
"common-tags": "^1.8.2",
"config": "workspace:*",
@@ -137,6 +144,7 @@
"@graphql-codegen/cli": "5.0.5",
"@graphql-codegen/typescript": "4.1.6",
"@graphql-codegen/typescript-resolvers": "4.5.0",
+ "@redocly/cli": "^2.26.0",
"@supabase/supa-mdx-lint": "0.3.2",
"@testing-library/react": "^16.0.0",
"@types/common-tags": "^1.8.4",
@@ -151,17 +159,16 @@
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "catalog:",
"@types/unist": "^2.0.6",
- "@redocly/cli": "^2.26.0",
"amaro": "^1.1.5",
"api-types": "workspace:*",
"cheerio": "^1.0.0-rc.12",
"chokidar": "^4.0.3",
- "concurrently": "^9.1.2",
"config": "workspace:*",
"dotenv": "^16.0.3",
"ejs": "^3.1.10",
"globby": "^13.2.2",
"graphiql": "^4.0.2",
+ "npm-run-all": "^4.1.5",
"openapi-types": "^12.1.3",
"postcss": "catalog:",
"shiki": "^3.2.1",
diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt
index d5b84ddf4b8..e2c713b8a4d 100644
--- a/apps/docs/public/humans.txt
+++ b/apps/docs/public/humans.txt
@@ -8,6 +8,7 @@ Adam Mohammed
Adam Mokan
AJ Matias
Akash Manimaran
+Akilesh Sethu
Alaister Young
Aleksi Immonen
Alex Hall
@@ -31,6 +32,7 @@ Artur Zakirov
Artyom Borissov
Aziz Quadri
Barco Fourie
+Barry Roodt
Beng Eu
Benjamin Coenen
Bo Lu
@@ -159,6 +161,7 @@ Julian Domke
Julien Goux
Kai M
Kalleby Santos
+Kanad Gupta
Kanishk Dudeja
Kamil Ogórek
Kang Ming Tay
@@ -184,6 +187,7 @@ Liam Cloud Hogan
Lindsay Moss
Lukas Bernert
Lukas Klingsbo
+Łukasz Korbasiewicz
Łukasz Niemier
Maksym Ionutsa
Manuel Mendez
@@ -219,6 +223,7 @@ Oli R
Pamela Chia
Patricia Szasz
Patrick Crane
+Paul Bauer
Paul Caselton
Paul Cioanca
Paul Copplestone
@@ -259,6 +264,7 @@ Sean Romberg
Sean Thompson
Sergio Cioban Filho
Shaii O
+Shane Adams
Shane E
Shreekar Shetty
Sreyas Udayavarman
diff --git a/apps/docs/scripts/__snapshots__/build-reference-content.dart.v2.json b/apps/docs/scripts/__snapshots__/build-reference-content.dart.v2.json
new file mode 100644
index 00000000000..3e4ee363394
--- /dev/null
+++ b/apps/docs/scripts/__snapshots__/build-reference-content.dart.v2.json
@@ -0,0 +1,5313 @@
+{
+ "bySlug": {
+ "introduction": {
+ "id": "introduction",
+ "title": "Introduction",
+ "slug": "introduction",
+ "type": "markdown"
+ },
+ "installing": {
+ "id": "installing",
+ "title": "Installing",
+ "slug": "installing",
+ "type": "markdown"
+ },
+ "initializing": {
+ "id": "initializing",
+ "title": "Initializing",
+ "slug": "initializing",
+ "type": "function"
+ },
+ "upgrade-guide": {
+ "id": "upgrade-guide",
+ "title": "Upgrade guide",
+ "slug": "upgrade-guide",
+ "type": "markdown"
+ },
+ "database": {
+ "type": "category",
+ "title": "Database"
+ },
+ "delete": {
+ "id": "delete",
+ "title": "delete",
+ "slug": "delete",
+ "product": "database",
+ "type": "function"
+ },
+ "insert": {
+ "id": "insert",
+ "title": "insert",
+ "slug": "insert",
+ "product": "database",
+ "type": "function"
+ },
+ "rpc": {
+ "id": "rpc",
+ "title": "rpc",
+ "slug": "rpc",
+ "product": "database",
+ "type": "function"
+ },
+ "select": {
+ "id": "select",
+ "title": "select",
+ "slug": "select",
+ "product": "database",
+ "type": "function"
+ },
+ "update": {
+ "id": "update",
+ "title": "update",
+ "slug": "update",
+ "product": "database",
+ "type": "function"
+ },
+ "upsert": {
+ "id": "upsert",
+ "title": "upsert",
+ "slug": "upsert",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters": {
+ "id": "using-filters",
+ "title": "Using filters",
+ "slug": "using-filters",
+ "type": "function"
+ },
+ "using-filters-containedby": {
+ "id": "using-filters-containedby",
+ "title": "containedBy",
+ "slug": "using-filters-containedby",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-contains": {
+ "id": "using-filters-contains",
+ "title": "contains",
+ "slug": "using-filters-contains",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-eq": {
+ "id": "using-filters-eq",
+ "title": "eq",
+ "slug": "using-filters-eq",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-filter": {
+ "id": "using-filters-filter",
+ "title": "filter",
+ "slug": "using-filters-filter",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-gt": {
+ "id": "using-filters-gt",
+ "title": "gt",
+ "slug": "using-filters-gt",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-gte": {
+ "id": "using-filters-gte",
+ "title": "gte",
+ "slug": "using-filters-gte",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-ilike": {
+ "id": "using-filters-ilike",
+ "title": "ilike",
+ "slug": "using-filters-ilike",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-infilter": {
+ "id": "using-filters-infilter",
+ "title": "inFilter",
+ "slug": "using-filters-infilter",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-isfilter": {
+ "id": "using-filters-isfilter",
+ "title": "isFilter",
+ "slug": "using-filters-isfilter",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-like": {
+ "id": "using-filters-like",
+ "title": "like",
+ "slug": "using-filters-like",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-lt": {
+ "id": "using-filters-lt",
+ "title": "lt",
+ "slug": "using-filters-lt",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-lte": {
+ "id": "using-filters-lte",
+ "title": "lte",
+ "slug": "using-filters-lte",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-match": {
+ "id": "using-filters-match",
+ "title": "match",
+ "slug": "using-filters-match",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-neq": {
+ "id": "using-filters-neq",
+ "title": "neq",
+ "slug": "using-filters-neq",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-not": {
+ "id": "using-filters-not",
+ "title": "not",
+ "slug": "using-filters-not",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-or": {
+ "id": "using-filters-or",
+ "title": "or",
+ "slug": "using-filters-or",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-overlaps": {
+ "id": "using-filters-overlaps",
+ "title": "overlaps",
+ "slug": "using-filters-overlaps",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-rangeadjacent": {
+ "id": "using-filters-rangeadjacent",
+ "title": "rangeAdjacent",
+ "slug": "using-filters-rangeadjacent",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-rangegt": {
+ "id": "using-filters-rangegt",
+ "title": "rangeGt",
+ "slug": "using-filters-rangegt",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-rangegte": {
+ "id": "using-filters-rangegte",
+ "title": "rangeGte",
+ "slug": "using-filters-rangegte",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-rangelt": {
+ "id": "using-filters-rangelt",
+ "title": "rangeLt",
+ "slug": "using-filters-rangelt",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-rangelte": {
+ "id": "using-filters-rangelte",
+ "title": "rangeLte",
+ "slug": "using-filters-rangelte",
+ "product": "database",
+ "type": "function"
+ },
+ "using-filters-textsearch": {
+ "id": "using-filters-textsearch",
+ "title": "textSearch",
+ "slug": "using-filters-textsearch",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers": {
+ "id": "using-modifiers",
+ "title": "Using modifiers",
+ "slug": "using-modifiers",
+ "type": "function"
+ },
+ "using-modifiers-csv": {
+ "id": "using-modifiers-csv",
+ "title": "csv",
+ "slug": "using-modifiers-csv",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers-explain": {
+ "id": "using-modifiers-explain",
+ "title": "explain",
+ "slug": "using-modifiers-explain",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers-limit": {
+ "id": "using-modifiers-limit",
+ "title": "limit",
+ "slug": "using-modifiers-limit",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers-maxaffected": {
+ "id": "using-modifiers-maxaffected",
+ "title": "maxAffected",
+ "slug": "using-modifiers-maxaffected",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers-maybesingle": {
+ "id": "using-modifiers-maybesingle",
+ "title": "maybeSingle",
+ "slug": "using-modifiers-maybesingle",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers-order": {
+ "id": "using-modifiers-order",
+ "title": "order",
+ "slug": "using-modifiers-order",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers-range": {
+ "id": "using-modifiers-range",
+ "title": "range",
+ "slug": "using-modifiers-range",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers-select": {
+ "id": "using-modifiers-select",
+ "title": "select",
+ "slug": "using-modifiers-select",
+ "product": "database",
+ "type": "function"
+ },
+ "using-modifiers-single": {
+ "id": "using-modifiers-single",
+ "title": "single",
+ "slug": "using-modifiers-single",
+ "product": "database",
+ "type": "function"
+ },
+ "auth": {
+ "type": "category",
+ "title": "Auth"
+ },
+ "auth-currentsession": {
+ "id": "auth-currentsession",
+ "title": "currentSession",
+ "slug": "auth-currentsession",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-currentuser": {
+ "id": "auth-currentuser",
+ "title": "currentUser",
+ "slug": "auth-currentuser",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-getuseridentities": {
+ "id": "auth-getuseridentities",
+ "title": "getUserIdentities",
+ "slug": "auth-getuseridentities",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-linkidentity": {
+ "id": "auth-linkidentity",
+ "title": "linkIdentity",
+ "slug": "auth-linkidentity",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-linkidentitywithidtoken": {
+ "id": "auth-linkidentitywithidtoken",
+ "title": "linkIdentityWithIdToken",
+ "slug": "auth-linkidentitywithidtoken",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-onauthstatechange": {
+ "id": "auth-onauthstatechange",
+ "title": "onAuthStateChange",
+ "slug": "auth-onauthstatechange",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-reauthenticate": {
+ "id": "auth-reauthenticate",
+ "title": "reauthenticate",
+ "slug": "auth-reauthenticate",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-refreshsession": {
+ "id": "auth-refreshsession",
+ "title": "refreshSession",
+ "slug": "auth-refreshsession",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-registerpasskey": {
+ "id": "auth-registerpasskey",
+ "title": "registerPasskey",
+ "slug": "auth-registerpasskey",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-resend": {
+ "id": "auth-resend",
+ "title": "resend",
+ "slug": "auth-resend",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-resetpasswordforemail": {
+ "id": "auth-resetpasswordforemail",
+ "title": "resetPasswordForEmail",
+ "slug": "auth-resetpasswordforemail",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-setsession": {
+ "id": "auth-setsession",
+ "title": "setSession",
+ "slug": "auth-setsession",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signinanonymously": {
+ "id": "auth-signinanonymously",
+ "title": "signInAnonymously",
+ "slug": "auth-signinanonymously",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signinwithidtoken": {
+ "id": "auth-signinwithidtoken",
+ "title": "signInWithIdToken",
+ "slug": "auth-signinwithidtoken",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signinwithoauth": {
+ "id": "auth-signinwithoauth",
+ "title": "signInWithOAuth",
+ "slug": "auth-signinwithoauth",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signinwithotp": {
+ "id": "auth-signinwithotp",
+ "title": "signInWithOtp",
+ "slug": "auth-signinwithotp",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signinwithpasskey": {
+ "id": "auth-signinwithpasskey",
+ "title": "signInWithPasskey",
+ "slug": "auth-signinwithpasskey",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signinwithpassword": {
+ "id": "auth-signinwithpassword",
+ "title": "signInWithPassword",
+ "slug": "auth-signinwithpassword",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signinwithsso": {
+ "id": "auth-signinwithsso",
+ "title": "signInWithSSO",
+ "slug": "auth-signinwithsso",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signout": {
+ "id": "auth-signout",
+ "title": "signOut",
+ "slug": "auth-signout",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-signup": {
+ "id": "auth-signup",
+ "title": "signUp",
+ "slug": "auth-signup",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-unlinkidentity": {
+ "id": "auth-unlinkidentity",
+ "title": "unlinkIdentity",
+ "slug": "auth-unlinkidentity",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-updateuser": {
+ "id": "auth-updateuser",
+ "title": "updateUser",
+ "slug": "auth-updateuser",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-verifyotp": {
+ "id": "auth-verifyotp",
+ "title": "verifyOtp",
+ "slug": "auth-verifyotp",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-admin": {
+ "id": "auth-admin",
+ "title": "Auth Admin",
+ "slug": "auth-admin",
+ "type": "function"
+ },
+ "auth-admin-createuser": {
+ "id": "auth-admin-createuser",
+ "title": "createUser",
+ "slug": "auth-admin-createuser",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-admin-deleteuser": {
+ "id": "auth-admin-deleteuser",
+ "title": "deleteUser",
+ "slug": "auth-admin-deleteuser",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-admin-generatelink": {
+ "id": "auth-admin-generatelink",
+ "title": "generateLink",
+ "slug": "auth-admin-generatelink",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-admin-getuserbyid": {
+ "id": "auth-admin-getuserbyid",
+ "title": "getUserById",
+ "slug": "auth-admin-getuserbyid",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-admin-inviteuserbyemail": {
+ "id": "auth-admin-inviteuserbyemail",
+ "title": "inviteUserByEmail",
+ "slug": "auth-admin-inviteuserbyemail",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-admin-listusers": {
+ "id": "auth-admin-listusers",
+ "title": "listUsers",
+ "slug": "auth-admin-listusers",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-admin-updateuserbyid": {
+ "id": "auth-admin-updateuserbyid",
+ "title": "updateUserById",
+ "slug": "auth-admin-updateuserbyid",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-mfa": {
+ "id": "auth-mfa",
+ "title": "Auth MFA",
+ "slug": "auth-mfa",
+ "type": "function"
+ },
+ "auth-mfa-challenge": {
+ "id": "auth-mfa-challenge",
+ "title": "challenge",
+ "slug": "auth-mfa-challenge",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-mfa-challengeandverify": {
+ "id": "auth-mfa-challengeandverify",
+ "title": "challengeAndVerify",
+ "slug": "auth-mfa-challengeandverify",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-mfa-enroll": {
+ "id": "auth-mfa-enroll",
+ "title": "enroll",
+ "slug": "auth-mfa-enroll",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-mfa-getauthenticatorassurancelevel": {
+ "id": "auth-mfa-getauthenticatorassurancelevel",
+ "title": "getAuthenticatorAssuranceLevel",
+ "slug": "auth-mfa-getauthenticatorassurancelevel",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-mfa-unenroll": {
+ "id": "auth-mfa-unenroll",
+ "title": "unenroll",
+ "slug": "auth-mfa-unenroll",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-mfa-verify": {
+ "id": "auth-mfa-verify",
+ "title": "verify",
+ "slug": "auth-mfa-verify",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-passkey": {
+ "id": "auth-passkey",
+ "title": "Auth Passkey",
+ "slug": "auth-passkey",
+ "type": "function"
+ },
+ "auth-passkey-delete": {
+ "id": "auth-passkey-delete",
+ "title": "delete",
+ "slug": "auth-passkey-delete",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-passkey-list": {
+ "id": "auth-passkey-list",
+ "title": "list",
+ "slug": "auth-passkey-list",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-passkey-startauthentication": {
+ "id": "auth-passkey-startauthentication",
+ "title": "startAuthentication",
+ "slug": "auth-passkey-startauthentication",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-passkey-startregistration": {
+ "id": "auth-passkey-startregistration",
+ "title": "startRegistration",
+ "slug": "auth-passkey-startregistration",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-passkey-update": {
+ "id": "auth-passkey-update",
+ "title": "update",
+ "slug": "auth-passkey-update",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-passkey-verifyauthentication": {
+ "id": "auth-passkey-verifyauthentication",
+ "title": "verifyAuthentication",
+ "slug": "auth-passkey-verifyauthentication",
+ "product": "auth",
+ "type": "function"
+ },
+ "auth-passkey-verifyregistration": {
+ "id": "auth-passkey-verifyregistration",
+ "title": "verifyRegistration",
+ "slug": "auth-passkey-verifyregistration",
+ "product": "auth",
+ "type": "function"
+ },
+ "passkey-admin": {
+ "id": "passkey-admin",
+ "title": "Passkey Admin",
+ "slug": "passkey-admin",
+ "type": "function"
+ },
+ "passkey-admin-deletepasskey": {
+ "id": "passkey-admin-deletepasskey",
+ "title": "deletePasskey",
+ "slug": "passkey-admin-deletepasskey",
+ "product": "auth",
+ "type": "function"
+ },
+ "passkey-admin-listpasskeys": {
+ "id": "passkey-admin-listpasskeys",
+ "title": "listPasskeys",
+ "slug": "passkey-admin-listpasskeys",
+ "product": "auth",
+ "type": "function"
+ },
+ "functions": {
+ "type": "category",
+ "title": "Edge Functions"
+ },
+ "functions-invoke": {
+ "id": "functions-invoke",
+ "title": "invoke",
+ "slug": "functions-invoke",
+ "product": "edge-functions",
+ "type": "function"
+ },
+ "realtime": {
+ "type": "category",
+ "title": "Realtime"
+ },
+ "getchannels": {
+ "id": "getchannels",
+ "title": "getChannels",
+ "slug": "getchannels",
+ "product": "realtime",
+ "type": "function"
+ },
+ "removeallchannels": {
+ "id": "removeallchannels",
+ "title": "removeAllChannels",
+ "slug": "removeallchannels",
+ "product": "realtime",
+ "type": "function"
+ },
+ "removechannel": {
+ "id": "removechannel",
+ "title": "removeChannel",
+ "slug": "removechannel",
+ "product": "realtime",
+ "type": "function"
+ },
+ "stream": {
+ "id": "stream",
+ "title": "stream",
+ "slug": "stream",
+ "product": "realtime",
+ "type": "function"
+ },
+ "subscribe": {
+ "id": "subscribe",
+ "title": "subscribe",
+ "slug": "subscribe",
+ "product": "realtime",
+ "type": "function"
+ },
+ "storage": {
+ "type": "category",
+ "title": "Storage"
+ },
+ "file-buckets": {
+ "id": "file-buckets",
+ "title": "File Buckets",
+ "slug": "file-buckets",
+ "type": "function"
+ },
+ "file-buckets-createbucket": {
+ "id": "file-buckets-createbucket",
+ "title": "createBucket",
+ "slug": "file-buckets-createbucket",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-createsignedurl": {
+ "id": "file-buckets-createsignedurl",
+ "title": "createSignedUrl",
+ "slug": "file-buckets-createsignedurl",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-deletebucket": {
+ "id": "file-buckets-deletebucket",
+ "title": "deleteBucket",
+ "slug": "file-buckets-deletebucket",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-download": {
+ "id": "file-buckets-download",
+ "title": "download",
+ "slug": "file-buckets-download",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-emptybucket": {
+ "id": "file-buckets-emptybucket",
+ "title": "emptyBucket",
+ "slug": "file-buckets-emptybucket",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-getbucket": {
+ "id": "file-buckets-getbucket",
+ "title": "getBucket",
+ "slug": "file-buckets-getbucket",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-getpublicurl": {
+ "id": "file-buckets-getpublicurl",
+ "title": "getPublicUrl",
+ "slug": "file-buckets-getpublicurl",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-list": {
+ "id": "file-buckets-list",
+ "title": "list",
+ "slug": "file-buckets-list",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-listbuckets": {
+ "id": "file-buckets-listbuckets",
+ "title": "listBuckets",
+ "slug": "file-buckets-listbuckets",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-move": {
+ "id": "file-buckets-move",
+ "title": "move",
+ "slug": "file-buckets-move",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-remove": {
+ "id": "file-buckets-remove",
+ "title": "remove",
+ "slug": "file-buckets-remove",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-update": {
+ "id": "file-buckets-update",
+ "title": "update",
+ "slug": "file-buckets-update",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-updatebucket": {
+ "id": "file-buckets-updatebucket",
+ "title": "updateBucket",
+ "slug": "file-buckets-updatebucket",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-upload": {
+ "id": "file-buckets-upload",
+ "title": "upload",
+ "slug": "file-buckets-upload",
+ "product": "storage",
+ "type": "function"
+ }
+ },
+ "flat": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ],
+ "sections": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ {
+ "type": "category",
+ "title": "Database",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ {
+ "id": "using-filters",
+ "isFunc": false,
+ "title": "Using filters",
+ "slug": "using-filters",
+ "product": "database",
+ "type": "function",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ },
+ {
+ "id": "using-modifiers",
+ "isFunc": false,
+ "title": "Using modifiers",
+ "slug": "using-modifiers",
+ "product": "database",
+ "type": "function",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "category",
+ "title": "Auth",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ {
+ "id": "auth-admin",
+ "isFunc": false,
+ "title": "Auth Admin",
+ "slug": "auth-admin",
+ "product": "auth",
+ "type": "function",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ },
+ {
+ "id": "auth-mfa",
+ "isFunc": false,
+ "title": "Auth MFA",
+ "slug": "auth-mfa",
+ "product": "auth",
+ "type": "function",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ },
+ {
+ "id": "auth-passkey",
+ "isFunc": false,
+ "title": "Auth Passkey",
+ "slug": "auth-passkey",
+ "product": "auth",
+ "type": "function",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ },
+ {
+ "id": "passkey-admin",
+ "isFunc": false,
+ "title": "Passkey Admin",
+ "slug": "passkey-admin",
+ "product": "auth",
+ "type": "function",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "category",
+ "title": "Edge Functions",
+ "items": [
+ "[Circular]"
+ ]
+ },
+ {
+ "type": "category",
+ "title": "Realtime",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ },
+ {
+ "type": "category",
+ "title": "Storage",
+ "items": [
+ {
+ "id": "file-buckets",
+ "isFunc": false,
+ "title": "File Buckets",
+ "slug": "file-buckets",
+ "product": "storage",
+ "type": "function",
+ "items": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ }
+ ]
+ }
+ ],
+ "functionsList": [
+ {
+ "id": "initializing",
+ "title": "Initializing",
+ "description": "You can initialize Supabase with the static `initialize()` method of the `Supabase` class.\n\nThe Supabase client is your entrypoint to the rest of the Supabase functionality\nand is the easiest way to interact with everything we offer within the Supabase ecosystem.\n",
+ "params": [
+ {
+ "name": "url",
+ "isOptional": false,
+ "type": "string",
+ "description": "The unique Supabase URL which is supplied when you create a new project in your project dashboard."
+ },
+ {
+ "name": "publishableKey",
+ "isOptional": false,
+ "type": "string",
+ "description": "The publishable (anon) key supplied when you create a new project in your project dashboard. Use this for client-side apps. The deprecated `anonKey` parameter is still accepted but `publishableKey` takes precedence when both are supplied."
+ },
+ {
+ "name": "headers",
+ "isOptional": true,
+ "type": "Map",
+ "description": "Custom header to be passed to the Supabase client."
+ },
+ {
+ "name": "httpClient",
+ "isOptional": true,
+ "type": "Client",
+ "description": "Custom http client to be used by the Supabase client."
+ },
+ {
+ "name": "authOptions",
+ "isOptional": true,
+ "type": "FlutterAuthClientOptions",
+ "description": "Options to change the Auth behaviors.",
+ "subContent": [
+ {
+ "name": "authFlowType",
+ "isOptional": true,
+ "type": "AuthFlowType",
+ "description": "Whether to use the `pkce` flow or the `implicit` flow. Defaults to `pkce`."
+ },
+ {
+ "name": "localStorage",
+ "isOptional": true,
+ "type": "LocalStorage",
+ "description": "Parameter to override the local storage to store auth tokens."
+ },
+ {
+ "name": "autoRefreshToken",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to automatically refresh the token when it expires. Defaults to `true`."
+ }
+ ]
+ },
+ {
+ "name": "postgrestOptions",
+ "isOptional": true,
+ "type": "PostgrestClientOptions",
+ "description": "Options to change the Postgrest behaviors.",
+ "subContent": [
+ {
+ "name": "schema",
+ "isOptional": true,
+ "type": "String",
+ "description": "Schema to query with the Supabase client. Defaults to `public`."
+ }
+ ]
+ },
+ {
+ "name": "realtimeClientOptions",
+ "isOptional": true,
+ "type": "RealtimeClientOptions",
+ "description": "Options to change the Realtime behaviors.",
+ "subContent": [
+ {
+ "name": "logLevel",
+ "isOptional": true,
+ "type": "RealtimeLogLevel",
+ "description": "Level of realtime server logs to to be logged."
+ }
+ ]
+ },
+ {
+ "name": "storageOptions",
+ "isOptional": true,
+ "type": "StorageClientOptions",
+ "description": "Options to change the Storage behaviors.",
+ "subContent": [
+ {
+ "name": "retryAttempts",
+ "isOptional": true,
+ "type": "int",
+ "description": "The number of times to retry a failed upload request. Defaults to `0`."
+ },
+ {
+ "name": "useNewHostname",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to rewrite legacy storage URLs to use the dedicated storage host (`[.storage.supabase.co`). Set to `true` only if your project has the dedicated storage host enabled. Defaults to `false`."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "flutter-initialize",
+ "name": "For Flutter",
+ "code": "```dart\nFuture] main() async {\n await Supabase.initialize(\n url: 'https://xyzcompany.supabase.co',\n publishableKey: 'your-publishable-key',\n );\n\n runApp(MyApp());\n}\n\n// Get a reference your Supabase client\nfinal supabase = Supabase.instance.client;\n```\n"
+ },
+ {
+ "id": "for-other-dart-projects",
+ "name": "For other Dart projects",
+ "code": "```dart\nfinal supabase = SupabaseClient(\n 'https://xyzcompany.supabase.co',\n 'your-secret-key', // use your secret key for server-side usage\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "delete",
+ "$ref": "supabase_flutter.delete",
+ "description": "Perform a DELETE on the table or view.\n",
+ "notes": "- `delete()` should always be combined with [Filters](/docs/reference/dart/using-filters) to target the item(s) you wish to delete.\n- If you use `delete()` with filters and you have RLS enabled, only rows visible through `SELECT` policies are deleted. Note that by default no rows are visible, so you need at least one `SELECT`/`ALL` policy that makes the rows visible.\n",
+ "examples": [
+ {
+ "id": "delete-records",
+ "name": "Delete records",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase\n .from('countries')\n .delete()\n .eq('id', 1);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'Mordor');\n```\n"
+ }
+ },
+ {
+ "id": "delete-multiple-records",
+ "name": "Delete multiple records",
+ "code": "```dart\nawait supabase\n .from('countries')\n .delete()\n .inFilter('id', [1, 2, 3])\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'Mordor'), (2, 'Gondor'), (3, 'Rohan');\n```\n"
+ }
+ },
+ {
+ "id": "fetch-delete-records",
+ "name": "Fetch deleted records",
+ "code": "```dart\nfinal List> data = await supabase\n .from('cities')\n .delete()\n .match({ 'id': 666 })\n .select();\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "insert",
+ "$ref": "supabase_flutter.insert",
+ "description": "Perform an INSERT into the table or view.\n",
+ "params": [
+ {
+ "name": "values",
+ "isOptional": false,
+ "type": "Map or List>",
+ "description": "The values to insert. Pass an object to insert a single row or an array to insert multiple rows."
+ }
+ ],
+ "examples": [
+ {
+ "id": "create-a-record",
+ "name": "Create a record",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase\n .from('cities')\n .insert({'name': 'The Shire', 'country_id': 554});\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n```\n"
+ },
+ "hideCodeBlock": true
+ },
+ {
+ "id": "fetch-inserted-data",
+ "name": "Fetch inserted record",
+ "code": "```dart\nfinal List> data =\n await supabase.from('cities').insert([\n {'name': 'The Shire', 'country_id': 554},\n {'name': 'Rohan', 'country_id': 555},\n ]).select();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'Denmark'\n }\n]\n```\n"
+ },
+ {
+ "id": "bulk-create",
+ "name": "Bulk create",
+ "code": "```dart\nawait supabase.from('cities').insert([\n {'name': 'The Shire', 'country_id': 554},\n {'name': 'Rohan', 'country_id': 555},\n]);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n```\n"
+ },
+ "response": "```json\nPostgrestException(\n 'code': '23505',\n 'details': 'Key (id)=(1) already exists.',\n 'hint': null,\n 'message': 'duplicate key value violates unique constraint \"countries_pkey\"'\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "rpc",
+ "$ref": "supabase_flutter.rpc",
+ "description": "Perform a function call.\n\nYou can call Postgres functions as Remote Procedure Calls, logic in your database that you can execute from anywhere.\nFunctions are useful when the logic rarely changes—like for password resets and updates.\n",
+ "params": [
+ {
+ "name": "fn",
+ "isOptional": false,
+ "type": "String",
+ "description": "The function name to call."
+ },
+ {
+ "name": "params",
+ "isOptional": true,
+ "type": "Map",
+ "description": "The arguments to pass to the function call."
+ }
+ ],
+ "examples": [
+ {
+ "id": "call-a-postgres-function-without-arguments",
+ "name": "Call a Postgres function without arguments",
+ "code": "```dart\nfinal data = await supabase\n .rpc('hello_world');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate function hello_world() returns text as $$\n select 'Hello world';\n$$ language sql;\n```\n"
+ },
+ "response": "```json\n'Hello world'\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ },
+ {
+ "id": "call-a-postgres-function-with-arguments",
+ "name": "Call a Postgres function with arguments",
+ "code": "```dart\nfinal data = await supabase\n .rpc('echo_city', params: { 'say': '👋' });\n```\n",
+ "data": {
+ "sql": "```sql\ncreate function echo(say text) returns text as $$\n select say;\n$$ language sql;\n```\n"
+ },
+ "response": "```json\n'👋'\n```\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "bulk-processing",
+ "name": "Bulk processing",
+ "code": "```dart\nfinal data = await supabase\n .rpc('add_one_each', params: { arr: [1, 2, 3] });\n```\n",
+ "data": {
+ "sql": "```sql\ncreate function add_one_each(arr int[]) returns int[] as $$\n select array_agg(n + 1) from unnest(arr) as n;\n$$ language sql;\n```\n"
+ },
+ "response": "```json\n[\n 2,\n 3,\n 4,\n]\n```\n",
+ "description": "You can process large payloads by passing in an array as an argument.\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "call-a-postgres-function-with-filters",
+ "name": "Call a Postgres function with filters",
+ "code": "```dart\nfinal data = await supabase\n .rpc('list_stored_countries')\n .eq('id', 1)\n .single();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'Mordor'),\n (2, 'Gondor');\n\ncreate function list_stored_countries() returns setof countries as $$\n select * from countries;\n$$ language sql;\n```\n"
+ },
+ "response": "```json\n{\n 'id': 1,\n 'name': 'Mordor'\n}\n```\n",
+ "description": "Postgres functions that return tables can also be combined with [Filters](/docs/reference/dart/using-filters) and [Modifiers](/docs/reference/dart/using-modifiers).\n",
+ "hideCodeBlock": true
+ }
+ ]
+ },
+ {
+ "id": "select",
+ "$ref": "supabase_flutter.select",
+ "description": "Perform a SELECT query on the table or view.\n",
+ "notes": "- By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed in Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data.\n- `select()` can be combined with [Filters](/docs/reference/dart/using-filters)\n- `select()` can be combined with [Modifiers](/docs/reference/dart/using-modifiers)\n- `apikey` is a reserved keyword if you're using the [Supabase Platform](/docs/guides/platform) and [should be avoided as a column name](https://github.com/supabase/supabase/issues/5465).\n",
+ "params": [
+ {
+ "name": "columns",
+ "isOptional": true,
+ "type": "String",
+ "description": "The columns to retrieve, separated by commas. Columns can be renamed when returned with `customName:columnName`"
+ }
+ ],
+ "examples": [
+ {
+ "id": "getting-your-data",
+ "name": "Getting your data",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'violin'\n },\n {\n 'id': 2,\n 'name': 'viola'\n },\n {\n 'id': 3,\n 'name': 'cello'\n },\n]\n```\n"
+ },
+ {
+ "id": "selecting-specific-columns",
+ "name": "Selecting specific columns",
+ "description": "You can select specific fields from your tables.",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('''\n name\n ''');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'violin'\n },\n {\n 'name': 'viola'\n },\n {\n 'name': 'cello'\n },\n]\n```\n"
+ },
+ {
+ "id": "query-referenced-tables",
+ "name": "Query referenced tables",
+ "description": "If your database has relationships, you can query related tables too.",
+ "code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('''\n name,\n instruments ( name )\n ''');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 2, 'flute'),\n (2, 1, 'violin');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'strings',\n 'instruments': [\n {\n 'name': 'violin'\n },\n ]\n },\n {\n 'name': 'woodwinds',\n 'instruments': [\n {\n 'name': 'flute'\n },\n ]\n },\n]\n```\n"
+ },
+ {
+ "id": "query-referenced-tables-through-a-join-table",
+ "name": "Query referenced tables through a join table",
+ "code": "```dart\nfinal data = await supabase\n .from('users')\n .select('''\n name,\n teams (\n name\n )\n ''');\n ```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text\n );\ncreate table\n teams (\n id int8 primary key,\n name text\n );\n-- join table\ncreate table\n users_teams (\n user_id int8 not null references users,\n team_id int8 not null references teams,\n -- both foreign keys must be part of a composite primary key\n primary key (user_id, team_id)\n );\n\ninsert into\n users (id, name)\nvalues\n (1, 'Kiran'),\n (2, 'Evan');\ninsert into\n teams (id, name)\nvalues\n (1, 'Green'),\n (2, 'Blue');\ninsert into\n users_teams (user_id, team_id)\nvalues\n (1, 1),\n (1, 2),\n (2, 2);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'Kiran',\n 'teams': [\n {\n 'name': 'Green'\n },\n {\n 'name': 'Blue'\n },\n ]\n },\n {\n 'name': 'Evan',\n 'teams': [\n {\n 'name': 'Blue'\n }\n ]\n }\n]\n```\n",
+ "description": "If you're in a situation where your tables are **NOT** directly\nrelated, but instead are joined by a _join table_, you can still use\nthe `select()` method to query the related data. The join table needs\nto have the foreign keys as part of its composite primary key.\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "query-the-same-referenced-table-multiple-times",
+ "name": "Query the same referenced table multiple times",
+ "description": "Sometimes you will need to query the same referenced table twice. In\nthis case, you can use the name of the joined column to identify which\njoin you intend to use. For convenience, you can also give an alias\nfor each column. This example queries a `messages` table that is\njoined to a `users` table via `sender_id` and `receiver_id`. Since you\nwant the `name` for both sender and receiver, you can alias the\ncolumns as `to` and `from`.\n",
+ "code": "```dart\nfinal data = await supabase\n .from('messages')\n .select('''\n content,\n from:sender_id(name),\n to:receiver_id(name)\n ''');\n```\n",
+ "data": {
+ "sql": "```sql\n create table\n users (id int8 primary key, name text);\n\n create table\n messages (\n sender_id int8 not null references users,\n receiver_id int8 not null references users,\n content text\n );\n\n insert into\n users (id, name)\n values\n (1, 'Kiran'),\n (2, 'Evan');\n\n insert into\n messages (sender_id, receiver_id, content)\n values\n (1, 2, '👋');\n ```\n"
+ },
+ "response": "```json\n[\n {\n 'content': '👋',\n 'from': {\n 'name': 'Kiran'\n },\n 'to': {\n 'name': 'Evan'\n }\n }\n]\n```\n"
+ },
+ {
+ "id": "filtering-through-referenced-tables",
+ "name": "Filtering through referenced tables",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('name, orchestral_sections(*)')\n .eq('orchestral_sections.name', 'percussion');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 2, 'flute'),\n (2, 1, 'violin');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'flute',\n 'orchestral_sections': null\n },\n {\n 'name': 'violin',\n 'orchestral_sections': null\n }\n]\n```\n",
+ "description": "If the filter on a referenced table's column is not satisfied, the referenced\ntable returns `[]` or `null` but the parent table is not filtered out.\nIf you want to filter out the parent table rows, use the `!inner` hint\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "querying-with-count-option",
+ "name": "Querying with count option",
+ "description": "You can get the number of rows by using the count option.\nAllowed values for count option are [CountOption.exact](https://postgrest.org/en/stable/api.html#exact-count), [CountOptionplanned](https://postgrest.org/en/stable/api.html#planned-count) and [CountOption.estimated](https://postgrest.org/en/stable/api.html#estimated-count).\n",
+ "code": "```dart\nfinal res = await supabase\n .from('instruments')\n .select('name')\n .count(CountOption.exact);\n\nfinal data = res.data;\nfinal count = res.count;\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\nPostgrestResponse(\n data: [\n {\n 'id': 1,\n 'name': 'violin'\n },\n {\n 'id': 2,\n 'name': 'viola'\n },\n {\n 'id': 3,\n 'name': 'cello'\n }\n ],\n count: 3\n);\n```\n"
+ },
+ {
+ "id": "querying-json-data",
+ "name": "Querying JSON data",
+ "description": "If you have data inside of a JSONB column, you can apply select\nand query filters to the data values. Postgres offers a\n[number of operators](https://www.postgresql.org/docs/current/functions-json.html)\nfor querying JSON data. Also see\n[PostgREST docs](http://postgrest.org/en/v7.0.0/api.html#json-columns) for more details.\n",
+ "code": "```dart\nfinal data = await supabase\n .from('users')\n .select('''\n id, name,\n address->city\n ''');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Frodo', '{\"city\":\"Hobbiton\"}');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'Frodo',\n 'city': 'Hobbiton'\n }\n]\n```\n"
+ },
+ {
+ "id": "querying-referenced-table-with-inner-join",
+ "name": "Querying referenced table with inner join",
+ "code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('name, instruments!inner(name)')\n .eq('orchestral_sections.name', 'strings')\n .limit(1);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table orchestral_sections (\n \"id\" \"uuid\" primary key default \"extensions\".\"uuid_generate_v4\"() not null,\n \"name\" text\n);\n\ncreate table instruments (\n \"id\" \"uuid\" primary key default \"extensions\".\"uuid_generate_v4\"() not null,\n \"name\" text,\n \"section_id\" \"uuid\" references public.orchestral_sections on delete cascade\n);\n\nwith section as (\n insert into orchestral_sections (name)\n values ('strings') returning id\n)\ninsert into instruments (name, section_id) values\n('violin', (select id from section)),\n('viola', (select id from section)),\n('cello', (select id from section)),\n('double bass', (select id from section));\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'violin',\n 'orchestral_sections': {'name': 'strings'}\n }\n]\n```\n",
+ "description": "If you don't want to return the referenced table contents, you can leave the parenthesis empty.\nLike `.select('name, books!inner()')`.\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "switching-schemas-per-query",
+ "name": "Switching schemas per query",
+ "code": "```dart\nfinal data = await supabase\n .schema('myschema')\n .from('mytable')\n .select();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate schema myschema;\n\ncreate table myschema.mytable (\n id uuid primary key default gen_random_uuid(),\n data text\n);\n\ninsert into myschema.mytable (data) values ('mydata');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': '4162e008-27b0-4c0f-82dc-ccaeee9a624d',\n 'data': 'mydata'\n }\n]\n```\n",
+ "description": "In addition to setting the schema during initialization, you can also switch schemas on a per-query basis.\nMake sure you've set up your [database privileges and API settings](/docs/guides/api/using-custom-schemas).\n",
+ "hideCodeBlock": true
+ }
+ ]
+ },
+ {
+ "id": "update",
+ "$ref": "supabase_flutter.update",
+ "description": "Perform an UPDATE on the table or view.\n",
+ "notes": "- `update()` should always be combined with [Filters](/docs/reference/dart/using-filters) to target the item(s) you wish to update.\n",
+ "params": [
+ {
+ "name": "values",
+ "isOptional": false,
+ "type": "Map",
+ "description": "The values to update with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "updating-your-data",
+ "name": "Update your data",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase\n .from('instruments')\n .update({ 'name': 'piano' })\n .eq('id', 1);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'harpsichord');\n```\n"
+ }
+ },
+ {
+ "id": "update-a-record-and-return-it",
+ "name": "Update a record and return it",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .update({ 'name': 'piano' })\n .eq('id', 1)\n .select();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'harpsichord');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'piano'\n }\n]\n```\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "updating-json-data",
+ "name": "Update JSON data",
+ "code": "```dart\nawait supabase\n .from('users')\n .update({\n 'address': {\n 'street': 'Melrose Place',\n 'postcode': 90210\n }\n })\n .eq('address->postcode', 90210);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Michael', '{ \"postcode\": 90210 }');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'Michael',\n 'address': {\n 'street': 'Melrose Place',\n 'postcode': 90210\n }\n }\n]\n```\n",
+ "description": "Postgres offers some\n[operators](/docs/guides/database/json#query-the-jsonb-data) for\nworking with JSON data. Currently, it is only possible to update the entire JSON document.\n"
+ }
+ ]
+ },
+ {
+ "id": "upsert",
+ "$ref": "supabase_flutter.upsert",
+ "description": "Perform an UPSERT on the table or view. Depending on the column(s) passed to `onConflict`, `.upsert()` allows you to perform the equivalent of `.insert()` if a row with the corresponding `onConflict` columns doesn't exist, or if it does exist, perform an alternative action depending on `ignoreDuplicates`.\n",
+ "notes": "- Primary keys must be included in `values` to use upsert.\n",
+ "params": [
+ {
+ "name": "values",
+ "isOptional": false,
+ "type": "Map or List>",
+ "description": "The values to upsert with. Pass a Map to upsert a single row or an array to upsert multiple rows."
+ },
+ {
+ "name": "onConflict",
+ "isOptional": true,
+ "type": "String",
+ "description": "Comma-separated UNIQUE column(s) to specify how duplicate rows are determined. Two rows are duplicates if all the `onConflict` columns are equal."
+ },
+ {
+ "name": "ignoreDuplicates",
+ "isOptional": true,
+ "type": "bool",
+ "description": "If `true`, duplicate rows are ignored. If `false`, duplicate rows are merged with existing rows."
+ },
+ {
+ "name": "defaultToNull",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Make missing fields default to `null`. Otherwise, use the default value for the column. This only applies when inserting new rows, not when merging with existing rows where ignoreDuplicates is set to false. This also only applies when doing bulk upserts."
+ }
+ ],
+ "examples": [
+ {
+ "id": "upsert-your-data",
+ "name": "Upsert your data",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .upsert({ 'id': 1, 'name': 'piano' })\n .select();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'harpsichord');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'piano'\n }\n]\n```\n"
+ },
+ {
+ "id": "bulk-upsert-your-data",
+ "name": "Bulk Upsert your data",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .upsert([\n { 'id': 1, 'name': 'piano' },\n { 'id': 2, 'name': 'harp' },\n ])\n .select();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'harpsichord');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'piano'\n },\n {\n 'id': 2,\n 'name': 'harp'\n }\n]\n```\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "upserting-into-tables-with-constraints",
+ "name": "Upserting into tables with constraints",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('users')\n .upsert({ 'id': 42, 'handle': 'saoirse', 'display_name': 'Saoirse' }, { onConflict: 'handle' })\n .select();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 generated by default as identity primary key,\n handle text not null unique,\n display_name text\n );\n\ninsert into\n users (id, handle, display_name)\nvalues\n (1, 'saoirse', null);\n```\n"
+ },
+ "response": "```json\nPostgrestException(\n 'code': '23505',\n 'details': 'Key (handle)=(saoirse) already exists.',\n 'hint': null,\n 'message': \"duplicate key value violates unique constraint \"users_handle_key\"'\n)\n```\n",
+ "description": "In the above query, `upsert()` implicitly uses the `id`\n(primary key) column to determine conflicts. If there is no existing\nrow with the same `id`, `upsert()` inserts a new row, which\nwill fail in this case as there is already a row with `handle` `\"saoirse\"`.\nUsing the `onConflict` option, you can instruct `upsert()` to use\nanother column with a unique constraint to determine conflicts.\n"
+ }
+ ]
+ },
+ {
+ "id": "using-filters",
+ "title": "Using filters",
+ "description": "Filters allow you to only return rows that match certain conditions.\n\nFilters can be used on `select()`, `update()`, `upsert()`, and `delete()` queries.\n\nIf a Database function returns a table response, you can also apply filters.\n",
+ "examples": [
+ {
+ "id": "applying-filters",
+ "name": "Applying Filters",
+ "description": "Filters must be applied after any of `select()`, `update()`, `upsert()`,\n`delete()`, and `rpc()` and before\n[modifiers](/docs/reference/dart/using-modifiers).\n",
+ "code": "```dart\nfinal data = await supabase\n .from('cities')\n .select('name, country_id')\n .eq('name', 'The Shire'); // Correct\n\nfinal data = await supabase\n .from('cities')\n .eq('name', 'The Shire') // Incorrect\n .select('name, country_id');\n```\n"
+ },
+ {
+ "id": "chaining-filters",
+ "name": "Chaining Filters",
+ "description": "Filters can be chained together to produce advanced queries as shown in the example code.\n",
+ "code": "```dart\nfinal data = await supabase\n .from('cities')\n .select('name, country_id')\n .gte('population', 1000)\n .lt('population', 10000)\n```\n"
+ },
+ {
+ "id": "conditional-chaining",
+ "name": "Conditional Chaining",
+ "description": "Filters can be built up one step at a time and then executed as shown in the example code.\n",
+ "code": "```dart\nfinal filterByName = null;\nfinal filterPopLow = 1000;\nfinal filterPopHigh = 10000;\n\nvar query = supabase\n .from('cities')\n .select('name, country_id');\n\nif (filterByName != null) query = query.eq('name', filterByName);\nif (filterPopLow != null) query = query.gte('population', filterPopLow);\nif (filterPopHigh != null) query = query.lt('population', filterPopHigh);\n\nfinal data = await query;\n```\n"
+ },
+ {
+ "id": "filter-by-value-within-json-column",
+ "name": "Filter by values within a JSON column",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Michael', '{ \"postcode\": 90210 }'),\n (2, 'Jane', null);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'Michael',\n 'address': {\n 'postcode': 90210\n }\n },\n]\n```\n",
+ "code": "```dart\nfinal data = await supabase\n .from('users')\n .select()\n .eq('address->postcode', 90210);\n```\n"
+ },
+ {
+ "id": "filter-referenced-tables",
+ "name": "Filter Referenced Tables",
+ "code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('''\n name,\n instruments!inner (\n name\n )\n ''')\n .eq('instruments.name', 'flute');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 2, 'flute'),\n (2, 1, 'violin');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'strings',\n 'instruments': [\n {\n 'name': 'flute'\n }\n ]\n },\n]\n```\n",
+ "description": "You can filter on referenced tables in your `select()` query using dot\nnotation.\n"
+ }
+ ]
+ },
+ {
+ "id": "using-filters-containedby",
+ "$ref": "supabase_flutter.containedBy",
+ "description": "Only relevant for jsonb, array, and range columns. Match only rows where every element appearing in `column` is contained by `value`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The jsonb, array, or range column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The jsonb, array, or range value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "on-array-columns",
+ "name": "On array columns",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('classes')\n .select('name')\n .containedBy('days', ['monday', 'tuesday', 'wednesday', 'friday']);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n classes (\n id int8 primary key,\n name text,\n days text[]\n );\n\ninsert into\n classes (id, name, days)\nvalues\n (1, 'Chemistry', array['monday', 'friday']),\n (2, 'History', array['monday', 'wednesday', 'thursday']);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'Chemistry'\n },\n]\n```\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "on-range-columns",
+ "name": "On range columns",
+ "code": "```dart\nfinal data = await supabase\n .from('reservations')\n .select()\n .containedBy('during', '[2000-01-01 00:00, 2000-01-01 23:59)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'room_name': 'Emerald',\n 'during': '[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")'\n },\n]\n```\n",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "on-jsonb-columns",
+ "name": "On `jsonb` columns",
+ "code": "```dart\nfinal data = await supabase\n .from('users')\n .select('name')\n .containedBy('address', {'postcode': 90210});\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Michael', '{ \"postcode\": 90210, \"street\": \"Melrose Place\" }'),\n (2, 'Jane', '{\"postcode\": 90210}');\n```\n"
+ },
+ "response": "```json\n [\n {\n 'name': 'Jane'\n },\n ]\n ```\n",
+ "hideCodeBlock": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-contains",
+ "$ref": "supabase_flutter.contains",
+ "description": "Only relevant for jsonb, array, and range columns. Match only rows where `column` contains every element appearing in `value`.",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The jsonb, array, or range column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The jsonb, array, or range value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "on-array-columns",
+ "name": "On array columns",
+ "description": "Only relevant for jsonb, array, and range columns. Match only rows where `column` contains every element appearing in `value`.\n",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('issues')\n .select()\n .contains('tags', ['is:open', 'priority:low']);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n issues (\n id int8 primary key,\n title text,\n tags text[]\n );\n\ninsert into\n issues (id, title, tags)\nvalues\n (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']),\n (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'title': 'Cache invalidation is not working',\n 'tags': ['is:open', 'severity:high', 'priority:low']\n },\n]\n```\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "on-range-columns",
+ "name": "On range columns",
+ "code": "```dart\nfinal data = await supabase\n .from('reservations')\n .select()\n .contains('during', '[2000-01-01 13:00, 2000-01-01 13:30)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'room_name': 'Emerald',\n 'during': '[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")'\n },\n]\n```\n",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.\n",
+ "hideCodeBlock": true
+ },
+ {
+ "id": "on-jsonb-columns",
+ "name": "On `jsonb` columns",
+ "code": "```dart\nfinal data = await supabase\n .from('users')\n .select('name')\n .contains('address', { 'street': 'Melrose Place' });\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Michael', '{ \"postcode\": 90210, \"street\": \"Melrose Place\" }'),\n (2, 'Jane', '{\"postcode\": 90210}');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'Michael'\n },\n]\n```\n",
+ "hideCodeBlock": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-eq",
+ "$ref": "supabase_flutter.eq",
+ "description": "Match only rows where `column` is equal to `value`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select()\n .eq('name', 'viola');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 2,\n 'name': 'viola'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-filter",
+ "$ref": "supabase_flutter.filter",
+ "description": "Match only rows which satisfy the filter. This is an escape hatch - you should use the specific filter methods wherever possible.\n",
+ "notes": "`.filter()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values, so it should only be used as an escape hatch in case other filters don't work.\n```dart\n.filter('arraycol','cs','{\"a\",\"b\"}') // Use Postgres array {} and 'cs' for contains.\n.filter('rangecol','cs','(1,2]') // Use Postgres range syntax for range column.\n.filter('id','in','(6,7)') // Use Postgres list () and 'in' for in_ filter.\n.filter('id','cs','{${mylist.join(',')}}') // You can insert a Dart array list.\n```\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "operator",
+ "isOptional": false,
+ "type": "String",
+ "description": "The operator to filter with, following PostgREST syntax."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The value to filter with, following PostgREST syntax."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('characters')\n .select()\n .filter('name', 'in', '(\"Ron\",\"Dumbledore\")')\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Harry'),\n (2, 'Hermione'),\n (3, 'Ron');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 3,\n 'name': 'Ron'\n }\n]\n```\n"
+ },
+ {
+ "id": "with-update",
+ "name": "With update()",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .update({ 'name': 'piano' })\n .filter('name', 'in', '(\"harpsichord\",\"clavichord\")');\n```\n"
+ },
+ {
+ "id": "with-delete",
+ "name": "With delete()",
+ "code": "```dart\nfinal data = await supabase\n .from('countries')\n .delete()\n .filter('name', 'in', '(\"Rohan\",\"Mordor\")');\n```\n"
+ },
+ {
+ "id": "with-rpc",
+ "name": "With rpc()",
+ "code": "```dart\n// Only valid if the database function returns a table type.\nfinal data = await supabase\n .rpc('echo_all_countries')\n .filter('name', 'in', '(\"Rohan\",\"Mordor\")');\n```\n"
+ },
+ {
+ "id": "on-a-referenced-table",
+ "name": "On a referenced table",
+ "code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('''\n name,\n instruments!inner (\n name\n )\n ''')\n .filter('characters.name', 'eq', 'flute')\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 2, 'flute'),\n (2, 1, 'violin');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'woodwinds',\n 'instruments': [\n {\n 'name': 'flute'\n }\n ]\n }\n]\n```\n",
+ "hideCodeBlock": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-gt",
+ "$ref": "supabase_flutter.gt",
+ "description": "Finds all rows whose value on the stated `column` is greater than the specified `value`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('countries')\n .select()\n .gt('id', 2);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'Rohan'),\n (2, 'The Shire'),\n (3, 'Mordor');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 3,\n 'name': 'Mordor'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-gte",
+ "$ref": "supabase_flutter.gte",
+ "description": "Finds all rows whose value on the stated `column` is greater than or equal to the specified `value`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('countries')\n .select()\n .gte('id', 2);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'Rohan'),\n (2, 'The Shire'),\n (3, 'Mordor');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 2,\n 'name': 'The Shire'\n },\n {\n 'id': 3,\n 'name': 'Mordor'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-ilike",
+ "$ref": "supabase_flutter.ilike",
+ "description": "Finds all rows whose value in the stated `column` matches the supplied `pattern` (case insensitive).\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "pattern",
+ "isOptional": false,
+ "type": "String",
+ "description": "The pattern to match with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('planets')\n .select()\n .ilike('name', '%ea%');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n planets (id int8 primary key, name text);\n\ninsert into\n planets (id, name)\nvalues\n (1, 'Mercury'),\n (2, 'Earth'),\n (3, 'Mars');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 2,\n 'name': 'Earth'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-infilter",
+ "$ref": "supabase_flutter.inFilter",
+ "description": "Finds all rows whose value on the stated `column` is found on the specified `values`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "values",
+ "isOptional": false,
+ "type": "List",
+ "description": "The List to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('characters')\n .select()\n .inFilter('name', ['Luke', 'Leia']);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Han'),\n (2, 'Luke'),\n (3, 'Leia');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 2,\n 'name': 'Luke'\n },\n {\n 'id': 3,\n 'name': 'Leia'\n },\n]\n```\n",
+ "description": "`inFilter()` is equivalent to `in()` in SDKs for other languages. It's named `inFilter()` in Dart to avoid a conflict with the `in` keyword in Dart.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-isfilter",
+ "$ref": "supabase_flutter.isFilter",
+ "description": "A check for exact equality (null, true, false), finds all rows whose value on the stated `column` exactly match the specified `value`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object?",
+ "description": "The value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "checking-nullness",
+ "name": "Checking for nullness, true or false",
+ "code": "```dart\nfinal data = await supabase\n .from('countries')\n .select()\n .isFilter('name', null);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'null'),\n (2, null);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'null'\n },\n]\n```\n",
+ "description": "Using the `eq()` filter doesn't work when filtering for `null`.\n\nInstead, you need to use `isFilter()`.\n\n`isFilter()` is equivalent to `is()` in SDKs for other languages. It's named `isFilter()` in Dart to avoid a conflict with the `is` keyword in Dart.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-like",
+ "$ref": "supabase_flutter.like",
+ "description": "Finds all rows whose value in the stated `column` matches the supplied `pattern` (case sensitive).\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "pattern",
+ "isOptional": false,
+ "type": "String",
+ "description": "The pattern to match with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('planets')\n .select()\n .like('name', '%Ea%');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n planets (id int8 primary key, name text);\n\ninsert into\n planets (id, name)\nvalues\n (1, 'Mercury'),\n (2, 'Earth'),\n (3, 'Mars');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 2,\n 'name': 'Earth'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-lt",
+ "$ref": "supabase_flutter.lt",
+ "description": "Finds all rows whose value on the stated `column` is less than the specified `value`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('countries')\n .select()\n .lt('id', 2);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'Rohan'),\n (2, 'The Shire'),\n (3, 'Mordor');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'Rohan'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-lte",
+ "$ref": "supabase_flutter.lte",
+ "description": "Finds all rows whose value on the stated `column` is less than or equal to the specified `value`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('countries')\n .select()\n .lte('id', 2);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'Rohan'),\n (2, 'The Shire'),\n (3, 'Mordor');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'Rohan'\n },\n {\n 'id': 2,\n 'name': 'The Shire'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-match",
+ "$ref": "supabase_flutter.match",
+ "description": "Finds all rows whose columns match the specified `query` object.\n",
+ "params": [
+ {
+ "name": "query",
+ "isOptional": false,
+ "type": "Map",
+ "description": "The object to filter with, with column names as keys mapped to their filter values"
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select()\n .match({ 'id': 2, 'name': 'viola' });\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'viola'\n }\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-neq",
+ "$ref": "supabase_flutter.neq",
+ "description": "Finds all rows whose value on the stated `column` doesn't match the specified `value`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('id, name')\n .neq('name', 'viola');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'violin'\n },\n {\n 'id': 3,\n 'name': 'cello'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-not",
+ "$ref": "supabase_flutter.not",
+ "description": "Finds all rows which doesn't satisfy the filter.\n",
+ "notes": "- `.not()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values.\n\n ```dart\n .not('name','eq','violin')\n .not('arraycol','cs','{\"a\",\"b\"}') // Use Postgres array {} for array column and 'cs' for contains.\n .not('rangecol','cs','(1,2]') // Use Postgres range syntax for range column.\n .not('id','in','(6,7)') // Use Postgres list () and 'in' instead of `inFilter`.\n .not('id','in','(${mylist.join(',')})') // You can insert a Dart list array.\n ```\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to filter on."
+ },
+ {
+ "name": "operator",
+ "isOptional": false,
+ "type": "String",
+ "description": "The operator to be negated to filter with, following PostgREST syntax."
+ },
+ {
+ "name": "value",
+ "isOptional": true,
+ "type": "Object",
+ "description": "The value to filter with, following PostgREST syntax."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "isSpotlight": false,
+ "code": "```dart\nfinal data = await supabase\n .from('countries')\n .select()\n .not('name', 'is', null)\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'null'),\n (2, null);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'null'\n }\n]\n```\n"
+ },
+ {
+ "id": "with-update",
+ "name": "With update()",
+ "code": "```dart\nfinal data = await supabase\n .from('cities')\n .update({ 'name': 'Mordor' })\n .not('name', 'eq', 'Rohan');\n```\n"
+ },
+ {
+ "id": "with-delete",
+ "name": "With delete()",
+ "code": "```dart\nfinal data = await supabase\n .from('cities')\n .delete()\n .not('name', 'eq', 'Mordor');\n```\n"
+ },
+ {
+ "id": "with-rpc",
+ "name": "With rpc()",
+ "code": "```dart\n// Only valid if the database function returns a table type.\nfinal data = await supabase\n .rpc('echo_all_cities')\n .not('name', 'eq', 'Mordor');\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "using-filters-or",
+ "$ref": "supabase_flutter.or",
+ "description": "Finds all rows satisfying at least one of the filters.\n",
+ "notes": "- `.or()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values.\n\n ```dart\n .or('id.in.(6,7),arraycol.cs.{\"a\",\"b\"}') // Use Postgres list () and 'in' instead of `inFilter`. Array {} and 'cs' for contains.\n .or('id.in.(${mylist.join(',')}),arraycol.cs.{${mylistArray.join(',')}}')\t// You can insert a Dart list for list or array column.\n .or('id.in.(${mylist.join(',')}),rangecol.cs.(${mylistRange.join(',')}]')\t// You can insert a Dart list for list or range column.\n ```\n",
+ "params": [
+ {
+ "name": "filters",
+ "isOptional": false,
+ "type": "String",
+ "description": "The filters to use, following PostgREST syntax"
+ },
+ {
+ "name": "referencedTable",
+ "isOptional": true,
+ "type": "String",
+ "description": "Set this to filter on referenced tables instead of the parent table"
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('name')\n .or('id.eq.2,name.eq.cello');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'viola'\n },\n {\n 'name': 'cello'\n }\n]\n```\n"
+ },
+ {
+ "id": "use-or-with-and",
+ "name": "Use `or` with `and`",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('name')\n .or('id.gt.3,and(id.eq.1,name.eq.violin)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'violin'\n }\n]\n```\n"
+ },
+ {
+ "id": "use-or-on-referenced-tables",
+ "name": "Use `or` on referenced tables",
+ "code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('''\n name,\n instruments!inner (\n name\n )\n ''')\n .or('section_id.eq.1,name.eq.guzheng', referencedTable: 'instruments' );\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, book_id, name)\nvalues\n (1, 2, 'flute'),\n (2, 1, 'violin');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'strings',\n 'instruments': [\n {\n 'name': 'violin'\n }\n ]\n }\n]\n```\n",
+ "hideCodeBlock": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-overlaps",
+ "$ref": "supabase_flutter.overlaps",
+ "description": "Only relevant for array and range columns. Match only rows where `column` and `value` have an element in common.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The array or range column to filter on."
+ },
+ {
+ "name": "value",
+ "isOptional": false,
+ "type": "Object",
+ "description": "The array or range value to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "on-array-columns",
+ "name": "On array columns",
+ "code": "```dart\nfinal data = await supabase\n .from('issues')\n .select('title')\n .overlaps('tags', ['is:closed', 'severity:high']);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n issues (\n id int8 primary key,\n title text,\n tags text[]\n );\n\ninsert into\n issues (id, title, tags)\nvalues\n (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']),\n (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'title': 'Cache invalidation is not working'\n },\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ },
+ {
+ "id": "on-range-columns",
+ "name": "On range columns",
+ "code": "```dart\nfinal data = await supabase\n .from('reservations')\n .select()\n .overlaps('during', '[2000-01-01 12:45, 2000-01-01 13:15)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'room_name': 'Emerald',\n 'during': '[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")'\n },\n]\n```\n",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.\n",
+ "hideCodeBlock": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-rangeadjacent",
+ "$ref": "supabase_flutter.rangeAdjacent",
+ "description": "Only relevant for range columns. Match only rows where `column` is mutually exclusive to `range` and there can be no element between the two ranges.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range column to filter on."
+ },
+ {
+ "name": "range",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('reservations')\n .select()\n .rangeAdjacent('during', '[2000-01-01 12:00, 2000-01-01 13:00)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'room_name': 'Emerald',\n 'during': '[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")'\n },\n]\n```\n",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-rangegt",
+ "$ref": "supabase_flutter.rangeGt",
+ "description": "Only relevant for range columns. Match only rows where every element in `column` is greater than any element in `range`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range column to filter on."
+ },
+ {
+ "name": "range",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('reservations')\n .select()\n .rangeGt('during', '[2000-01-02 08:00, 2000-01-02 09:00)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 2,\n 'room_name': 'Topaz',\n 'during': '[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")'\n }\n]\n```\n",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-rangegte",
+ "$ref": "supabase_flutter.rangeGte",
+ "description": "Only relevant for range columns. Match only rows where every element in `column` is either contained in `range` or greater than any element in `range`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range column to filter on."
+ },
+ {
+ "name": "range",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('reservations')\n .select()\n .rangeGte('during', '[2000-01-02 08:30, 2000-01-02 09:30)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 2,\n 'room_name': 'Topaz',\n 'during': '[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")''\n },\n]\n```\n",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-rangelt",
+ "$ref": "supabase_flutter.rangeLt",
+ "description": "Only relevant for range columns. Match only rows where every element in `column` is less than any element in `range`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range column to filter on."
+ },
+ {
+ "name": "range",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('reservations')\n .select()\n .rangeLt('during', '[2000-01-01 15:00, 2000-01-01 16:00)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'room_name': 'Emerald',\n 'during': '[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")'\n },\n]\n```\n",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-rangelte",
+ "$ref": "supabase_flutter.rangeLte",
+ "description": "Only relevant for range columns. Match only rows where every element in `column` is either contained in `range` or less than any element in `range`.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range column to filter on."
+ },
+ {
+ "name": "range",
+ "isOptional": false,
+ "type": "String",
+ "description": "The range to filter with."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('reservations')\n .select()\n .rangeLte('during', '[2000-01-01 15:00, 2000-01-01 16:00)');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'room_name': 'Emerald',\n 'during': '[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")'\n },\n]\n```\n",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-filters-textsearch",
+ "$ref": "supabase_flutter.textSearch",
+ "description": "Finds all rows whose tsvector value on the stated `column` matches to_tsquery(query).\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The text or tsvector column to filter on."
+ },
+ {
+ "name": "query",
+ "isOptional": false,
+ "type": "String",
+ "description": "The query text to match with."
+ },
+ {
+ "name": "config",
+ "isOptional": true,
+ "type": "String",
+ "description": "The text search configuration to use."
+ },
+ {
+ "name": "type",
+ "isOptional": true,
+ "type": "TextSearchType",
+ "description": "Change how the `query` text is interpreted."
+ }
+ ],
+ "examples": [
+ {
+ "id": "text-search",
+ "name": "Text search",
+ "code": "```dart\nfinal data = await supabase\n .from('quotes')\n .select('catchphrase')\n .textSearch('content', \"'eggs' & 'ham'\",\n config: 'english'\n );\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table texts (\n id bigint\n primary key\n generated always as identity,\n content text\n);\n\ninsert into texts (content) values\n ('Four score and seven years ago'),\n ('The road goes ever on and on'),\n ('Green eggs and ham')\n;\n```\n"
+ },
+ "response": "```json\n[\n {\n 'content': 'Green eggs and ham'\n }\n]\n```\n"
+ },
+ {
+ "id": "basic-normalization",
+ "name": "Basic normalization",
+ "description": "Uses PostgreSQL's `plainto_tsquery` function.",
+ "code": "```dart\nfinal data = await supabase\n .from('quotes')\n .select('catchphrase')\n .textSearch('catchphrase', \"'fat' & 'cat'\",\n type: TextSearchType.plain,\n config: 'english'\n );\n```\n"
+ },
+ {
+ "id": "full-normalization",
+ "name": "Full normalization",
+ "description": "Uses PostgreSQL's `phraseto_tsquery` function.",
+ "code": "```dart\nfinal data = await supabase\n .from('quotes')\n .select('catchphrase')\n .textSearch('catchphrase', \"'fat' & 'cat'\",\n type: TextSearchType.phrase,\n config: 'english'\n );\n```\n"
+ },
+ {
+ "id": "web-search",
+ "name": "Websearch",
+ "description": "Uses PostgreSQL's `websearch_to_tsquery` function.\nThis function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used\nwith advanced operators.\n\n- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.\n- `\"quoted text\"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.\n- `OR`: the word “or” will be converted to the | operator.\n- `-`: a dash will be converted to the ! operator.\n",
+ "code": "```dart\nfinal data = await supabase\n .from('quotes')\n .select('catchphrase')\n .textSearch('catchphrase', \"'fat or cat'\",\n type: TextSearchType.websearch,\n config: 'english'\n );\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers",
+ "title": "Using modifiers",
+ "description": "Filters work on the row level. That is, they allow you to return rows that\nonly match certain conditions without changing the shape of the rows.\nModifiers are everything that don't fit that definition—allowing you to\nchange the format of the response (e.g., returning a CSV string).\n\nModifiers must be specified after filters. Some modifiers only apply for\nqueries that return rows (e.g., `select()` or `rpc()` on a function that\nreturns a table response).\n"
+ },
+ {
+ "id": "using-modifiers-csv",
+ "$ref": "supabase_flutter.csv",
+ "examples": [
+ {
+ "id": "return-data-as-csv",
+ "name": "Return data as CSV",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select()\n .csv();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n'id,name\\n1,violin\\n2,viola\\n3,cello'\n```\n",
+ "description": "By default, the data is returned in JSON format, but can also be returned as Comma Separated Values.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers-explain",
+ "$ref": "supabase_flutter.explain",
+ "description": "For debugging slow queries, you can get the [Postgres `EXPLAIN` execution plan](https://www.postgresql.org/docs/current/sql-explain.html) of a query\nusing the `explain()` method. This works on any query, even for `rpc()` or writes.\n\nExplain is not enabled by default as it can reveal sensitive information about your database.\nIt's best to only enable this for testing environments but if you wish to enable it for production you can provide additional protection by using a `pre-request` function.\n\nFollow the [Performance Debugging Guide](/docs/guides/database/debugging-performance) to enable the functionality on your project.\n",
+ "params": [
+ {
+ "name": "analyze",
+ "isOptional": true,
+ "type": "bool",
+ "description": "If `true`, the query will be executed and the actual run time will be returned."
+ },
+ {
+ "name": "verbose",
+ "isOptional": true,
+ "type": "bool",
+ "description": "If `true`, the query identifier will be returned and `data` will include the output columns of the query."
+ },
+ {
+ "name": "settings",
+ "isOptional": true,
+ "type": "bool",
+ "description": "If `true`, include information on configuration parameters that affect query planning."
+ },
+ {
+ "name": "buffers",
+ "isOptional": true,
+ "type": "bool",
+ "description": "If `true`, include information on buffer usage."
+ },
+ {
+ "name": "wal",
+ "isOptional": true,
+ "type": "bool",
+ "description": "If `true`, include information on WAL record generation."
+ }
+ ],
+ "examples": [
+ {
+ "id": "get-execution-plan",
+ "name": "Get the execution plan",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select()\n .explain();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```\nAggregate (cost=33.34..33.36 rows=1 width=112)\n -> Limit (cost=0.00..18.33 rows=1000 width=40)\n -> Seq Scan on instruments (cost=0.00..22.00 rows=1200 width=40)\n```\n",
+ "description": "By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ },
+ {
+ "id": "get-execution-plan-with-analyze-and-verbose",
+ "name": "Get the execution plan with analyze and verbose",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select()\n .explain(analyze:true, verbose:true);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```\nAggregate (cost=33.34..33.36 rows=1 width=112) (actual time=0.041..0.041 rows=1 loops=1)\n Output: NULL::bigint, count(ROW(instruments.id, instruments.name)), COALESCE(json_agg(ROW(instruments.id, instruments.name)), '[]'::json), NULLIF(current_setting('response.headers'::text, true), ''::text), NULLIF(current_setting('response.status'::text, true), ''::text)\n -> Limit (cost=0.00..18.33 rows=1000 width=40) (actual time=0.005..0.006 rows=3 loops=1)\n Output: instruments.id, instruments.name\n -> Seq Scan on public.instruments (cost=0.00..22.00 rows=1200 width=40) (actual time=0.004..0.005 rows=3 loops=1)\n Output: instruments.id, instruments.name\nQuery Identifier: -4730654291623321173\nPlanning Time: 0.407 ms\nExecution Time: 0.119 ms\n```\n",
+ "description": "By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter.\n",
+ "hideCodeBlock": true,
+ "isSpotlight": false
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers-limit",
+ "$ref": "supabase_flutter.limit",
+ "description": "Limits the result with the specified count.\n",
+ "params": [
+ {
+ "name": "count",
+ "isOptional": false,
+ "type": "int",
+ "description": "The maximum number of rows to return."
+ },
+ {
+ "name": "referencedTable",
+ "isOptional": true,
+ "type": "int",
+ "description": "Set this to limit rows of referenced tables instead of the parent table."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('name')\n .limit(1);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'violin'\n }\n]\n```\n"
+ },
+ {
+ "id": "on-a-referenced-table",
+ "name": "On a referenced table",
+ "code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('''\n name,\n instruments (\n name\n )\n ''')\n .limit(1, referencedTable: 'instruments');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 1, 'violin'),\n (2, 1, 'viola');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'strings',\n 'instruments': [\n {\n 'name': 'violin'\n }\n ]\n }\n]\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers-maxaffected",
+ "$ref": "supabase_flutter.maxAffected",
+ "description": "Sets the maximum number of rows that can be affected by the query. Only effective with PATCH and DELETE operations. Requires PostgREST v13 or higher.\n\nWhen the limit is exceeded, the query will fail with an error. This provides a safety mechanism to prevent accidentally affecting more rows than intended.\n",
+ "notes": "- This method is only effective with UPDATE and DELETE operations.\n- Requires PostgREST v13 or higher on your Supabase instance.\n- If the number of affected rows exceeds the limit, the query will fail and no rows will be modified.\n",
+ "params": [
+ {
+ "name": "count",
+ "isOptional": false,
+ "type": "int",
+ "description": "The maximum number of rows that can be affected by the query."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-update",
+ "name": "With update()",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase\n .from('users')\n .update({'active': false})\n .eq('status', 'inactive')\n .maxAffected(5);\n```\n",
+ "description": "Limit the number of rows that can be updated. If more than 5 rows match the filter, the operation will fail.\n"
+ },
+ {
+ "id": "with-delete",
+ "name": "With delete()",
+ "code": "```dart\nawait supabase\n .from('users')\n .delete()\n .eq('active', false)\n .maxAffected(10);\n```\n",
+ "description": "Limit the number of rows that can be deleted. If more than 10 rows match the filter, the operation will fail.\n"
+ },
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "code": "```dart\nfinal data = await supabase\n .from('users')\n .update({'status': 'INACTIVE'})\n .eq('id', 1)\n .maxAffected(1)\n .select();\n```\n",
+ "description": "Combine maxAffected with select() to limit affected rows and return the updated data.\n"
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers-maybesingle",
+ "$ref": "supabase_flutter.maybeSingle",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select()\n .eq('name', 'guzheng')\n .maybeSingle();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\nnull\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers-order",
+ "$ref": "supabase_flutter.order",
+ "description": "Orders the result with the specified column.\n",
+ "params": [
+ {
+ "name": "column",
+ "isOptional": false,
+ "type": "String",
+ "description": "The column to order by."
+ },
+ {
+ "name": "ascending",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to order in ascending order. Default is `false`."
+ },
+ {
+ "name": "nullsFirst",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to order nulls first. Default is `false`."
+ },
+ {
+ "name": "referencedTable",
+ "isOptional": true,
+ "type": "String",
+ "description": "Specify the referenced table when ordering by a column in an embedded resource."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('id, name')\n .order('id', ascending: false);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 3,\n 'name': 'cello'\n },\n {\n 'id': 2,\n 'name': 'viola'\n },\n {\n 'id': 1,\n 'name': 'viola'\n }\n]\n```\n"
+ },
+ {
+ "id": "on-a-referenced-table",
+ "name": "On a referenced table",
+ "code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('''\n name,\n instruments (\n name\n )\n ''')\n .order('name', referencedTable: 'instruments', ascending: false);\n ```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 1, 'harp'),\n (2, 1, 'violin');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'strings',\n 'instruments': [\n {\n 'name': 'violin'\n },\n {\n 'name': 'harp'\n }\n ]\n },\n {\n 'name': 'woodwinds',\n 'instruments': []\n }\n]\n```\n"
+ },
+ {
+ "id": "order-parent-table-by-a-referenced-table",
+ "name": "Order parent table by a referenced table",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('''\n name,\n section:orchestral_sections (\n name\n )\n ''')\n .order('section(name)', ascending: true)\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 1, 'violin'),\n (2, 2, 'flute');\n```\n"
+ },
+ "response": "```json\n[\n {\n \"name\": \"violin\",\n \"orchestral_sections\": { \"name\": \"strings\" }\n },\n {\n \"name\": \"flute\",\n \"orchestral_sections\": { \"name\": \"woodwinds\" }\n }\n]\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers-range",
+ "$ref": "supabase_flutter.range",
+ "description": "Limits the result to rows within the specified range, inclusive.\n",
+ "params": [
+ {
+ "name": "from",
+ "isOptional": false,
+ "type": "int",
+ "description": "The starting index from which to limit the result."
+ },
+ {
+ "name": "to",
+ "isOptional": false,
+ "type": "int",
+ "description": "The last index to which to limit the result."
+ },
+ {
+ "name": "referencedTable",
+ "isOptional": true,
+ "type": "String",
+ "description": "Set this to limit rows of referenced tables instead of the parent table."
+ }
+ ],
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('name')\n .range(0, 1);\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'violin'\n },\n {\n 'name': 'viola'\n }\n]\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers-select",
+ "$ref": "supabase_flutter.select",
+ "examples": [
+ {
+ "id": "with-upsert",
+ "name": "With `upsert()`",
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .upsert({ 'id': 1, 'name': 'piano' })\n .select();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'harpsichord');\n```\n"
+ },
+ "response": "```json\n[\n {\n \"id\": 1,\n \"name\": \"piano\"\n }\n]\n```\n",
+ "hideCodeBlock": true,
+ "isSpotlight": true
+ }
+ ]
+ },
+ {
+ "id": "using-modifiers-single",
+ "$ref": "supabase_flutter.single",
+ "description": "Retrieves only one row from the result. Result must be one row (e.g. using limit), otherwise this will result in an error.\n",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With select()",
+ "isSpotlight": true,
+ "code": "```dart\nfinal data = await supabase\n .from('instruments')\n .select('name')\n .limit(1)\n .single();\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n instruments (id int8 primary key, name text);\n\ninsert into\n instruments (id, name)\nvalues\n (1, 'violin'),\n (2, 'viola'),\n (3, 'cello');\n```\n"
+ },
+ "response": "```json\n{\n 'name': 'violin'\n}\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-currentsession",
+ "$ref": "supabase_flutter.currentSession",
+ "description": "Returns the session data, if there is an active session.\n",
+ "examples": [
+ {
+ "id": "get-the-session-data",
+ "name": "Get the session data",
+ "isSpotlight": true,
+ "code": "```dart\nfinal Session? session = supabase.auth.currentSession;\n```\n",
+ "response": "```json\nSession(\n accessToken: '',\n tokenType: 'bearer',\n expiresIn: 3600,\n refreshToken: '',\n user: User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-currentuser",
+ "$ref": "supabase_flutter.currentUser",
+ "description": "Returns the user data, if there is a logged in user.\n",
+ "examples": [
+ {
+ "name": "Get the logged in user",
+ "isSpotlight": true,
+ "code": "```dart\nfinal User? user = supabase.auth.currentUser;\n```\n",
+ "response": "```json\nUser(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-getuseridentities",
+ "$ref": "supabase_flutter.getUserIdentities",
+ "description": "Gets all the identities linked to a user.\n",
+ "notes": "- The user needs to be signed in to call `getUserIdentities()`.\n",
+ "examples": [
+ {
+ "id": "get-user-identities",
+ "name": "Returns a list of identities linked to the user",
+ "isSpotlight": true,
+ "code": "```dart\nfinal identities = await supabase.auth.getUserIdentities();\n```\n",
+ "response": "```json\n[\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '2024-01-01T00:00:00Z',\n userId: '2024-01-01T00:00:00Z',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n];\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-linkidentity",
+ "$ref": "supabase_flutter.linkIdentity",
+ "description": "Links an oauth identity to an existing user. This method supports the PKCE flow.\n",
+ "notes": "- The **Enable Manual Linking** option must be enabled from your [project's authentication settings](/dashboard/project/_/auth/providers).\n- The user needs to be signed in to call `linkIdentity()`.\n- If the candidate identity is already linked to the existing user or another user, `linkIdentity()` will fail.\n",
+ "params": [
+ {
+ "name": "provider",
+ "isOptional": false,
+ "type": "OAuthProvider",
+ "description": "The provider to link the identity to."
+ },
+ {
+ "name": "redirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "The URL to redirect the user to after they sign in with the third-party provider."
+ },
+ {
+ "name": "scopes",
+ "isOptional": true,
+ "type": "String",
+ "description": "A list of scopes to request from the third-party provider."
+ },
+ {
+ "name": "authScreenLaunchMode",
+ "isOptional": true,
+ "type": "LaunchMode",
+ "description": "The launch mode for the auth screen. Defaults to `LaunchMode.platformDefault`."
+ },
+ {
+ "name": "queryParams",
+ "isOptional": true,
+ "type": "Map",
+ "description": "Additional query parameters to be passed to the OAuth flow."
+ }
+ ],
+ "examples": [
+ {
+ "id": "link-identity",
+ "name": "Link an identity to a user",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.linkIdentity(OAuthProvider.google);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-linkidentitywithidtoken",
+ "$ref": "supabase_flutter.linkIdentityWithIdToken",
+ "description": "Links an identity to an existing user using an ID token obtained from a third-party OAuth provider. This allows linking identities using native OAuth flows (Google, Apple, Facebook, etc.) similar to `signInWithIdToken()` but for linking rather than signing in.\n",
+ "notes": "- The **Enable Manual Linking** option must be enabled from your [project's authentication settings](/dashboard/project/_/auth/providers).\n- The user needs to be signed in to call `linkIdentityWithIdToken()`.\n- Supports the same OAuth providers as `signInWithIdToken()`: Google, Apple, Facebook, Kakao, and Keycloak.\n- If the candidate identity is already linked to another user, the operation will fail.\n",
+ "params": [
+ {
+ "name": "provider",
+ "isOptional": false,
+ "type": "OAuthProvider",
+ "description": "The OAuth provider to link the identity from."
+ },
+ {
+ "name": "idToken",
+ "isOptional": false,
+ "type": "String",
+ "description": "The identity token obtained from the third-party provider."
+ },
+ {
+ "name": "accessToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "Access token obtained from the third-party provider. Required for Google sign in."
+ },
+ {
+ "name": "nonce",
+ "isOptional": true,
+ "type": "String",
+ "description": "Raw nonce value used to perform the third-party sign in. Required for Apple sign-in."
+ },
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "The captcha token to be used for captcha verification."
+ }
+ ],
+ "examples": [
+ {
+ "id": "link-google-identity",
+ "name": "Link Google identity",
+ "isSpotlight": true,
+ "description": "Link a Google identity to the currently signed-in user using native Google Sign-In.\n",
+ "code": "```dart\nimport 'package:google_sign_in/google_sign_in.dart';\nimport 'package:supabase_flutter/supabase_flutter.dart';\n\nconst webClientId = '';\nconst iosClientId = '';\n\nfinal GoogleSignIn googleSignIn = GoogleSignIn(\n clientId: iosClientId,\n serverClientId: webClientId,\n);\nfinal googleUser = await googleSignIn.signIn();\nfinal googleAuth = await googleUser!.authentication;\nfinal accessToken = googleAuth.accessToken;\nfinal idToken = googleAuth.idToken;\n\nif (accessToken == null) {\n throw 'No Access Token found.';\n}\nif (idToken == null) {\n throw 'No ID Token found.';\n}\n\nfinal response = await supabase.auth.linkIdentityWithIdToken(\n provider: OAuthProvider.google,\n idToken: idToken,\n accessToken: accessToken,\n);\n```\n"
+ },
+ {
+ "id": "link-apple-identity",
+ "name": "Link Apple identity",
+ "description": "Link an Apple identity to the currently signed-in user using native Apple Sign In.\n",
+ "code": "```dart\nimport 'package:sign_in_with_apple/sign_in_with_apple.dart';\nimport 'package:supabase_flutter/supabase_flutter.dart';\nimport 'package:crypto/crypto.dart';\n\nfinal rawNonce = supabase.auth.generateRawNonce();\nfinal hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();\n\nfinal credential = await SignInWithApple.getAppleIDCredential(\n scopes: [\n AppleIDAuthorizationScopes.email,\n AppleIDAuthorizationScopes.fullName,\n ],\n nonce: hashedNonce,\n);\n\nfinal idToken = credential.identityToken;\nif (idToken == null) {\n throw const AuthException(\n 'Could not find ID Token from generated credential.',\n );\n}\n\nfinal response = await supabase.auth.linkIdentityWithIdToken(\n provider: OAuthProvider.apple,\n idToken: idToken,\n nonce: rawNonce,\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-onauthstatechange",
+ "$ref": "supabase_flutter.onAuthStateChange",
+ "description": "Receive a notification every time an auth event happens.\n",
+ "notes": "- **You must provide an `onError` handler.** Network errors (e.g. an offline token refresh) are emitted as stream errors. If no `onError` is provided, Dart rethrows them as unhandled zone exceptions, crashing the app.\n- Auth event types: `initialSession`, `signedIn`, `signedOut`, `passwordRecovery`, `tokenRefreshed`, `userUpdated`, `userDeleted`, `mfaChallengeVerified`\n",
+ "examples": [
+ {
+ "id": "listen-to-auth-changes",
+ "name": "Listen to auth changes",
+ "isSpotlight": true,
+ "code": "```dart\nfinal authSubscription = supabase.auth.onAuthStateChange.listen(\n (data) {\n final AuthChangeEvent event = data.event;\n final Session? session = data.session;\n // handle event\n },\n onError: (error, stackTrace) {\n // Network errors (e.g. offline) are emitted here.\n // Handle or log them to avoid an unhandled exception crash.\n },\n);\n```\n"
+ },
+ {
+ "id": "listen-to-a-specific-event",
+ "name": "Listen to a specific event",
+ "code": "```dart\nfinal authSubscription = supabase.auth.onAuthStateChange.listen(\n (data) {\n final AuthChangeEvent event = data.event;\n if (event == AuthChangeEvent.signedIn) {\n // handle signIn\n }\n },\n onError: (error, stackTrace) {\n // Handle or log network / auth errors here.\n },\n);\n```\n"
+ },
+ {
+ "id": "unsubscribe-from-auth-subscription",
+ "name": "Unsubscribe from auth subscription",
+ "code": "```dart\nfinal authSubscription = supabase.auth.onAuthStateChange.listen(\n (data) {},\n onError: (error, stackTrace) {},\n);\n\nauthSubscription.cancel();\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-reauthenticate",
+ "$ref": "supabase_flutter.reauthenticate",
+ "notes": "- This method is used together with `updateUser()` when a user's password needs to be updated.\n- This method sends a nonce to the user's email. If the user doesn't have a confirmed email address, the method sends the nonce to the user's confirmed phone number instead.\n",
+ "examples": [
+ {
+ "id": "send-reauthentication-nonce",
+ "name": "Send reauthentication nonce",
+ "description": "Sends a reauthentication nonce to the user's email or phone number.",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.reauthenticate();\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-refreshsession",
+ "$ref": "supabase_flutter.refreshSession",
+ "notes": "- This method will refresh and return a new session whether the current one is expired or not.\n",
+ "examples": [
+ {
+ "id": "refresh-session-using-the-current-session",
+ "name": "Refresh session using the current session",
+ "isSpotlight": true,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.refreshSession();\nfinal session = res.session;\n```\n",
+ "response": "```json\nAuthResponse(\n user: User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n session: Session(\n accessToken: '',\n tokenType: 'bearer',\n expiresIn: 3600,\n refreshToken: '',\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ),\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-registerpasskey",
+ "$ref": "supabase_flutter.registerPasskey",
+ "notes": "Registers a new passkey (WebAuthn credential) for the signed in user.\n- Available on `supabase_flutter` 2.15.0 and later as an extension on `GoTrueClient`.\n- Drives the full WebAuthn ceremony end to end: starts the registration with the Supabase server, prompts the user to create a credential on the device, and verifies it with the server.\n- Requires a signed in (non-anonymous) user. If the user has verified MFA factors, the session has to be at `aal2` to manage passkeys.\n- For native flows or custom UI, use the lower-level [`auth.passkey`](/docs/reference/dart/auth-passkey-api) namespace instead.\n- Passkeys are a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.\n",
+ "examples": [
+ {
+ "id": "register-passkey",
+ "name": "Register a passkey for the current user",
+ "isSpotlight": true,
+ "code": "```dart\nfinal Passkey passkey = await supabase.auth.registerPasskey();\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-resend",
+ "$ref": "supabase_flutter.resend",
+ "notes": "- Resends a signup confirmation, email change, or phone change email to the user.\n- Passwordless sign-ins can be resent by calling the `signInWithOtp()` method again.\n- Password recovery emails can be resent by calling the `resetPasswordForEmail()` method again.\n- This method only resend an email or phone OTP to the user if an initial signup, email change, or phone change request was made.\n",
+ "examples": [
+ {
+ "id": "resend-email-signup-confirmation",
+ "name": "Resend an email signup confirmation",
+ "description": "Resends the email signup confirmation to the user",
+ "isSpotlight": true,
+ "code": "```dart\nfinal ResendResponse res = await supabase.auth.resend(\n type: OtpType.signup,\n email: 'email@example.com',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-resetpasswordforemail",
+ "$ref": "supabase_flutter.resetPasswordForEmail",
+ "description": "Sends a reset request to an email address.\n",
+ "notes": "Sends a password reset request to an email address. When the user clicks the reset link in the email they are redirected back to your application. Prompt the user for a new password and call auth.updateUser():\n\n```dart\nawait supabase.auth.resetPasswordForEmail(\n 'sample@email.com',\n redirectTo: kIsWeb ? null : 'io.supabase.flutter://reset-callback/',\n);\n```\n",
+ "examples": [
+ {
+ "id": "reset-password",
+ "name": "Reset password for Flutter",
+ "isSpotlight": true,
+ "code": "`redirectTo` is used to open the app via deeplink when user opens the password reset email.\n```dart\nawait supabase.auth.resetPasswordForEmail(\n 'sample@email.com',\n redirectTo: kIsWeb ? null : 'io.supabase.flutter://reset-callback/',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-setsession",
+ "$ref": "supabase_flutter.setSession",
+ "notes": "- `setSession()` takes in a refresh token and uses it to get a new session.\n- The refresh token can only be used once to obtain a new session.\n- [Refresh token rotation](/docs/guides/cli/config#auth.enable_refresh_token_rotation) is enabled by default on all projects to guard against replay attacks.\n- You can configure the [`REFRESH_TOKEN_REUSE_INTERVAL`](/docs/guides/cli/config#auth.refresh_token_reuse_interval) which provides a short window in which the same refresh token can be used multiple times in the event of concurrency or offline issues.\n",
+ "params": [
+ {
+ "name": "refreshToken",
+ "isOptional": false,
+ "type": "String",
+ "description": "Refresh token to use to get a new session."
+ },
+ {
+ "name": "accessToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "Optional access token to set along with the refresh token."
+ }
+ ],
+ "examples": [
+ {
+ "id": "refresh-the-session",
+ "name": "Refresh the session",
+ "description": "Sets the session data from refresh_token and returns current session or an error if the refresh_token is invalid.",
+ "isSpotlight": true,
+ "code": "```dart\nfinal refreshToken = supabase.currentSession?.refreshToken ?? '';\nfinal AuthResponse response = await supabase.auth.setSession(refreshToken);\n\nfinal session = res.session;\n```\n"
+ },
+ {
+ "id": "set-session-with-access-token",
+ "name": "Set session with access token",
+ "description": "You can optionally provide an access token along with the refresh token.",
+ "isSpotlight": false,
+ "code": "```dart\nfinal AuthResponse response = await supabase.auth.setSession(\n refreshToken,\n accessToken: accessToken,\n);\n```\n",
+ "response": "```json\nAuthResponse(\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n session: Session(\n accessToken: '',\n tokenType: 'bearer',\n expiresIn: 3600,\n refreshToken: '',\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ),\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signinanonymously",
+ "$ref": "supabase_flutter.signInAnonymously",
+ "description": "Creates an anonymous user.\n",
+ "notes": "- Returns an anonymous user\n- It is recommended to set up captcha for anonymous sign-ins to prevent abuse. You can pass in the captcha token in the `options` param.\n",
+ "params": [
+ {
+ "name": "data",
+ "isOptional": true,
+ "type": "Map",
+ "description": "The user's metadata to be stored in the user's object."
+ },
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "The captcha token to be used for captcha verification."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-in-anonymously",
+ "name": "Create an anonymous user",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.signInAnonymously();\n```\n",
+ "response": "```json\nAuthResponse(\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: '',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {},\n userMetadata: {},\n identities: [],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n isAnonymous: true,\n ),\n session: Session(\n accessToken: '',\n tokenType: 'bearer',\n expiresIn: 3600,\n refreshToken: '',\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: '',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {},\n userMetadata: {},\n identities: [],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n isAnonymous: true,\n ),\n ),\n);\n```\n"
+ },
+ {
+ "id": "sign-in-anonymously-with-user-metadata",
+ "name": "Create an anonymous user with custom user metadata",
+ "isSpotlight": false,
+ "code": "```dart\nawait supabase.auth.signInAnonymously(\n data: {'hello': 'world'},\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signinwithidtoken",
+ "$ref": "supabase_flutter.signInWithIdToken",
+ "description": "Allows you to perform native Google, Apple, and Facebook sign in by combining it with [google_sign_in](https://pub.dev/packages/google_sign_in), [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple), or [flutter_facebook_auth](https://pub.dev/packages/flutter_facebook_auth) packages.\n",
+ "params": [
+ {
+ "name": "provider",
+ "isOptional": false,
+ "type": "OAuthProvider",
+ "description": "The provider to perform the sign in with."
+ },
+ {
+ "name": "idToken",
+ "isOptional": false,
+ "type": "String",
+ "description": "The identity token obtained from the third-party provider."
+ },
+ {
+ "name": "accessToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "Access token obtained from the third-party provider. Required for Google sign in."
+ },
+ {
+ "name": "nonce",
+ "isOptional": true,
+ "type": "String",
+ "description": "Raw nonce value used to perform the third-party sign in. Required for Apple sign-in."
+ },
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "The captcha token to be used for captcha verification."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-in-with-google",
+ "name": "Native Google sign in",
+ "isSpotlight": true,
+ "description": "You can perform native Google sign in on Android and iOS using [google_sign_in](https://pub.dev/packages/google_sign_in).\nFor platform specific settings, follow the instructions in the package README.\n\nFirst, create client IDs for your app. You need to create a web client ID as well to perform Google sign-in with Supabase.\n\n- [Steps to obtain web client ID](https://developers.google.com/identity/sign-in/android/start-integrating#configure_a_project)\n- [Steps to obtain Android client ID](https://developers.google.com/identity/sign-in/android/start-integrating#configure_a_project)\n- [Steps to obtain iOS client ID](https://developers.google.com/identity/sign-in/ios/start-integrating#get_an_oauth_client_id)\n\nAdd the web client ID to the `Authentication -> Providers -> Google -> Client IDs` section in your Supabase dashboard\n",
+ "code": "```dart\nimport 'package:google_sign_in/google_sign_in.dart';\nimport 'package:supabase_flutter/supabase_flutter.dart';\n\nconst webClientId = '';\n\nconst iosClientId = '',\n tokenType: 'bearer',\n expiresIn: 3600,\n refreshToken: '',\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n ...\n },\n userMetadata: {\n ...\n },\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n provider: 'google',\n ...\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ),\n);\n```\n"
+ },
+ {
+ "id": "sign-in-with-apple",
+ "name": "Native Apple Sign in",
+ "description": "You need to [register your app ID with Apple](https://developer.apple.com/help/account/manage-identifiers/register-an-app-id/) with the `Sign In with Apple` capability selected, and add the bundle ID to your Supabase dashboard in `Authentication -> Providers -> Apple` before performing native Apple sign in.",
+ "code": "```dart\nimport 'package:sign_in_with_apple/sign_in_with_apple.dart';\nimport 'package:supabase_flutter/supabase_flutter.dart';\nimport 'package:crypto/crypto.dart';\n\n/// Performs Apple sign in on iOS or macOS\nfinal rawNonce = supabase.auth.generateRawNonce();\nfinal hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();\n\nfinal credential = await SignInWithApple.getAppleIDCredential(\n scopes: [\n AppleIDAuthorizationScopes.email,\n AppleIDAuthorizationScopes.fullName,\n ],\n nonce: hashedNonce,\n);\n\nfinal idToken = credential.identityToken;\nif (idToken == null) {\n throw const AuthException(\n 'Could not find ID Token from generated credential.',\n );\n}\n\nfinal response = await supabase.auth.signInWithIdToken(\n provider: OAuthProvider.apple,\n idToken: idToken,\n nonce: rawNonce,\n);\n```\n"
+ },
+ {
+ "id": "sign-in-with-facebook",
+ "name": "Native Facebook Sign in",
+ "description": "You can perform native Facebook sign in using [flutter_facebook_auth](https://pub.dev/packages/flutter_facebook_auth).\n\nFirst, set up your Facebook app in the [Facebook Developer Console](https://developers.facebook.com) and configure it in your Supabase dashboard under `Authentication -> Providers -> Facebook`.\n",
+ "code": "```dart\nimport 'package:flutter_facebook_auth/flutter_facebook_auth.dart';\nimport 'package:supabase_flutter/supabase_flutter.dart';\n\nFuture signInWithFacebook() async {\n final LoginResult result = await FacebookAuth.instance.login(\n permissions: ['public_profile', 'email'],\n );\n\n if (result.status == LoginStatus.success) {\n final accessToken = result.accessToken!.tokenString;\n\n final response = await supabase.auth.signInWithIdToken(\n provider: OAuthProvider.facebook,\n idToken: accessToken,\n );\n } else {\n throw const AuthException(\n 'Facebook login failed: ${result.status}',\n );\n }\n}\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signinwithoauth",
+ "$ref": "supabase_flutter.signInWithOAuth",
+ "description": "Signs the user in using third-party OAuth providers.\n",
+ "notes": "- This method is used for signing in using a third-party provider.\n- Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers).\n",
+ "params": [
+ {
+ "name": "provider",
+ "isOptional": false,
+ "type": "OAuthProvider",
+ "description": "The OAuth provider to use for signing in."
+ },
+ {
+ "name": "redirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "The URL to redirect the user to after they sign in with the third-party provider."
+ },
+ {
+ "name": "scopes",
+ "isOptional": true,
+ "type": "String",
+ "description": "A list of scopes to request from the third-party provider."
+ },
+ {
+ "name": "authScreenLaunchMode",
+ "isOptional": true,
+ "type": "LaunchMode",
+ "description": "The launch mode for the auth screen. Defaults to `LaunchMode.platformDefault`."
+ },
+ {
+ "name": "queryParams",
+ "isOptional": true,
+ "type": "Map",
+ "description": "Additional query parameters to be passed to the OAuth flow."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-in-using-a-third-party-provider",
+ "name": "Sign in using a third-party provider",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.signInWithOAuth(\n OAuthProvider.github,\n redirectTo: kIsWeb ? null : 'my.scheme://my-host', // Optionally set the redirect link to bring back the user via deeplink.\n authScreenLaunchMode:\n kIsWeb ? LaunchMode.platformDefault : LaunchMode.externalApplication, // Launch the auth screen in a new webview on mobile.\n);\n```\n"
+ },
+ {
+ "id": "sign-in-with-custom-provider",
+ "name": "Sign in with a custom OIDC provider",
+ "description": "`OAuthProvider` is now a class instead of an enum, allowing you to use custom OIDC providers by passing a custom provider string.\n",
+ "isSpotlight": false,
+ "code": "```dart\nawait supabase.auth.signInWithOAuth(\n OAuthProvider('custom:my-oidc-provider'),\n redirectTo: kIsWeb ? null : 'my.scheme://my-host',\n);\n```\n"
+ },
+ {
+ "id": "sign-in-using-a-third-party-provider-with-redirect",
+ "name": "With `redirectTo`",
+ "description": "Specify the redirect link to bring back the user via deeplink.\nNote that `redirectTo` should be null for Flutter Web.\n",
+ "code": "```dart\nawait supabase.auth.signInWithOAuth(\n OAuthProvider.github,\n redirectTo: kIsWeb ? null : 'io.supabase.flutter://reset-callback/',\n);\n```\n"
+ },
+ {
+ "id": "sign-in-with-scopes",
+ "name": "With scopes",
+ "description": "If you need additional data from an OAuth provider, you can include a space-separated list of scopes in your request to get back an OAuth provider token.\nYou may also need to specify the scopes in the provider's OAuth app settings, depending on the provider.\n",
+ "code": "```dart\nawait supabase.auth.signInWithOAuth(\n OAuthProvider.github,\n scopes: 'repo gist notifications'\n);\n...\n// after user comes back from signin flow\n\nfinal Session? session = supabase.auth.currentSession;\nfinal String? oAuthToken = session?.providerToken;\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signinwithotp",
+ "$ref": "supabase_flutter.signInWithOtp",
+ "notes": "- Requires either an email or phone number.\n- This method is used for passwordless sign-ins where an OTP is sent to the user's email or phone number.\n- If you're using an email, you can configure whether you want the user to receive a magiclink or an OTP.\n- If you're using phone, you can configure whether you want the user to receive an OTP.\n- The magic link's destination URL is determined by the [`SITE_URL`](https://supabase.com/docs/guides/auth/redirect-urls). You can modify the `SITE_URL` or add additional redirect urls in [your project](https://supabase.com/dashboard/project/_/auth/url-configuration).\n",
+ "params": [
+ {
+ "name": "email",
+ "isOptional": true,
+ "type": "String",
+ "description": "Email address to send the magic link or OTP to."
+ },
+ {
+ "name": "phone",
+ "isOptional": true,
+ "type": "String",
+ "description": "Phone number to send the OTP to."
+ },
+ {
+ "name": "emailRedirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "The URL to redirect the user to after they click on the magic link."
+ },
+ {
+ "name": "shouldCreateUser",
+ "isOptional": true,
+ "type": "bool",
+ "description": "If set to false, this method will not create a new user. Defaults to true."
+ },
+ {
+ "name": "data",
+ "isOptional": true,
+ "type": "Map",
+ "description": "The user's metadata to be stored in the user's object."
+ },
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "The captcha token to be used for captcha verification."
+ },
+ {
+ "name": "channel",
+ "isOptional": true,
+ "type": "OtpChannel",
+ "description": "Messaging channel to use (e.g. whatsapp or sms). Defaults to `OtpChannel.sms`."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-in-with-email",
+ "name": "Sign in with email.",
+ "isSpotlight": true,
+ "description": "The user will be sent an email which contains either a magiclink or an OTP or both. By default, a given user can only request an OTP once every 60 seconds.\nYou can pass `emailRedirectTo` with a dynamic link to bring the users back to your app after they click on the magic link.\n",
+ "code": "```dart\nawait supabase.auth.signInWithOtp(\n email: 'example@email.com',\n emailRedirectTo: kIsWeb ? null : 'io.supabase.flutter://signin-callback/',\n);\n```\n",
+ "response": "```json\nAuthResponse(\n user: null,\n session: null,\n);\n```\n"
+ },
+ {
+ "id": "sign-in-with-sms-otp",
+ "name": "Sign in with SMS OTP.",
+ "description": "The user will be sent a SMS which contains an OTP. By default, a given user can only request an OTP once every 60 seconds.",
+ "code": "```dart\nawait supabase.auth.signInWithOtp(\n phone: '+13334445555',\n);\n```\n"
+ },
+ {
+ "id": "sign-in-with-whatsapp-otp",
+ "name": "Sign in with WhatsApp OTP",
+ "isSpotlight": false,
+ "description": "The user will be sent a WhatsApp message which contains an OTP. By default, a given user can only request an OTP once every 60 seconds. Note that a user will need to have a valid WhatsApp account that is linked to Twilio in order to use this feature.",
+ "code": "```dart\nawait supabase.auth.signInWithOtp(\n phone: '+13334445555',\n channel: OtpChannel.whatsapp,\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signinwithpasskey",
+ "$ref": "supabase_flutter.signInWithPasskey",
+ "notes": "Signs the user in with a passkey (WebAuthn).\n- Available on `supabase_flutter` 2.15.0 and later as an extension on `GoTrueClient`.\n- Drives the full WebAuthn ceremony end to end: starts the challenge with the Supabase server, prompts the user for biometrics or a security key via the platform passkey API, and verifies the credential with the server.\n- Does not require an existing session. On success the session is persisted and an `AuthChangeEvent.signedIn` event is fired.\n- For native flows or custom UI, use the lower-level [`auth.passkey`](/docs/reference/dart/auth-passkey-api) namespace instead.\n- Passkeys are a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.\n- Platform setup the library cannot perform (Associated Domains on iOS/macOS, Digital Asset Links on Android, the `passkeys` web SDK on web) is documented in the `supabase_flutter` package README.\n",
+ "params": [
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "Captcha token to be used for captcha verification."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-in-with-passkey",
+ "name": "Sign in with a passkey",
+ "isSpotlight": true,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.signInWithPasskey();\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signinwithpassword",
+ "$ref": "supabase_flutter.signInWithPassword",
+ "description": "Log in an existing user using email or phone number with password.\n",
+ "notes": "- Requires either an email and password or a phone number and password.\n",
+ "params": [
+ {
+ "name": "email",
+ "isOptional": true,
+ "type": "String",
+ "description": "User's email address to be used for email authentication."
+ },
+ {
+ "name": "phone",
+ "isOptional": true,
+ "type": "String",
+ "description": "User's phone number to be used for phone authentication."
+ },
+ {
+ "name": "password",
+ "isOptional": false,
+ "type": "String",
+ "description": "Password to be used for authentication."
+ },
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "The captcha token to be used for captcha verification."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-in-with-email-and-password",
+ "name": "Sign in with email and password",
+ "isSpotlight": true,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.signInWithPassword(\n email: 'example@email.com',\n password: 'example-password',\n);\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n",
+ "response": "```json\nAuthResponse(\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n session: Session(\n accessToken: '',\n tokenType: 'bearer',\n expiresIn: 3600,\n refreshToken: '',\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ),\n);\n```\n"
+ },
+ {
+ "id": "sign-in-with-phone-and-password",
+ "name": "Sign in with phone and password",
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.signInWithPassword(\n phone: '+13334445555',\n password: 'example-password',\n);\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signinwithsso",
+ "$ref": "supabase_flutter.signInWithSSO",
+ "notes": "- Before you can call this method you need to [establish a connection](/docs/guides/auth/sso/auth-sso-saml#managing-saml-20-connections) to an identity provider. Use the [CLI commands](/docs/reference/cli/supabase-sso) to do this.\n- If you've associated an email domain to the identity provider, you can use the `domain` property to start a sign-in flow.\n- In case you need to use a different way to start the authentication flow with an identity provider, you can use the `providerId` property. For example:\n - Mapping specific user email addresses with an identity provider.\n - Using different hints to identify the correct identity provider, like a company-specific page, IP address or other tracking information.\n",
+ "params": [
+ {
+ "name": "providerId",
+ "isOptional": true,
+ "type": "String",
+ "description": "The ID of the SSO provider to use for signing in."
+ },
+ {
+ "name": "domain",
+ "isOptional": true,
+ "type": "String",
+ "description": "The email domain to use for signing in."
+ },
+ {
+ "name": "redirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "The URL to redirect the user to after they sign in with the third-party provider."
+ },
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "The captcha token to be used for captcha verification."
+ },
+ {
+ "name": "launchMode",
+ "isOptional": true,
+ "type": "LaunchMode",
+ "description": "The launch mode for the auth screen. Defaults to `LaunchMode.platformDefault`."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-in-with-domain",
+ "name": "Sign in with email domain",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.signInWithSSO(\n domain: 'company.com',\n);\n```\n"
+ },
+ {
+ "id": "sign-in-with-provider-uuid",
+ "name": "Sign in with provider UUID",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.signInWithSSO(\n providerId: '21648a9d-8d5a-4555-a9d1-d6375dc14e92',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signout",
+ "$ref": "supabase_flutter.signOut",
+ "description": "Signs out the current user, if there is a logged in user.\n",
+ "notes": "- In order to use the `signOut()` method, the user needs to be signed in first.\n",
+ "params": [
+ {
+ "name": "scope",
+ "isOptional": true,
+ "type": "SignOutScope",
+ "description": "Whether to sign out from all devices or just the current device. Defaults to `SignOutScope.local`."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-out",
+ "name": "Sign out",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.signOut();\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-signup",
+ "$ref": "supabase_flutter.signUp",
+ "description": "Creates a new user.\n",
+ "notes": "- By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://supabase.com/dashboard/project/_/auth/providers).\n- **Confirm email** determines if users need to confirm their email address after signing up.\n - If **Confirm email** is enabled, a `user` is returned but `session` is null.\n - If **Confirm email** is disabled, both a `user` and a `session` are returned.\n- When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/guides/auth/redirect-urls) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://supabase.com/dashboard/project/_/auth/url-configuration).\n- If signUp() is called for an existing confirmed user:\n - When both **Confirm email** and **Confirm phone** (even when phone provider is disabled) are enabled in [your project](/dashboard/project/_/auth/providers), an obfuscated/fake user object is returned.\n - When either **Confirm email** or **Confirm phone** (even when phone provider is disabled) is disabled, the error message, `User already registered` is returned.\n",
+ "params": [
+ {
+ "name": "email",
+ "isOptional": true,
+ "type": "String",
+ "description": "User's email address to be used for email authentication."
+ },
+ {
+ "name": "phone",
+ "isOptional": true,
+ "type": "String",
+ "description": "User's phone number to be used for phone authentication."
+ },
+ {
+ "name": "password",
+ "isOptional": false,
+ "type": "String",
+ "description": "Password to be used for authentication."
+ },
+ {
+ "name": "emailRedirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "The URL to redirect the user to after they confirm their email address."
+ },
+ {
+ "name": "data",
+ "isOptional": true,
+ "type": "Map",
+ "description": "The user's metadata to be stored in the user's object."
+ },
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "The captcha token to be used for captcha verification."
+ },
+ {
+ "name": "channel",
+ "isOptional": true,
+ "type": "OtpChannel",
+ "description": "Messaging channel to use (e.g. whatsapp or sms). Defaults to `OtpChannel.sms`."
+ }
+ ],
+ "examples": [
+ {
+ "id": "sign-up",
+ "name": "Sign up with an email and password",
+ "isSpotlight": true,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.signUp(\n email: 'example@email.com',\n password: 'example-password',\n);\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n",
+ "response": "```json\n// Some fields may be null if \"confirm email\" is enabled.\nAuthResponse(\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n session: Session(\n accessToken: '',\n tokenType: 'bearer',\n expiresIn: 3600,\n refreshToken: '',\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ),\n);\n```\n"
+ },
+ {
+ "id": "sign-up-phone",
+ "name": "Sign up with a phone number and password (SMS)",
+ "isSpotlight": true,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.signUp(\n phone: '123456789',\n password: 'example-password',\n channel: OtpChannel.sms,\n);\n```\n"
+ },
+ {
+ "id": "sign-up-with-metadata",
+ "name": "Sign up with additional metadata",
+ "isSpotlight": true,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.signUp(\n email: 'example@email.com',\n password: 'example-password',\n data: {'username': 'my_user_name'},\n);\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n"
+ },
+ {
+ "id": "sign-up-with-redirect-url",
+ "name": "Sign up with redirect URL",
+ "isSpotlight": true,
+ "description": "- See [redirect URLs and wildcards](/docs/guides/auth#redirect-urls-and-wildcards) to add additional redirect URLs to your project.\n",
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.signUp(\n email: 'example@email.com',\n password: 'example-password',\n emailRedirectTo: 'com.supabase.myapp://callback',\n);\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-unlinkidentity",
+ "$ref": "supabase_flutter.unlinkIdentity",
+ "description": "Unlinks an identity from a user by deleting it. The user will no longer be able to sign in with that identity once it's unlinked.\n",
+ "notes": "- The **Enable Manual Linking** option must be enabled from your [project's authentication settings](/dashboard/project/_/auth/providers).\n- The user needs to be signed in to call `unlinkIdentity()`.\n- The user must have at least 2 identities in order to unlink an identity.\n- The identity to be unlinked must belong to the user.\n",
+ "params": [
+ {
+ "name": "identity",
+ "isOptional": false,
+ "type": "UserIdentity",
+ "description": "The user identity to unlink."
+ }
+ ],
+ "examples": [
+ {
+ "id": "unlink-identity",
+ "name": "Unlink an identity",
+ "isSpotlight": true,
+ "code": "```dart\n// retrieve all identities linked to a user\nfinal identities = await supabase.auth.getUserIdentities();\n\n// find the google identity\nfinal googleIdentity = identities.firstWhere(\n (element) => element.provider == 'google',\n);\n\n// unlink the google identity\nawait supabase.auth.unlinkIdentity(googleIdentity);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-updateuser",
+ "$ref": "supabase_flutter.updateUser",
+ "description": "Updates user data for a logged in user.\n",
+ "notes": "- In order to use the `updateUser()` method, the user needs to be signed in first.\n- By default, email updates sends a confirmation link to both the user's current and new email.\nTo only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://supabase.com/dashboard/project/_/auth/providers).\n",
+ "params": [
+ {
+ "name": "attributes",
+ "isOptional": false,
+ "type": "UserAttributes",
+ "description": "Attributes to update for the user.",
+ "subContent": [
+ {
+ "name": "email",
+ "isOptional": true,
+ "type": "String",
+ "description": "The new email address for the user."
+ },
+ {
+ "name": "phone",
+ "isOptional": true,
+ "type": "String",
+ "description": "The new phone number for the user."
+ },
+ {
+ "name": "password",
+ "isOptional": true,
+ "type": "String",
+ "description": "The new password for the user."
+ },
+ {
+ "name": "data",
+ "isOptional": true,
+ "type": "Map",
+ "description": "The user's metadata to be stored in the user's object."
+ },
+ {
+ "name": "nonce",
+ "isOptional": true,
+ "type": "String",
+ "description": "The nonce sent for reauthentication if the user's password is to be updated."
+ }
+ ]
+ },
+ {
+ "name": "emailRedirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "The URI to redirect the user to after the email is updated."
+ }
+ ],
+ "examples": [
+ {
+ "id": "update-the-email-for-an-authenticated-user",
+ "name": "Update the email for an authenticated user",
+ "description": "Sends a \"Confirm Email Change\" email to the new email address.",
+ "isSpotlight": true,
+ "code": "```dart\nfinal UserResponse res = await supabase.auth.updateUser(\n UserAttributes(\n email: 'example@email.com',\n ),\n);\nfinal User? updatedUser = res.user;\n```\n",
+ "response": "```json\nUserResponse(\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n);\n```\n"
+ },
+ {
+ "id": "update-the-password-for-an-authenticated-user",
+ "name": "Update the password for an authenticated user",
+ "isSpotlight": false,
+ "code": "```dart\nfinal UserResponse res = await supabase.auth.updateUser(\n UserAttributes(\n password: 'new password',\n ),\n);\nfinal User? updatedUser = res.user;\n```\n"
+ },
+ {
+ "id": "update-the-users-metadata",
+ "name": "Update the user's metadata",
+ "isSpotlight": true,
+ "code": "```dart\nfinal UserResponse res = await supabase.auth.updateUser(\n UserAttributes(\n data: { 'hello': 'world' },\n ),\n);\nfinal User? updatedUser = res.user;\n```\n"
+ },
+ {
+ "id": "update-password-with-reauthentication",
+ "name": "Update the user's password with a nonce",
+ "description": "If \"Secure password change\" is enabled, updating the user's password requires a nonce. The nonce is sent to the user's email or phone number.",
+ "isSpotlight": true,
+ "code": "```dart\nsupabase.auth.updateUser(UserAttributes(\n email: 'example@email.com',\n nonce: '123456',\n));\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-verifyotp",
+ "$ref": "supabase_flutter.verifyOtp",
+ "notes": "- The `verifyOtp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `email`, `recovery`, `invite` or `email_change` (`signup` and `magiclink` types are deprecated).\n- The verification type used should be determined based on the corresponding auth method called before `verifyOtp` to sign up or sign in a user.\n",
+ "params": [
+ {
+ "name": "token",
+ "isOptional": false,
+ "type": "String",
+ "description": "The token that user was sent to their email or mobile phone"
+ },
+ {
+ "name": "type",
+ "isOptional": false,
+ "type": "OtpType",
+ "description": "Type of the OTP to verify"
+ },
+ {
+ "name": "email",
+ "isOptional": true,
+ "type": "String",
+ "description": "Email address that the OTP was sent to"
+ },
+ {
+ "name": "phone",
+ "isOptional": true,
+ "type": "String",
+ "description": "Phone number that the OTP was sent to"
+ },
+ {
+ "name": "redirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "URI to redirect the user to after the OTP is verified"
+ },
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "The captcha token to be used for captcha verification"
+ },
+ {
+ "name": "tokenHash",
+ "isOptional": true,
+ "type": "String",
+ "description": "Token used in an email link"
+ }
+ ],
+ "examples": [
+ {
+ "id": "verify-signup-one-time-password(otp)",
+ "name": "Verify Signup One-Time Password (OTP)",
+ "isSpotlight": false,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.verifyOTP(\n type: OtpType.signup,\n token: token,\n phone: '+13334445555',\n);\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n",
+ "response": "```json\nAuthResponse(\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n session: Session(\n accessToken: '',\n tokenType: 'bearer',\n expiresIn: 3600,\n refreshToken: '',\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ),\n);\n```\n"
+ },
+ {
+ "id": "verify-sms-one-time-password(otp)",
+ "name": "Verify SMS One-Time Password (OTP)",
+ "isSpotlight": true,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.verifyOTP(\n type: OtpType.sms,\n token: '111111',\n phone: '+13334445555',\n);\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-admin",
+ "title": "Auth Admin",
+ "notes": "- Any method under the `supabase.auth.admin` namespace requires a `secret` key.\n- These methods are considered admin methods and should be called on a trusted server. Never expose your `secret` key in the Flutter app.\n",
+ "examples": [
+ {
+ "id": "create-auth-admin-client",
+ "name": "Create server-side auth client",
+ "isSpotlight": true,
+ "code": "```dart\nfinal supabase = SupabaseClient(supabaseUrl, secretKey);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-admin-createuser",
+ "$ref": "supabase_flutter.createUser",
+ "notes": "Creates a new user.\n- To confirm the user's email address or phone number, set `email_confirm` or `phone_confirm` to true. Both arguments default to false.\n- `createUser()` will not send a confirmation email to the user. You can use [`inviteUserByEmail()`](/docs/reference/dart/auth-admin-inviteuserbyemail) if you want to send them an email invite instead.\n- If you are sure that the created user's email or phone number is legitimate and verified, you can set the `email_confirm` or `phone_confirm` param to `true`.\n",
+ "params": [
+ {
+ "name": "attributes",
+ "isOptional": false,
+ "type": "AdminUserAttributes",
+ "description": "Attributes to create the user with.",
+ "subContent": [
+ {
+ "name": "email",
+ "isOptional": false,
+ "type": "String",
+ "description": "The email address of the user."
+ },
+ {
+ "name": "password",
+ "isOptional": false,
+ "type": "String",
+ "description": "The password of the user."
+ },
+ {
+ "name": "phone",
+ "isOptional": true,
+ "type": "String",
+ "description": "The phone number of the user."
+ },
+ {
+ "name": "userMetadata",
+ "isOptional": true,
+ "type": "Map",
+ "description": "A custom data object to store the user's metadata. This maps to the `auth.users.user_metadata` column."
+ },
+ {
+ "name": "appMetadata",
+ "isOptional": true,
+ "type": "Map",
+ "description": "A custom data object to store the user's application specific metadata. This maps to the `auth.users.app_metadata` column."
+ },
+ {
+ "name": "emailConfirm",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to confirm the user's email address."
+ },
+ {
+ "name": "phoneConfirm",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to confirm the user's phone number."
+ },
+ {
+ "name": "banDuration",
+ "isOptional": true,
+ "type": "String",
+ "description": "Determines how long a user is banned for. Some possible durations include '300ms', '2h45m'."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "create-a-new-user-with-custom-user-metadata",
+ "name": "With custom user metadata",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.admin.createUser(AdminUserAttributes(\n email: 'user@email.com',\n password: 'password',\n userMetadata: {'name': 'Yoda'},\n));\n```\n",
+ "response": "```json\nUserResponse(\n user: User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'user@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {'name': 'Yoda'},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'user@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n);\n```\n"
+ },
+ {
+ "id": "auto-confirm-the-users-email",
+ "name": "Auto-confirm the user's email",
+ "code": "```dart\nfinal res = await supabase.auth.admin.createUser(AdminUserAttributes(\n email: 'user@email.com',\n emailConfirm: true,\n));\n```\n"
+ },
+ {
+ "id": "auto-confirm-the-users-phone-number",
+ "name": "Auto-confirm the user's phone number",
+ "code": "```dart\nfinal res = await supabase.auth.admin.createUser(AdminUserAttributes(\n phone: '1234567890',\n phoneConfirm: true,\n));\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-admin-deleteuser",
+ "$ref": "supabase_flutter.deleteUser",
+ "notes": "Delete a user.\n- The `deleteUser()` method requires the user's ID, which maps to the `auth.users.id` column.\n",
+ "params": [
+ {
+ "name": "id",
+ "isOptional": false,
+ "type": "String",
+ "description": "ID of the user to be deleted."
+ }
+ ],
+ "examples": [
+ {
+ "id": "removes-a-user",
+ "name": "Removes a user",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.admin\n .deleteUser('715ed5db-f090-4b8c-a067-640ecee36aa0');\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-admin-generatelink",
+ "$ref": "supabase_flutter.generateLink",
+ "notes": "Generates email links and OTPs. This will not send links or OTPs to the end user. This function is for custom admin functionality.\n- The following types can be passed into `generateLink()`: `signup`, `magiclink`, `invite`, `recovery`, `emailChangeCurrent`, `emailChangeNew`, `phoneChange`.\n- `generateLink()` only generates the email link for `email_change_email` if the \"Secure email change\" setting is enabled under the \"Email\" provider in your Supabase project.\n- `generateLink()` handles the creation of the user for `signup`, `invite` and `magiclink`.\n",
+ "params": [
+ {
+ "name": "type",
+ "isOptional": false,
+ "type": "GenerateLinkType",
+ "description": "The type of invite link to generate."
+ },
+ {
+ "name": "email",
+ "isOptional": false,
+ "type": "String",
+ "description": "Email address of the user to invite."
+ },
+ {
+ "name": "password",
+ "isOptional": true,
+ "type": "String",
+ "description": "Password for the user. Required for `signup` type."
+ },
+ {
+ "name": "redirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "URI to redirect the user to after they open the invite link."
+ },
+ {
+ "name": "data",
+ "isOptional": true,
+ "type": "Map",
+ "description": "A custom data object to store the user's metadata. This maps to the `auth.users.user_metadata` column."
+ }
+ ],
+ "examples": [
+ {
+ "id": "generate-a-signup-link",
+ "name": "Generate a signup link",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.admin.generateLink(\n type: GenerateLinkType.signup,\n email: 'email@example.com',\n password: 'secret',\n);\nfinal actionLink = res.properties.actionLink;\n```\n",
+ "response": "```json\nGenerateLinkResponse(\n properties: GenerateLinkProperties(\n actionLink: '',\n emailOtp: '999999',\n hashedToken: '',\n verificationType: GenerateLinkType.signup\n ),\n user: User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'email@example.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'email@example.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n)\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-admin-getuserbyid",
+ "$ref": "supabase_flutter.getUserById",
+ "notes": "Get user by id.\n- Fetches the user object from the database based on the user's id.\n- The `getUserById()` method requires the user's id which maps to the `auth.users.id` column.\n",
+ "params": [
+ {
+ "name": "uid",
+ "isOptional": false,
+ "type": "String",
+ "description": "User ID of the user to fetch."
+ }
+ ],
+ "examples": [
+ {
+ "id": "fetch-the-user-object-using-the-access-token-jwt",
+ "name": "Fetch the user object using the access_token jwt",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.admin.getUserById(userId);\nfinal user = res.user;\n```\n",
+ "response": "```json\nUserResponse(\n user: const User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-admin-inviteuserbyemail",
+ "$ref": "supabase_flutter.inviteUserByEmail",
+ "notes": "Sends an invite link to the user's email address.\n",
+ "params": [
+ {
+ "name": "email",
+ "isOptional": false,
+ "type": "String",
+ "description": "Email address of the user to invite."
+ },
+ {
+ "name": "redirectTo",
+ "isOptional": true,
+ "type": "String",
+ "description": "URI to redirect the user to after they open the invite link."
+ },
+ {
+ "name": "data",
+ "isOptional": true,
+ "type": "Map",
+ "description": "A custom data object to store the user's metadata. This maps to the `auth.users.user_metadata` column."
+ }
+ ],
+ "examples": [
+ {
+ "id": "invite-a-user",
+ "name": "Invite a user",
+ "isSpotlight": true,
+ "code": "```dart\nfinal UserResponse res = await supabase.auth.admin\n .inviteUserByEmail('email@example.com');\nfinal User? user = res.user;\n```\n",
+ "response": "```json\nUserResponse(\n user: User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'email@example.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'email@example.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-admin-listusers",
+ "$ref": "supabase_flutter.listUsers",
+ "notes": "Get a list of users.\n- Defaults to return 50 users per page.\n",
+ "params": [
+ {
+ "name": "page",
+ "isOptional": true,
+ "type": "int",
+ "description": "What page of users to return."
+ },
+ {
+ "name": "page",
+ "isOptional": true,
+ "type": "int",
+ "description": "How many users to be returned per page. Defaults to 50."
+ }
+ ],
+ "examples": [
+ {
+ "id": "get-a-full-list-of-users",
+ "name": "Get a page of users",
+ "isSpotlight": true,
+ "code": "```dart\n// Returns the first 50 users.\nfinal List users = await supabase.auth.admin.listUsers();\n```\n",
+ "response": "```json\n[\n User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n )\n]\n```\n"
+ },
+ {
+ "id": "get-paginated-list-of-users",
+ "name": "Paginated list of users",
+ "isSpotlight": false,
+ "code": "```dart\n// Returns the 101th - 200th users.\nfinal List res = await supabase.auth.admin.listUsers(\n page: 2,\n perPage: 100,\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-admin-updateuserbyid",
+ "$ref": "supabase_flutter.updateUserById",
+ "params": [
+ {
+ "name": "uid",
+ "isOptional": false,
+ "type": "GenerateLinkType",
+ "description": "User ID of the user to update."
+ },
+ {
+ "name": "attributes",
+ "isOptional": false,
+ "type": "AdminUserAttributes",
+ "description": "Attributes to update for the user.",
+ "subContent": [
+ {
+ "name": "email",
+ "isOptional": false,
+ "type": "String",
+ "description": "The email address of the user."
+ },
+ {
+ "name": "password",
+ "isOptional": false,
+ "type": "String",
+ "description": "The password of the user."
+ },
+ {
+ "name": "phone",
+ "isOptional": true,
+ "type": "String",
+ "description": "The phone number of the user."
+ },
+ {
+ "name": "userMetadata",
+ "isOptional": true,
+ "type": "Map",
+ "description": "A custom data object to store the user's metadata. This maps to the `auth.users.user_metadata` column."
+ },
+ {
+ "name": "appMetadata",
+ "isOptional": true,
+ "type": "Map",
+ "description": "A custom data object to store the user's application specific metadata. This maps to the `auth.users.app_metadata` column."
+ },
+ {
+ "name": "emailConfirm",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to confirm the user's email address."
+ },
+ {
+ "name": "phoneConfirm",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to confirm the user's phone number."
+ },
+ {
+ "name": "banDuration",
+ "isOptional": true,
+ "type": "String",
+ "description": "Determines how long a user is banned for. Some possible durations include '300ms', '2h45m'."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "updates-a-users-email",
+ "name": "Updates a user's email",
+ "isSpotlight": false,
+ "code": "```dart\nawait supabase.auth.admin.updateUserById(\n '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4',\n attributes: AdminUserAttributes(\n email: 'new@email.com',\n ),\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-mfa",
+ "title": "Auth MFA",
+ "notes": "This section contains methods commonly used for Multi-Factor Authentication (MFA) and are invoked behind the `supabase.auth.mfa` namespace.\n\nCurrently, Supabase supports time-based one-time password (TOTP) and phone verification code as the 2nd factor. Recovery codes are not supported but users can enroll multiple factors, with an upper limit of 10..\n\nHaving a 2nd factor for recovery frees the user of the burden of having to store their recovery codes somewhere. It also reduces the attack surface since multiple recovery codes are usually generated compared to just having 1 backup factor.\n\nLearn more about implementing MFA on your application on our guide [here](https://supabase.com/docs/guides/auth/auth-mfa#overview).\n"
+ },
+ {
+ "id": "auth-mfa-challenge",
+ "$ref": "supabase_flutter.challenge",
+ "notes": "Prepares a challenge used to verify that a user has access to a MFA factor.\n- An [enrolled factor](/docs/reference/dart/auth-mfa-enroll) is required before creating a challenge.\n- To verify a challenge, see [`mfa.verify()`](/docs/reference/dart/auth-mfa-verify).\n",
+ "params": [
+ {
+ "name": "factorId",
+ "isOptional": false,
+ "type": "String",
+ "description": "System assigned identifier for authenticator device as returned by enroll"
+ }
+ ],
+ "examples": [
+ {
+ "id": "create-mfa-challenge",
+ "name": "Create a challenge for a factor",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.mfa.challenge(\n factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225',\n);\n```\n",
+ "response": "```json\nAuthMFAChallengeResponse(\n id: '',\n expiresAt: DateTime.fromMillisecondsSinceEpoch(1700000000),\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-mfa-challengeandverify",
+ "$ref": "supabase_flutter.challengeAndVerify",
+ "notes": "Helper method which creates a challenge and immediately uses the given code to verify against it thereafter. The verification code is provided by the user by entering a code seen in their authenticator app.\n- An [enrolled factor](/docs/reference/dart/auth-mfa-enroll) is required before invoking `challengeAndVerify()`.\n- Executes [`mfa.challenge()`](/docs/reference/dart/auth-mfa-challenge) and [`mfa.verify()`](/docs/reference/dart/auth-mfa-verify) in a single step.\n",
+ "params": [
+ {
+ "name": "factorId",
+ "isOptional": false,
+ "type": "String",
+ "description": "System assigned identifier for authenticator device as returned by enroll"
+ },
+ {
+ "name": "code",
+ "isOptional": false,
+ "type": "String",
+ "description": "The verification code on the user's authenticator app"
+ }
+ ],
+ "examples": [
+ {
+ "id": "challenge-and-verify",
+ "name": "Create and verify a challenge for a factor",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.mfa.challengeAndVerify(\n factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225',\n code: '123456',\n);\n```\n",
+ "response": "```json\nAuthMFAVerifyResponse(\n accessToken: '',\n tokenType: 'Bearer',\n expiresIn: Duration(seconds: 3600),\n refreshToken: '',\n user: User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n isAnonymous: false,\n factors: [\n Factor(\n id: '',\n friendlyName: 'Important Auth App',\n factorType: FactorType.totp,\n status: 'verified',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z'\n )\n ]\n )\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-mfa-enroll",
+ "$ref": "supabase_flutter.enroll",
+ "notes": "Starts the enrollment process for a new Multi-Factor Authentication (MFA) factor. This method creates a new `unverified` factor.\nTo verify a factor, present the QR code or secret to the user and ask them to add it to their authenticator app.\nThe user has to enter the code from their authenticator app to verify it.\n- Use `totp` or `phone` as the `factorType` and the returned `id` to create a challenge.\n- To create a challenge, see [`mfa.challenge()`](/docs/reference/dart/auth-mfa-challenge).\n- To verify a challenge, see [`mfa.verify()`](/docs/reference/dart/auth-mfa-verify).\n- To create and verify a challenge in a single step, see [`mfa.challengeAndVerify()`](/docs/reference/dart/auth-mfa-challengeandverify).\n",
+ "params": [
+ {
+ "name": "factorType",
+ "isOptional": true,
+ "type": "String",
+ "description": "Type of factor being enrolled."
+ },
+ {
+ "name": "issuer",
+ "isOptional": true,
+ "type": "String",
+ "description": "Domain which the user is enrolled with."
+ },
+ {
+ "name": "friendlyName",
+ "isOptional": true,
+ "type": "String",
+ "description": "Human readable name assigned to the factor."
+ },
+ {
+ "name": "phone",
+ "isOptional": true,
+ "type": "String",
+ "description": "Phone number to enroll for phone factor type."
+ }
+ ],
+ "examples": [
+ {
+ "id": "enroll-totp-factor",
+ "name": "Enroll a time-based, one-time password (TOTP) factor",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.mfa.enroll(factorType: FactorType.totp);\n\nfinal qrCodeUrl = res.totp.qrCode;\n```\n",
+ "response": "```json\nAuthMFAEnrollResponse(\n id: '',\n type: FactorType.totp,\n totp: TOTPEnrollment(\n qrCode: '',\n secret: '',\n uri: '',\n ),\n phone: null,\n);\n```\n"
+ },
+ {
+ "id": "enroll-phone-factor",
+ "name": "Enroll a Phone Factor",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.mfa.enroll(factorType: FactorType.phone, phone: '+1234567890');\n\nfinal phone = res.phone;\n```\n",
+ "response": "```json\nAuthMFAEnrollResponse(\n id: '',\n type: FactorType.phone,\n totp: null,\n phone: PhoneEnrollment(\n phone: '+1234567890',\n ),\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-mfa-getauthenticatorassurancelevel",
+ "$ref": "supabase_flutter.getAuthenticatorAssuranceLevel",
+ "notes": "Returns the Authenticator Assurance Level (AAL) for the active session.\n- Authenticator Assurance Level (AAL) is the measure of the strength of an authentication mechanism.\n- In Supabase, having an AAL of `aal1` means the user has signed in with their first factor, such as email, password, or OAuth sign-in. An AAL of `aal2` means the user has also signed in with their second factor, such as a time-based, one-time-password (TOTP).\n- If the user has a verified factor, the `nextLevel` field returns `aal2`. Otherwise, it returns `aal1`.\n",
+ "examples": [
+ {
+ "id": "get-aal",
+ "name": "Get the AAL details of a session",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = supabase.auth.mfa.getAuthenticatorAssuranceLevel();\nfinal currentLevel = res.currentLevel;\nfinal nextLevel = res.nextLevel;\nfinal currentAuthenticationMethods = res.currentAuthenticationMethods;\n```\n",
+ "response": "```json\nAuthMFAGetAuthenticatorAssuranceLevelResponse(\n currentLevel: AuthenticatorAssuranceLevels.aal1,\n nextLevel: AuthenticatorAssuranceLevels.aal2,\n currentAuthenticationMethods: [\n AMREntry(\n method: AMRMethod.password,\n timestamp: DateTime.fromMillisecondsSinceEpoch(1700000000000),\n )\n ]\n}\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-mfa-unenroll",
+ "$ref": "supabase_flutter.unenroll",
+ "notes": "Unenroll removes a MFA factor.\nA user has to have an `aal2` authenticator level in order to unenroll a `verified` factor.\n",
+ "params": [
+ {
+ "name": "factorId",
+ "isOptional": false,
+ "type": "String",
+ "description": "System assigned identifier for authenticator device as returned by enroll"
+ }
+ ],
+ "examples": [
+ {
+ "id": "unenroll-a-factor",
+ "name": "Unenroll a factor",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.mfa.unenroll(\n '34e770dd-9ff9-416c-87fa-43b31d7ef225',\n);\n```\n",
+ "response": "```json\nAuthMFAUnenrollResponse(\n id: '',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-mfa-verify",
+ "$ref": "supabase_flutter.verify",
+ "notes": "Verifies a code against a challenge. The verification code is provided by the user by entering a code seen in their authenticator app.\n- To verify a challenge, please [create a challenge](/docs/reference/dart/auth-mfa-challenge) first.\n",
+ "params": [
+ {
+ "name": "factorId",
+ "isOptional": false,
+ "type": "String",
+ "description": "System assigned identifier for authenticator device as returned by enroll"
+ },
+ {
+ "name": "challengeId",
+ "isOptional": false,
+ "type": "String",
+ "description": "The ID of the challenge to verify"
+ },
+ {
+ "name": "code",
+ "isOptional": false,
+ "type": "String",
+ "description": "The verification code on the user's authenticator app"
+ }
+ ],
+ "examples": [
+ {
+ "id": "verify-challenge",
+ "name": "Verify a challenge for a factor",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.auth.mfa.verify(\n factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225',\n challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15',\n code: '123456',\n);\n```\n",
+ "response": "```json\nAuthMFAVerifyResponse(\n accessToken: '',\n tokenType: 'Bearer',\n expiresIn: Duration(seconds: 3600),\n refreshToken: '',\n user: User(\n id: '11111111-1111-1111-1111-111111111111',\n aud: 'authenticated',\n role: 'authenticated',\n email: 'example@email.com',\n emailConfirmedAt: '2024-01-01T00:00:00Z',\n phone: '',\n lastSignInAt: '2024-01-01T00:00:00Z',\n appMetadata: {\n 'provider': 'email',\n 'providers': ['email']\n },\n userMetadata: {},\n identities: [\n UserIdentity(\n identityId: '22222222-2222-2222-2222-222222222222',\n id: '11111111-1111-1111-1111-111111111111',\n userId: '11111111-1111-1111-1111-111111111111',\n identityData: {\n 'email': 'example@email.com',\n 'email_verified': false,\n 'phone_verified': false,\n 'sub': '11111111-1111-1111-1111-111111111111'\n },\n provider: 'email',\n lastSignInAt: '2024-01-01T00:00:00Z',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n ),\n ],\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z',\n isAnonymous: false,\n factors: [\n Factor(\n id: '',\n friendlyName: 'Important Auth App',\n factorType: FactorType.totp,\n status: 'verified',\n createdAt: '2024-01-01T00:00:00Z',\n updatedAt: '2024-01-01T00:00:00Z'\n )\n ]\n )\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-passkey",
+ "title": "Auth Passkey",
+ "notes": "This section contains methods for WebAuthn passkey registration, authentication, and management. Methods are invoked behind the `supabase.auth.passkey` namespace.\n\nThese methods expose the server side of the WebAuthn ceremony. The client side (the FaceID/TouchID/security key prompt) has to be performed with a platform passkey API: `navigator.credentials.create()`/`get()` on web, or a passkey plugin on iOS/Android/macOS. Options and credentials are exchanged as `Map` in the W3C WebAuthn Level 3 JSON format.\n\nFor a one-call alternative that runs the full ceremony, see [`signInWithPasskey()`](/docs/reference/dart/auth-signinwithpasskey) and [`registerPasskey()`](/docs/reference/dart/auth-registerpasskey) on `supabase_flutter`.\n\nPasskey support is a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.\n"
+ },
+ {
+ "id": "auth-passkey-delete",
+ "$ref": "supabase_flutter.delete",
+ "notes": "Deletes a passkey from the signed in user.\n- If the user has verified MFA factors, the session has to be at `aal2` to manage passkeys.\n",
+ "params": [
+ {
+ "name": "passkeyId",
+ "isOptional": false,
+ "type": "String",
+ "description": "ID of the passkey to delete."
+ }
+ ],
+ "examples": [
+ {
+ "id": "delete-passkey",
+ "name": "Delete a passkey",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.passkey.delete(\n passkeyId: '34e770dd-9ff9-416c-87fa-43b31d7ef225',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-passkey-list",
+ "$ref": "supabase_flutter.list",
+ "notes": "Returns the list of passkeys registered to the signed in user.\n",
+ "examples": [
+ {
+ "id": "list-passkeys",
+ "name": "List the current user's passkeys",
+ "isSpotlight": true,
+ "code": "```dart\nfinal List passkeys = await supabase.auth.passkey.list();\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-passkey-startauthentication",
+ "$ref": "supabase_flutter.startAuthentication",
+ "notes": "Starts a passkey sign in.\n- Does not require an existing session.\n- Pass the returned `options` to the platform's passkey API to obtain an assertion, then call [`passkey.verifyAuthentication()`](/docs/reference/dart/auth-passkey-verifyauthentication) with the result.\n",
+ "params": [
+ {
+ "name": "captchaToken",
+ "isOptional": true,
+ "type": "String",
+ "description": "Captcha token to be used for captcha verification."
+ }
+ ],
+ "examples": [
+ {
+ "id": "start-passkey-authentication",
+ "name": "Start a passkey sign in",
+ "isSpotlight": true,
+ "code": "```dart\nfinal PasskeyAuthenticationOptionsResponse authentication =\n await supabase.auth.passkey.startAuthentication();\n\n// Hand authentication.options to the platform passkey API.\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-passkey-startregistration",
+ "$ref": "supabase_flutter.startRegistration",
+ "notes": "Starts the registration of a new passkey for the signed in user.\n- Requires a signed in (non-anonymous) user.\n- Pass the returned `options` to the platform's passkey API to create the credential, then call [`passkey.verifyRegistration()`](/docs/reference/dart/auth-passkey-verifyregistration) with the result.\n",
+ "examples": [
+ {
+ "id": "start-passkey-registration",
+ "name": "Start a passkey registration",
+ "isSpotlight": true,
+ "code": "```dart\nfinal PasskeyRegistrationOptionsResponse registration =\n await supabase.auth.passkey.startRegistration();\n\n// Hand registration.options to the platform passkey API.\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-passkey-update",
+ "$ref": "supabase_flutter.update",
+ "notes": "Updates the friendly name of a passkey.\n",
+ "params": [
+ {
+ "name": "passkeyId",
+ "isOptional": false,
+ "type": "String",
+ "description": "ID of the passkey to rename."
+ },
+ {
+ "name": "friendlyName",
+ "isOptional": false,
+ "type": "String",
+ "description": "New human readable name for the passkey. Limited to 120 characters."
+ }
+ ],
+ "examples": [
+ {
+ "id": "rename-passkey",
+ "name": "Rename a passkey",
+ "isSpotlight": true,
+ "code": "```dart\nfinal Passkey passkey = await supabase.auth.passkey.update(\n passkeyId: '34e770dd-9ff9-416c-87fa-43b31d7ef225',\n friendlyName: 'Work laptop',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-passkey-verifyauthentication",
+ "$ref": "supabase_flutter.verifyAuthentication",
+ "notes": "Completes a passkey sign in and returns the new session.\n- On success the session is persisted and an `AuthChangeEvent.signedIn` event is fired.\n",
+ "params": [
+ {
+ "name": "challengeId",
+ "isOptional": false,
+ "type": "String",
+ "description": "The challenge ID returned by `passkey.startAuthentication()`."
+ },
+ {
+ "name": "credential",
+ "isOptional": false,
+ "type": "Map",
+ "description": "The assertion produced by the platform's passkey API, serialized in the W3C `AuthenticationResponseJSON` format."
+ }
+ ],
+ "examples": [
+ {
+ "id": "verify-passkey-authentication",
+ "name": "Verify a passkey sign in",
+ "isSpotlight": true,
+ "code": "```dart\nfinal AuthResponse res = await supabase.auth.passkey.verifyAuthentication(\n challengeId: authentication.challengeId,\n credential: credential,\n);\nfinal Session? session = res.session;\nfinal User? user = res.user;\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "auth-passkey-verifyregistration",
+ "$ref": "supabase_flutter.verifyRegistration",
+ "notes": "Completes the registration of a new passkey and returns the stored [`Passkey`](/docs/reference/dart/auth-passkey-api).\n",
+ "params": [
+ {
+ "name": "challengeId",
+ "isOptional": false,
+ "type": "String",
+ "description": "The challenge ID returned by `passkey.startRegistration()`."
+ },
+ {
+ "name": "credential",
+ "isOptional": false,
+ "type": "Map",
+ "description": "The credential created by the platform's passkey API, serialized in the W3C `RegistrationResponseJSON` format."
+ }
+ ],
+ "examples": [
+ {
+ "id": "verify-passkey-registration",
+ "name": "Verify a passkey registration",
+ "isSpotlight": true,
+ "code": "```dart\nfinal Passkey passkey = await supabase.auth.passkey.verifyRegistration(\n challengeId: registration.challengeId,\n credential: credential,\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "passkey-admin",
+ "title": "Passkey Admin",
+ "notes": "Contains passkey administration methods, accessed under the `supabase.auth.admin.passkey` namespace. Requires a `secret` key.\n\nPasskey support is a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.\n"
+ },
+ {
+ "id": "passkey-admin-deletepasskey",
+ "$ref": "supabase_flutter.deletePasskey",
+ "notes": "Deletes a passkey from a user.\n",
+ "params": [
+ {
+ "name": "userId",
+ "isOptional": false,
+ "type": "String",
+ "description": "User ID that owns the passkey."
+ },
+ {
+ "name": "passkeyId",
+ "isOptional": false,
+ "type": "String",
+ "description": "ID of the passkey to delete."
+ }
+ ],
+ "examples": [
+ {
+ "id": "admin-delete-passkey",
+ "name": "Delete a user's passkey",
+ "isSpotlight": true,
+ "code": "```dart\nawait supabase.auth.admin.passkey.deletePasskey(\n userId: '11111111-1111-1111-1111-111111111111',\n passkeyId: '34e770dd-9ff9-416c-87fa-43b31d7ef225',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "passkey-admin-listpasskeys",
+ "$ref": "supabase_flutter.listPasskeys",
+ "notes": "Returns the list of passkeys registered to the user with the given ID.\n",
+ "params": [
+ {
+ "name": "userId",
+ "isOptional": false,
+ "type": "String",
+ "description": "User ID whose passkeys should be returned."
+ }
+ ],
+ "examples": [
+ {
+ "id": "admin-list-passkeys",
+ "name": "List a user's passkeys",
+ "isSpotlight": true,
+ "code": "```dart\nfinal List passkeys = await supabase.auth.admin.passkey.listPasskeys(\n userId: '11111111-1111-1111-1111-111111111111',\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "functions-invoke",
+ "$ref": "supabase_flutter.invoke",
+ "description": "Invokes a Supabase Function. See the [guide](/docs/guides/functions) for details on writing Functions.\n",
+ "notes": "- Requires an Authorization header.\n- Invoke params generally match the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) spec.\n",
+ "params": [
+ {
+ "name": "functionName",
+ "isOptional": false,
+ "type": "String",
+ "description": "The name of the function to invoke."
+ },
+ {
+ "name": "headers",
+ "isOptional": true,
+ "type": "Map",
+ "description": "Custom headers to send with the request."
+ },
+ {
+ "name": "body",
+ "isOptional": true,
+ "type": "Map",
+ "description": "The body of the request."
+ },
+ {
+ "name": "method",
+ "isOptional": true,
+ "type": "HttpMethod",
+ "description": "HTTP method of the request. Defaults to POST."
+ }
+ ],
+ "examples": [
+ {
+ "id": "basic-invocation",
+ "name": "Basic invocation.",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.functions.invoke('hello', body: {'foo': 'baa'});\nfinal data = res.data;\n```\n"
+ },
+ {
+ "id": "parsing-custom-headers",
+ "name": "Parsing custom headers.",
+ "description": "Any `headers` will be passed through to the function. A common pattern is to pass a logged-in user's JWT token as an Authorization header.\n",
+ "isSpotlight": true,
+ "code": "```dart\nfinal res = await supabase.functions.invoke(\n 'hello',\n body: {'foo': 'baa'},\n headers: {\n 'Authorization': 'Bearer ${supabase.auth.currentSession?.accessToken}'\n },\n);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "getchannels",
+ "$ref": "supabase_flutter.getChannels",
+ "description": "Returns all Realtime channels.\n",
+ "examples": [
+ {
+ "id": "get-all-channels",
+ "name": "Get all channels",
+ "isSpotlight": true,
+ "code": "```dart\nfinal channels = supabase.getChannels();\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "removeallchannels",
+ "$ref": "supabase_flutter.removeAllChannels",
+ "description": "Unsubscribes and removes all Realtime channels from Realtime client.\n",
+ "notes": "- Removing channels is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed.\n",
+ "examples": [
+ {
+ "id": "remove-all-channels",
+ "name": "Remove all channels",
+ "isSpotlight": true,
+ "code": "```dart\nfinal statuses = await supabase.removeAllChannels();\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "removechannel",
+ "$ref": "supabase_flutter.removeChannel",
+ "description": "Unsubscribes and removes Realtime channel from Realtime client.\n",
+ "notes": "- Removing a channel is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed.\n",
+ "examples": [
+ {
+ "id": "removes-a-channel",
+ "name": "Remove a channel",
+ "isSpotlight": true,
+ "code": "```dart\nfinal status = await supabase.removeChannel(channel);\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "stream",
+ "$ref": "supabase_flutter.stream",
+ "description": "Returns real-time data from your table as a `Stream`.\n",
+ "notes": "- Realtime is disabled by default for new tables. You can turn it on by [managing replication](/docs/guides/realtime/postgres-changes#replication-setup).\n- `stream()` will emit the initial data as well as any further change on the database as `Stream>>` by combining Postgrest and Realtime.\n- Takes a list of primary key column names that will be used to update and delete the proper records within the SDK.\n- To use a private Realtime channel, pass `channelOptions: const RealtimeChannelConfig(private: true)` to the `stream()` call.\n- The following filters are available\n - `.eq('column', value)` listens to rows where the column equals the value\n - `.neq('column', value)` listens to rows where the column does not equal the value\n - `.gt('column', value)` listens to rows where the column is greater than the value\n - `.gte('column', value)` listens to rows where the column is greater than or equal to the value\n - `.lt('column', value)` listens to rows where the column is less than the value\n - `.lte('column', value)` listens to rows where the column is less than or equal to the value\n - `.inFilter('column', [val1, val2, val3])` listens to rows where the column is one of the values\n",
+ "examples": [
+ {
+ "id": "listen-to-table",
+ "name": "Listen to a table",
+ "isSpotlight": true,
+ "code": "```dart\nsupabase.from('countries')\n .stream(primaryKey: ['id'])\n .listen((List> data) {\n // Do something awesome with the data\n});\n```\n"
+ },
+ {
+ "id": "with-filter-order-limit",
+ "name": "With filter, order and limit",
+ "code": "```dart\nsupabase.from('countries')\n .stream(primaryKey: ['id'])\n .eq('id', 120)\n .order('name')\n .limit(10);\n```\n"
+ },
+ {
+ "id": "with-in-filter",
+ "name": "With an IN filter",
+ "code": "```dart\nsupabase.from('countries')\n .stream(primaryKey: ['id'])\n .inFilter('id', [1, 2, 3])\n .order('name')\n .limit(10);\n```\n"
+ },
+ {
+ "id": "using-stream-with-stream-builder",
+ "name": "Using `stream()` with `StreamBuilder`",
+ "description": "When using `stream()` with a `StreamBuilder` within your Flutter application, make sure to store your stream in a variable to prevent refetching upon rebuilding.\n",
+ "code": "```dart\nfinal supabase = Supabase.instance.client;\n\nclass MyWidget extends StatefulWidget {\n const MyWidget({Key? key}) : super(key: key);\n\n @override\n State createState() => _MyWidgetState();\n}\n\nclass _MyWidgetState extends State {\n // Persist the stream in a local variable to prevent refetching upon rebuilds\n final _stream = supabase.from('countries').stream(primaryKey: ['id']);\n\n @override\n Widget build(BuildContext context) {\n return StreamBuilder(\n stream: _stream,\n builder: (context, snapshot) {\n // Return your widget with the data from the snapshot\n },\n );\n }\n}\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "subscribe",
+ "$ref": "supabase_flutter.subscribe",
+ "description": "Subscribe to realtime changes in your database.\n",
+ "notes": "- Realtime is disabled by default for new tables. You can turn it on by [managing replication](/docs/guides/realtime/postgres-changes#replication-setup).\n- If you want to receive the \"previous\" data for updates and deletes, you will need to set `REPLICA IDENTITY` to `FULL`, like this: `ALTER TABLE your_table REPLICA IDENTITY FULL;`\n",
+ "examples": [
+ {
+ "id": "listen-to-database-changes",
+ "name": "Listen to database changes",
+ "isSpotlight": true,
+ "code": "```dart\nsupabase\n .channel('public:countries')\n .onPostgresChanges(\n event: PostgresChangeEvent.all,\n schema: 'public',\n table: 'countries',\n callback: (payload) {\n print('Change received: ${payload.toString()}');\n })\n .subscribe();\n```\n"
+ },
+ {
+ "id": "listen-to-inserts",
+ "name": "Listen to inserts",
+ "code": "```dart\nsupabase\n .channel('public:countries')\n .onPostgresChanges(\n event: PostgresChangeEvent.insert,\n schema: 'public',\n table: 'countries',\n callback: (payload) {\n print('Change received: ${payload.toString()}');\n })\n .subscribe();\n```\n"
+ },
+ {
+ "id": "listen-to-updates",
+ "name": "Listen to updates",
+ "description": "By default, Supabase will send only the updated record. If you want to receive the previous values as well you can\nenable full replication for the table you are listening too:\n\n```sql\nalter table \"your_table\" replica identity full;\n```\n",
+ "code": "```dart\nsupabase\n .channel('public:countries')\n .onPostgresChanges(\n event: PostgresChangeEvent.update,\n schema: 'public',\n table: 'countries',\n callback: (payload) {\n print('Change received: ${payload.toString()}');\n })\n .subscribe();\n```\n"
+ },
+ {
+ "id": "listen-to-deletes",
+ "name": "Listen to deletes",
+ "description": "By default, Supabase does not send deleted records. If you want to receive the deleted record you can\nenable full replication for the table you are listening too:\n\n```sql\nalter table \"your_table\" replica identity full;\n```\n",
+ "code": "```dart\nsupabase\n .channel('public:countries')\n .onPostgresChanges(\n event: PostgresChangeEvent.delete,\n schema: 'public',\n table: 'countries',\n callback: (payload) {\n print('Change received: ${payload.toString()}');\n })\n .subscribe();\n```\n"
+ },
+ {
+ "id": "listen-to-multiple-events",
+ "name": "Listen to multiple events",
+ "description": "You can chain listeners if you want to listen to multiple events for each table.",
+ "code": "```dart\nsupabase\n .channel('public:countries')\n .onPostgresChanges(\n event: PostgresChangeEvent.insert,\n schema: 'public',\n table: 'countries',\n callback: (payload) {\n print('Insert event received: ${payload.toString()}');\n })\n .onPostgresChanges(\n event: PostgresChangeEvent.delete,\n schema: 'public',\n table: 'countries',\n callback: (payload) {\n print('Delete event received: ${payload.toString()}');\n })\n .subscribe();\n```\n"
+ },
+ {
+ "id": "listening-to-row-level-changes",
+ "name": "Listen to row level changes",
+ "description": "You can listen to individual rows by using the `filter` parameter passing a `PostgresChangeFilter` object with `PostgresChangeFilterType.eq` as `type`.",
+ "code": "```dart\nsupabase\n .channel('public:countries:id=eq.200')\n .onPostgresChanges(\n event: PostgresChangeEvent.delete,\n schema: 'public',\n table: 'countries',\n filter: PostgresChangeFilter(\n type: PostgresChangeFilterType.eq,\n column: 'id',\n value: 200,\n ),\n callback: (payload) {\n print('Change received: ${payload.toString()}');\n })\n .subscribe();\n```\n"
+ },
+ {
+ "id": "listen-to-broadcast",
+ "name": "Listen to broadcast messages",
+ "code": "```dart\nsupabase\n .channel('room1')\n .onBroadcast(\n event: 'cursor-pos',\n callback: (payload) {\n print('Cursor position received!: $payload');\n })\n .subscribe();\n```\n"
+ },
+ {
+ "id": "listen-to-presence-events",
+ "name": "Listen to presence events",
+ "code": "```dart\nfinal channel = supabase.channel('room1');\n\nchannel.onPresenceSync((payload) {\n print('Synced presence state: ${channel.presenceState()}');\n}).onPresenceJoin((payload) {\n print('Newly joined presences $payload');\n}).onPresenceLeave((payload) {\n print('Newly left presences: $payload');\n}).subscribe((status, error) async {\n if (status == RealtimeSubscribeStatus.subscribed) {\n await channel.track({'online_at': DateTime.now().toIso8601String()});\n }\n});\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets",
+ "title": "File Buckets",
+ "notes": "This section contains methods for working with File Buckets.\n"
+ },
+ {
+ "id": "file-buckets-createbucket",
+ "$ref": "supabase_flutter.createBucket",
+ "description": "Creates a new Storage bucket\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: `insert`\n - `objects` permissions: none\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "id",
+ "isOptional": false,
+ "type": "String",
+ "description": "A unique identifier for the bucket you are creating."
+ },
+ {
+ "name": "bucketOptions",
+ "isOptional": true,
+ "type": "BucketOptions",
+ "description": "A parameter to optionally make the bucket public.",
+ "subContent": [
+ {
+ "name": "public",
+ "isOptional": false,
+ "type": "bool",
+ "description": "The visibility of the bucket. Public buckets don't require an authorization token to download objects, but still require a valid token for all other operations. By default, buckets are private."
+ },
+ {
+ "name": "fileSizeLimit",
+ "isOptional": true,
+ "type": "String",
+ "description": "Specifies the max file size in bytes that can be uploaded to this bucket. The global file size limit takes precedence over this value. The default value is null, which doesn't set a per bucket file size limit."
+ },
+ {
+ "name": "allowedMimeTypes",
+ "isOptional": true,
+ "type": "List",
+ "description": "Specifies the allowed mime types that this bucket can accept during upload. The default value is null, which allows files with all mime types to be uploaded. Each mime type specified can be a wildcard, e.g. image/*, or a specific mime type, e.g. image/png."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "create-bucket",
+ "name": "Create bucket",
+ "isSpotlight": true,
+ "code": "```dart\nfinal String bucketId = await supabase\n .storage\n .createBucket('avatars');\n```\n",
+ "response": "```json\n'avatars'\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-createsignedurl",
+ "$ref": "supabase_flutter.createSignedUrl",
+ "description": "Create signed url to download file without requiring permissions. This URL can be valid for a set number of seconds.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: none\n - `objects` permissions: `select`\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "path",
+ "isOptional": false,
+ "type": "String",
+ "description": "The file path, including the file name. For example folder/image.png."
+ },
+ {
+ "name": "expiresIn",
+ "isOptional": false,
+ "type": "int",
+ "description": "The number of seconds until the signed URL expires. For example, 60 for a URL which is valid for one minute."
+ },
+ {
+ "name": "transform",
+ "isOptional": true,
+ "type": "TransformOptions",
+ "description": "Transform the asset before serving it to the client.",
+ "subContent": [
+ {
+ "name": "width",
+ "isOptional": true,
+ "type": "int",
+ "description": "The width of the image in pixels."
+ },
+ {
+ "name": "height",
+ "isOptional": true,
+ "type": "int",
+ "description": "The height of the image in pixels."
+ },
+ {
+ "name": "resize",
+ "isOptional": true,
+ "type": "ResizeMode",
+ "description": "Specifies how image cropping should be handled when performing image transformations. Defaults to `ResizeMode.cover`."
+ },
+ {
+ "name": "quality",
+ "isOptional": true,
+ "type": "int",
+ "description": "Set the quality of the returned image. A number from 20 to 100, with 100 being the highest quality. Defaults to 80"
+ },
+ {
+ "name": "format",
+ "isOptional": true,
+ "type": "RequestImageFormat",
+ "description": "Specify the format of the image requested. When using 'origin' we force the format to be the same as the original image. When this option is not passed in, images are optimized to modern image formats like Webp."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "create-signed-url",
+ "name": "Create Signed URL",
+ "isSpotlight": true,
+ "code": "```dart\nfinal String signedUrl = await supabase\n .storage\n .from('avatars')\n .createSignedUrl('avatar1.png', 60);\n```\n"
+ },
+ {
+ "id": "create-signed-url-with-transform",
+ "name": "With transform",
+ "code": "```dart\nfinal String signedUrl = await supabase\n .storage\n .from('avatars')\n .createSignedUrl(\n 'avatar1.png',\n 60,\n transform: TransformOptions(\n width: 200,\n height: 200,\n ),\n );\n```\n",
+ "response": "```json\n'https://example.supabase.co/storage/v1/object/sign/avatars/folder/avatar1.png?token=''\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-deletebucket",
+ "$ref": "supabase_flutter.deleteBucket",
+ "description": "Deletes an existing bucket. A bucket can't be deleted with existing objects inside it. You must first `empty()` the bucket.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: `select` and `delete`\n - `objects` permissions: none\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "id",
+ "isOptional": false,
+ "type": "String",
+ "description": "A unique identifier for the bucket you are deleting."
+ }
+ ],
+ "examples": [
+ {
+ "id": "delete-bucket",
+ "name": "Delete bucket",
+ "isSpotlight": true,
+ "code": "```dart\nfinal String res = await supabase\n .storage\n .deleteBucket('avatars');\n```\n",
+ "response": "```json\n'Successfully deleted'\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-download",
+ "$ref": "supabase_flutter.download",
+ "description": "Downloads a file.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: none\n - `objects` permissions: `select`\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "path",
+ "isOptional": false,
+ "type": "String",
+ "description": "The full path and file name of the file to be downloaded. For example folder/image.png."
+ },
+ {
+ "name": "transform",
+ "isOptional": true,
+ "type": "TransformOptions",
+ "description": "Transform the asset before serving it to the client.",
+ "subContent": [
+ {
+ "name": "width",
+ "isOptional": true,
+ "type": "int",
+ "description": "The width of the image in pixels."
+ },
+ {
+ "name": "height",
+ "isOptional": true,
+ "type": "int",
+ "description": "The height of the image in pixels."
+ },
+ {
+ "name": "resize",
+ "isOptional": true,
+ "type": "ResizeMode",
+ "description": "Specifies how image cropping should be handled when performing image transformations. Defaults to `ResizeMode.cover`."
+ },
+ {
+ "name": "quality",
+ "isOptional": true,
+ "type": "int",
+ "description": "Set the quality of the returned image. A number from 20 to 100, with 100 being the highest quality. Defaults to 80"
+ },
+ {
+ "name": "format",
+ "isOptional": true,
+ "type": "RequestImageFormat",
+ "description": "Specify the format of the image requested. When using 'origin' we force the format to be the same as the original image. When this option is not passed in, images are optimized to modern image formats like Webp."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "download-file",
+ "name": "Download file",
+ "isSpotlight": true,
+ "code": "```dart\nfinal Uint8List file = await supabase\n .storage\n .from('avatars')\n .download('avatar1.png');\n```\n",
+ "response": "```json\n\n```\n"
+ },
+ {
+ "id": "download-file-with-transform",
+ "name": "With transform",
+ "isSpotlight": true,
+ "code": "```dart\nfinal Uint8List file = await supabase\n .storage\n .from('avatars')\n .download(\n 'avatar1.png',\n transform: TransformOptions(\n width: 200,\n height: 200,\n ),\n );\n```\n",
+ "response": "```json\n\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-emptybucket",
+ "$ref": "supabase_flutter.emptyBucket",
+ "description": "Removes all objects inside a single bucket.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: `select`\n - `objects` permissions: `select` and `delete`\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "id",
+ "isOptional": false,
+ "type": "String",
+ "description": "A unique identifier for the bucket you are emptying."
+ }
+ ],
+ "examples": [
+ {
+ "id": "empty-bucket",
+ "name": "Empty bucket",
+ "isSpotlight": true,
+ "code": "```dart\nfinal String res = await supabase\n .storage\n .emptyBucket('avatars');\n```\n",
+ "response": "```json\n'Successfully emptied'\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-getbucket",
+ "$ref": "supabase_flutter.getBucket",
+ "description": "Retrieves the details of an existing Storage bucket.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: `select`\n - `objects` permissions: none\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "id",
+ "isOptional": false,
+ "type": "String",
+ "description": "The unique identifier of the bucket you would like to retrieve."
+ }
+ ],
+ "examples": [
+ {
+ "id": "get-bucket",
+ "name": "Get bucket",
+ "isSpotlight": true,
+ "code": "```dart\nfinal Bucket bucket = await supabase\n .storage\n .getBucket('avatars');\n```\n",
+ "response": "```json\nBucket(\n id: 'avatars',\n name: 'avatars',\n owner: '',\n public: false,\n file_size_limit: 1024,\n allowed_mime_types: [\n 'image/png'\n ],\n created_at: '2024-05-22T22:26:05.100Z',\n updated_at: '2024-05-22T22:26:05.100Z'\n)\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-getpublicurl",
+ "$ref": "supabase_flutter.getPublicUrl",
+ "description": "Retrieve URLs for assets in public buckets\n",
+ "notes": "- The bucket needs to be set to public, either via [updateBucket()](/docs/reference/dart/storage-updatebucket) or by going to Storage on [supabase.com/dashboard](https://supabase.com/dashboard), clicking the overflow menu on a bucket and choosing \"Make public\"\n- Policy permissions required:\n - `buckets` permissions: none\n - `objects` permissions: none\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "path",
+ "isOptional": false,
+ "type": "String",
+ "description": "The path and name of the file to generate the public URL for. For example folder/image.png."
+ },
+ {
+ "name": "transform",
+ "isOptional": true,
+ "type": "TransformOptions",
+ "description": "Transform the asset before serving it to the client.",
+ "subContent": [
+ {
+ "name": "width",
+ "isOptional": true,
+ "type": "int",
+ "description": "The width of the image in pixels."
+ },
+ {
+ "name": "height",
+ "isOptional": true,
+ "type": "int",
+ "description": "The height of the image in pixels."
+ },
+ {
+ "name": "resize",
+ "isOptional": true,
+ "type": "ResizeMode",
+ "description": "Specifies how image cropping should be handled when performing image transformations. Defaults to `ResizeMode.cover`."
+ },
+ {
+ "name": "quality",
+ "isOptional": true,
+ "type": "int",
+ "description": "Set the quality of the returned image. A number from 20 to 100, with 100 being the highest quality. Defaults to 80"
+ },
+ {
+ "name": "format",
+ "isOptional": true,
+ "type": "RequestImageFormat",
+ "description": "Specify the format of the image requested. When using 'origin' we force the format to be the same as the original image. When this option is not passed in, images are optimized to modern image formats like Webp."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "returns-the-url-for-an-asset-in-a-public-bucket",
+ "name": "Returns the URL for an asset in a public bucket",
+ "isSpotlight": true,
+ "code": "```dart\nfinal String publicUrl = supabase\n .storage\n .from('public-bucket')\n .getPublicUrl('avatar1.png');\n```\n",
+ "response": "```json\n'https://example.supabase.co/storage/v1/object/public/public-bucket/folder/avatar1.png'\n```\n"
+ },
+ {
+ "id": "returns-the-url-for-an-asset-in-a-public-bucket-with-transform",
+ "name": "With transform",
+ "isSpotlight": true,
+ "code": "```dart\nfinal String publicUrl = await supabase\n .storage\n .from('public-bucket')\n .getPublicUrl(\n 'avatar1.png',\n transform: TransformOptions(\n width: 200,\n height: 200,\n ),\n );\n```\n",
+ "response": "```json\n'https://example.supabase.co/storage/v1/object/public/public-bucket/folder/avatar1.png'\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-list",
+ "$ref": "supabase_flutter.list",
+ "description": "Lists all the files within a bucket.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: none\n - `objects` permissions: `select`\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "path",
+ "isOptional": false,
+ "type": "String",
+ "description": "The folder path."
+ },
+ {
+ "name": "searchOptions",
+ "isOptional": true,
+ "type": "SearchOptions",
+ "description": "Options for the search operations such as limit and offset.",
+ "subContent": [
+ {
+ "name": "limit",
+ "isOptional": true,
+ "type": "int",
+ "description": "The number of files you want to be returned."
+ },
+ {
+ "name": "offset",
+ "isOptional": true,
+ "type": "int",
+ "description": "The starting position."
+ },
+ {
+ "name": "sortBy",
+ "isOptional": true,
+ "type": "SortBy",
+ "description": "The column to sort by. Can be any column inside a FileObject.",
+ "subContent": [
+ {
+ "name": "column",
+ "isOptional": true,
+ "type": "String"
+ },
+ {
+ "name": "order",
+ "isOptional": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "search",
+ "isOptional": true,
+ "type": "String",
+ "description": "The search string to filter files by."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "list-files-in-a-bucket",
+ "name": "List files in a bucket",
+ "isSpotlight": true,
+ "code": "```dart\nfinal List objects = await supabase\n .storage\n .from('avatars')\n .list();\n```\n",
+ "response": "```json\n[\n FileObject(\n name: 'avatar1.png',\n id: 'e668cf7f-821b-4a2f-9dce-7dfa5dd1cfd2',\n updated_at: '2024-05-22T23:06:05.580Z',\n created_at: '2024-05-22T23:04:34.443Z',\n last_accessed_at: '2024-05-22T23:04:34.443Z',\n metadata: {\n eTag: 'c5e8c553235d9af30ef4f6e280790b92',\n size: 32175,\n mimetype: 'image/png',\n cacheControl: 'max-age=3600',\n lastModified: '2024-05-22T23:06:05.574Z',\n contentLength: 32175,\n httpStatusCode: 200\n ),\n]\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-listbuckets",
+ "$ref": "supabase_flutter.listBuckets",
+ "description": "Retrieves the details of all Storage buckets within an existing product.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: `select`\n - `objects` permissions: none\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "examples": [
+ {
+ "id": "list-buckets",
+ "name": "List buckets",
+ "isSpotlight": true,
+ "code": "```dart\nfinal List buckets = await supabase\n .storage\n .listBuckets();\n```\n",
+ "response": "```json\n[\n Bucket(\n id: 'avatars',\n name: 'avatars',\n owner: '',\n public: false,\n file_size_limit: 1024,\n allowed_mime_types: [\n 'image/png'\n ],\n created_at: '2024-05-22T22:26:05.100Z',\n updated_at: '2024-05-22T22:26:05.100Z'\n ),\n]\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-move",
+ "$ref": "supabase_flutter.move",
+ "description": "Moves an existing file, optionally renaming it at the same time.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: none\n - `objects` permissions: `update` and `select`\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "fromPath",
+ "isOptional": false,
+ "type": "String",
+ "description": "The original file path, including the current file name. For example folder/image.png."
+ },
+ {
+ "name": "toPath",
+ "isOptional": false,
+ "type": "String",
+ "description": "The new file path, including the new file name. For example folder/image-new.png."
+ }
+ ],
+ "examples": [
+ {
+ "id": "move-file",
+ "name": "Move file",
+ "isSpotlight": true,
+ "code": "```dart\nfinal String result = await supabase\n .storage\n .from('avatars')\n .move('public/avatar1.png', 'private/avatar2.png');\n```\n",
+ "response": "```json\n'Successfully moved'\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-remove",
+ "$ref": "supabase_flutter.remove",
+ "description": "Deletes files within the same bucket\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: none\n - `objects` permissions: `delete` and `select`\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "paths",
+ "isOptional": false,
+ "type": "List",
+ "description": "A list of files to delete, including the path and file name. For example ['folder/image.png']."
+ }
+ ],
+ "examples": [
+ {
+ "id": "delete-file",
+ "name": "Delete file",
+ "isSpotlight": true,
+ "code": "```dart\nfinal List objects = await supabase\n .storage\n .from('avatars')\n .remove(['avatar1.png']);\n```\n",
+ "response": "```json\n[\n FileObject(\n name: 'avatar1.png',\n id: 'e668cf7f-821b-4a2f-9dce-7dfa5dd1cfd2',\n updated_at: '2024-05-22T23:06:05.580Z',\n created_at: '2024-05-22T23:04:34.443Z',\n last_accessed_at: '2024-05-22T23:04:34.443Z',\n metadata: {\n eTag: 'c5e8c553235d9af30ef4f6e280790b92',\n size: 32175,\n mimetype: 'image/png',\n cacheControl: 'max-age=3600',\n lastModified: '2024-05-22T23:06:05.574Z',\n contentLength: 32175,\n httpStatusCode: 200\n ),\n]\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-update",
+ "$ref": "supabase_flutter.update",
+ "description": "Replaces an existing file at the specified path with a new one.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: none\n - `objects` permissions: `update` and `select`\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "path",
+ "isOptional": false,
+ "type": "String",
+ "description": "The relative file path. Should be of the format folder/subfolder/filename.png. The bucket must already exist before attempting to update."
+ },
+ {
+ "name": "file",
+ "isOptional": false,
+ "type": "File or Uint8List",
+ "description": "File object to be stored in the bucket."
+ },
+ {
+ "name": "fileOptions",
+ "isOptional": true,
+ "type": "FileOptions",
+ "subContent": [
+ {
+ "name": "cacheControl",
+ "isOptional": true,
+ "type": "String",
+ "description": "The number of seconds the asset is cached in the browser and in the Supabase CDN. This is set in the `Cache-Control` header as `max-age=`. Defaults to 3600 seconds."
+ },
+ {
+ "name": "upsert",
+ "isOptional": true,
+ "type": "bool",
+ "description": "When upsert is set to true, the file is overwritten if it exists. When set to false, an error is thrown if the object already exists. Defaults to false."
+ },
+ {
+ "name": "contentType",
+ "isOptional": true,
+ "type": "String",
+ "description": "The `Content-Type` header value. Gets parsed with MediaType.parse(mime). Throws a FormatError if the media type is invalid."
+ }
+ ]
+ },
+ {
+ "name": "retryAttempts",
+ "isOptional": true,
+ "type": "int",
+ "description": "Sets the retryAttempts parameter set across the storage client. Defaults to 10."
+ },
+ {
+ "name": "retryController",
+ "isOptional": true,
+ "type": "StorageRetryController",
+ "description": "Pass a RetryController instance and call `cancel()` to cancel the retry attempts."
+ }
+ ],
+ "examples": [
+ {
+ "id": "update-file",
+ "name": "Update file",
+ "isSpotlight": true,
+ "code": "```dart\nfinal avatarFile = File('path/to/local/file');\nfinal String path = await supabase.storage.from('avatars').update(\n 'public/avatar1.png',\n avatarFile,\n fileOptions: const FileOptions(cacheControl: '3600', upsert: false),\n );\n```\n",
+ "response": "```json\n'avatars/public/avatar1.png'\n```\n"
+ },
+ {
+ "id": "update-file-on-web",
+ "name": "Update file on web",
+ "code": "```dart\nfinal Uint8List avatarFile = file.bytes;\nfinal String path = await supabase.storage.from('avatars').updateBinary(\n 'public/avatar1.png',\n avatarFile,\n fileOptions: const FileOptions(cacheControl: '3600', upsert: false),\n );\n```\n",
+ "response": "```json\n'avatars/public/avatar1.png'\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-updatebucket",
+ "$ref": "supabase_flutter.updateBucket",
+ "description": "Updates a new Storage bucket\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: `update`\n - `objects` permissions: none\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "id",
+ "isOptional": false,
+ "type": "String",
+ "description": "A unique identifier for the bucket you are updating."
+ },
+ {
+ "name": "bucketOptions",
+ "isOptional": false,
+ "type": "BucketOptions",
+ "description": "A parameter to optionally make the bucket public.",
+ "subContent": [
+ {
+ "name": "public",
+ "isOptional": false,
+ "type": "bool",
+ "description": "The visibility of the bucket. Public buckets don't require an authorization token to download objects, but still require a valid token for all other operations. By default, buckets are private."
+ },
+ {
+ "name": "fileSizeLimit",
+ "isOptional": true,
+ "type": "String",
+ "description": "Specifies the max file size in bytes that can be uploaded to this bucket. The global file size limit takes precedence over this value. The default value is null, which doesn't set a per bucket file size limit."
+ },
+ {
+ "name": "allowedMimeTypes",
+ "isOptional": true,
+ "type": "List",
+ "description": "Specifies the allowed mime types that this bucket can accept during upload. The default value is null, which allows files with all mime types to be uploaded. Each mime type specified can be a wildcard, e.g. image/*, or a specific mime type, e.g. image/png."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "update-bucket",
+ "name": "Update bucket",
+ "isSpotlight": true,
+ "code": "```dart\nfinal String res = await supabase\n .storage\n .updateBucket('avatars', const BucketOptions(public: false));\n```\n",
+ "response": "```json\n'Successfully updated'\n```\n"
+ }
+ ]
+ },
+ {
+ "id": "file-buckets-upload",
+ "$ref": "supabase_flutter.upload",
+ "description": "Uploads a file to an existing bucket.\n",
+ "notes": "- Policy permissions required:\n - `buckets` permissions: none\n - `objects` permissions: `insert`\n- Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works\n",
+ "params": [
+ {
+ "name": "path",
+ "isOptional": false,
+ "type": "String",
+ "description": "The relative file path. Should be of the format folder/subfolder/filename.png. The bucket must already exist before attempting to update."
+ },
+ {
+ "name": "file",
+ "isOptional": false,
+ "type": "File or Uint8List",
+ "description": "File object to be stored in the bucket."
+ },
+ {
+ "name": "fileOptions",
+ "isOptional": true,
+ "type": "FileOptions",
+ "subContent": [
+ {
+ "name": "cacheControl",
+ "isOptional": true,
+ "type": "String",
+ "description": "The number of seconds the asset is cached in the browser and in the Supabase CDN. This is set in the `Cache-Control` header as `max-age=`. Defaults to 3600 seconds."
+ },
+ {
+ "name": "upsert",
+ "isOptional": true,
+ "type": "bool",
+ "description": "When upsert is set to true, the file is overwritten if it exists. When set to false, an error is thrown if the object already exists. Defaults to false."
+ },
+ {
+ "name": "contentType",
+ "isOptional": true,
+ "type": "String",
+ "description": "The `Content-Type` header value. Gets parsed with MediaType.parse(mime). Throws a FormatError if the media type is invalid."
+ }
+ ]
+ },
+ {
+ "name": "retryAttempts",
+ "isOptional": true,
+ "type": "int",
+ "description": "Sets the retryAttempts parameter set across the storage client. Defaults to 10."
+ },
+ {
+ "name": "retryController",
+ "isOptional": true,
+ "type": "StorageRetryController",
+ "description": "Pass a RetryController instance and call `cancel()` to cancel the retry attempts."
+ }
+ ],
+ "examples": [
+ {
+ "id": "upload-file",
+ "name": "Upload file",
+ "isSpotlight": true,
+ "code": "```dart\nfinal avatarFile = File('path/to/file');\nfinal String fullPath = await supabase.storage.from('avatars').upload(\n 'public/avatar1.png',\n avatarFile,\n fileOptions: const FileOptions(cacheControl: '3600', upsert: false),\n );\n```\n",
+ "response": "```json\n'avatars/public/avatar1.png'\n```\n"
+ },
+ {
+ "id": "upload-file-on-web",
+ "name": "Upload file on web",
+ "code": "```dart\nfinal Uint8List avatarFile = file.bytes;\nfinal String fullPath = await supabase.storage.from('avatars').uploadBinary(\n 'public/avatar1.png',\n avatarFile,\n fileOptions: const FileOptions(cacheControl: '3600', upsert: false),\n );\n```\n",
+ "response": "```json\n'avatars/public/avatar1.png'\n```\n"
+ }
+ ]
+ }
+ ],
+ "typeSpec": {
+ "methods": {},
+ "variables": {}
+ }
+}
\ No newline at end of file
diff --git a/apps/docs/scripts/__snapshots__/build-reference-content.v2.json b/apps/docs/scripts/__snapshots__/build-reference-content.v2.json
index f15c6ac0e19..d551a84d8f7 100644
--- a/apps/docs/scripts/__snapshots__/build-reference-content.v2.json
+++ b/apps/docs/scripts/__snapshots__/build-reference-content.v2.json
@@ -1345,6 +1345,20 @@
"product": "storage",
"type": "function"
},
+ "file-buckets-purgebucketcache": {
+ "id": "file-buckets-purgebucketcache",
+ "title": "purgeBucketCache",
+ "slug": "file-buckets-purgebucketcache",
+ "product": "storage",
+ "type": "function"
+ },
+ "file-buckets-purgecache": {
+ "id": "file-buckets-purgecache",
+ "title": "purgeCache",
+ "slug": "file-buckets-purgecache",
+ "product": "storage",
+ "type": "function"
+ },
"file-buckets-remove": {
"id": "file-buckets-remove",
"title": "remove",
@@ -1709,6 +1723,8 @@
"[Circular]",
"[Circular]",
"[Circular]",
+ "[Circular]",
+ "[Circular]",
"[Circular]"
],
"sections": [
@@ -2034,6 +2050,8 @@
"[Circular]",
"[Circular]",
"[Circular]",
+ "[Circular]",
+ "[Circular]",
"[Circular]"
]
},
@@ -2896,6 +2914,14 @@
"id": "file-buckets-move",
"$ref": "@supabase/storage-js.packages/StorageFileApi.default.move"
},
+ {
+ "id": "file-buckets-purgebucketcache",
+ "$ref": "@supabase/storage-js.StorageClient.purgeBucketCache"
+ },
+ {
+ "id": "file-buckets-purgecache",
+ "$ref": "@supabase/storage-js.packages/StorageFileApi.default.purgeCache"
+ },
{
"id": "file-buckets-remove",
"$ref": "@supabase/storage-js.packages/StorageFileApi.default.remove"
@@ -27781,6 +27807,20 @@
"shortText": "OAuth client secret (write-only, not returned in responses)"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_url",
"type": {
@@ -28038,6 +28078,20 @@
"shortText": "Timestamp when the provider was created"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_document",
"type": {
@@ -28510,6 +28564,17 @@
"shortText": "Timestamp when the provider was created"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_document",
"type": {
@@ -28883,6 +28948,17 @@
"shortText": "Timestamp when the provider was created"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_document",
"type": {
@@ -29263,6 +29339,20 @@
"shortText": "OAuth client secret (write-only, not returned in responses)"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_url",
"type": {
@@ -29467,6 +29557,17 @@
"shortText": "Timestamp when the provider was created"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_document",
"type": {
@@ -35210,6 +35311,9 @@
"type": {
"type": "nameOnly",
"name": "TableName"
+ },
+ "comment": {
+ "shortText": "The table or view name to query"
}
}
],
@@ -35230,6 +35334,9 @@
"type": {
"type": "nameOnly",
"name": "ViewName"
+ },
+ "comment": {
+ "shortText": "The table or view name to query"
}
}
],
@@ -35749,6 +35856,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range column to filter on"
}
},
{
@@ -35780,6 +35890,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range value to filter with"
}
}
],
@@ -35830,6 +35943,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range column to filter on"
}
},
{
@@ -35861,6 +35977,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range value to filter with"
}
}
],
@@ -35881,6 +36000,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range column to filter on"
}
},
{
@@ -35912,6 +36034,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range value to filter with"
}
}
],
@@ -35962,6 +36087,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range column to filter on"
}
},
{
@@ -35993,6 +36121,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range value to filter with"
}
}
],
@@ -36206,6 +36337,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36318,6 +36452,9 @@
"value": "not.imatch"
}
]
+ },
+ "comment": {
+ "shortText": "The operator to filter with, following PostgREST syntax"
}
},
{
@@ -36325,6 +36462,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -36366,6 +36506,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36373,6 +36516,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The operator to filter with, following PostgREST syntax"
}
},
{
@@ -36380,6 +36526,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -36413,6 +36562,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36420,6 +36572,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -36452,6 +36607,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36459,6 +36617,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -36479,6 +36640,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36486,6 +36650,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -36517,6 +36684,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36524,6 +36694,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -36544,6 +36717,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36551,6 +36727,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The pattern to match with"
}
}
],
@@ -36582,6 +36761,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36589,6 +36771,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The pattern to match with"
}
}
],
@@ -36609,6 +36794,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36619,6 +36807,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -36639,6 +36830,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36649,6 +36843,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -36669,6 +36866,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36679,6 +36879,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -36699,6 +36902,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36709,6 +36915,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -36773,10 +36982,16 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
- "name": "value"
+ "name": "value",
+ "comment": {
+ "shortText": "The value to filter with"
+ }
}
],
"ret": {
@@ -36808,6 +37023,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36824,6 +37042,9 @@
"name": "boolean"
}
]
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -36874,6 +37095,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36881,6 +37105,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The pattern to match with"
}
}
],
@@ -36912,6 +37139,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36919,6 +37149,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The pattern to match with"
}
}
],
@@ -36939,6 +37172,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36949,6 +37185,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -36969,6 +37208,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -36979,6 +37221,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -36999,6 +37244,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37009,6 +37257,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -37029,6 +37280,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37039,6 +37293,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -37136,6 +37393,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37143,6 +37403,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -37174,6 +37437,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37181,6 +37447,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -37201,6 +37470,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37208,6 +37480,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -37239,6 +37514,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37246,6 +37524,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -37274,6 +37555,9 @@
"type": "nameOnly",
"name": "Row['ColumnName']"
}
+ },
+ "comment": {
+ "shortText": "The object to filter with, with column names as keys mapped\nto their filter values"
}
}
],
@@ -37313,6 +37597,9 @@
"type": "intrinsic",
"name": "unknown"
}
+ },
+ "comment": {
+ "shortText": "The object to filter with, with column names as keys mapped\nto their filter values"
}
}
],
@@ -37412,6 +37699,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37419,6 +37709,9 @@
"type": {
"type": "literal",
"value": "is"
+ },
+ "comment": {
+ "shortText": "The operator to be negated to filter with, following\nPostgREST syntax"
}
},
{
@@ -37426,6 +37719,9 @@
"type": {
"type": "literal",
"value": null
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -37452,6 +37748,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37459,6 +37758,9 @@
"type": {
"type": "nameOnly",
"name": "FilterOperator"
+ },
+ "comment": {
+ "shortText": "The operator to be negated to filter with, following\nPostgREST syntax"
}
},
{
@@ -37466,6 +37768,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -37483,6 +37788,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -37490,6 +37798,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The operator to be negated to filter with, following\nPostgREST syntax"
}
},
{
@@ -37497,6 +37808,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -37637,6 +37951,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to order by"
}
},
{
@@ -37650,7 +37967,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, the result will be in ascending order"
+ }
},
{
"name": "nullsFirst",
@@ -37658,7 +37978,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, `null`s appear first. If `false`,\n`null`s appear last."
+ }
},
{
"name": "referencedTable",
@@ -37666,11 +37989,17 @@
"type": "intrinsic",
"name": "undefined"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Set this to order a referenced table by\nits columns"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -37721,6 +38050,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to order by"
}
},
{
@@ -37734,7 +38066,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, the result will be in ascending order"
+ }
},
{
"name": "nullsFirst",
@@ -37742,7 +38077,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, `null`s appear first. If `false`,\n`null`s appear last."
+ }
},
{
"name": "referencedTable",
@@ -37750,11 +38088,17 @@
"type": "intrinsic",
"name": "string"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Set this to order a referenced table by\nits columns"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -37874,6 +38218,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The array or range column to filter on"
}
},
{
@@ -37893,6 +38240,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The array or range value to filter with"
}
}
],
@@ -37934,6 +38284,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The array or range column to filter on"
}
},
{
@@ -37953,6 +38306,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The array or range value to filter with"
}
}
],
@@ -38100,6 +38456,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38107,6 +38466,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38139,6 +38501,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38146,6 +38511,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38166,6 +38534,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38173,6 +38544,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38205,6 +38579,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38212,6 +38589,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38232,6 +38612,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38239,6 +38622,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38271,6 +38657,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38278,6 +38667,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38298,6 +38690,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38305,6 +38700,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38337,6 +38735,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38344,6 +38745,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38364,6 +38768,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38371,6 +38778,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38403,6 +38813,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -38410,6 +38823,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -38430,6 +38846,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -38437,6 +38856,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The PostgreSQL regular expression pattern to match with"
}
}
],
@@ -38457,6 +38879,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -38464,6 +38889,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The PostgreSQL regular expression pattern to match with"
}
}
],
@@ -38484,6 +38912,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -38491,6 +38922,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The PostgreSQL regular expression pattern to match with"
}
}
],
@@ -38511,6 +38945,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -38518,6 +38955,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The PostgreSQL regular expression pattern to match with"
}
}
],
@@ -38735,6 +39175,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The text or tsvector column to filter on"
}
},
{
@@ -38742,6 +39185,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The query text to match with"
}
},
{
@@ -38755,7 +39201,10 @@
"type": "intrinsic",
"name": "string"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "The text search configuration to use"
+ }
},
{
"name": "type",
@@ -38776,11 +39225,17 @@
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Change how the `query` text is interpreted"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -38830,6 +39285,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The text or tsvector column to filter on"
}
},
{
@@ -38837,6 +39295,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The query text to match with"
}
},
{
@@ -38850,7 +39311,10 @@
"type": "intrinsic",
"name": "string"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "The text search configuration to use"
+ }
},
{
"name": "type",
@@ -38871,11 +39335,17 @@
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Change how the `query` text is interpreted"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -40312,6 +40782,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to order by"
}
},
{
@@ -40325,7 +40798,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, the result will be in ascending order"
+ }
},
{
"name": "nullsFirst",
@@ -40333,7 +40809,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, `null`s appear first. If `false`,\n`null`s appear last."
+ }
},
{
"name": "referencedTable",
@@ -40341,11 +40820,17 @@
"type": "intrinsic",
"name": "undefined"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Set this to order a referenced table by\nits columns"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -40396,6 +40881,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to order by"
}
},
{
@@ -40409,7 +40897,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, the result will be in ascending order"
+ }
},
{
"name": "nullsFirst",
@@ -40417,7 +40908,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, `null`s appear first. If `false`,\n`null`s appear last."
+ }
},
{
"name": "referencedTable",
@@ -40425,11 +40919,17 @@
"type": "intrinsic",
"name": "string"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Set this to order a referenced table by\nits columns"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -41019,6 +41519,14 @@
},
"isOptional": true
},
+ {
+ "name": "replication_ready",
+ "type": {
+ "type": "intrinsic",
+ "name": "boolean"
+ },
+ "isOptional": true
+ },
{
"name": "self",
"type": {
@@ -41031,7 +41539,7 @@
},
"isOptional": true,
"comment": {
- "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages"
+ "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages\nreplication_ready option instructs the server to emit a `system` event once the\nPostgres replication connection backing this channel is established and ready to\nstream changes. Listen for it with `channel.on('system', {}, (payload) => ...)`;\nthe payload's `status` is `'ok'` (`message: 'Replication connection established'`)\non success or `'error'` if the connection is not ready in time."
}
},
{
@@ -41269,7 +41777,7 @@
}
},
"comment": {
- "shortText": "Creates an event handler that listens to changes.",
+ "shortText": "Listen for presence events on this channel — when peers join, leave, or\nsync presence state.",
"text": "- By default, Broadcast and Presence are enabled for all projects.\n- By default, listening to database changes is disabled for new projects due to database performance and security concerns. You can turn it on by managing Realtime's [replication](/docs/guides/api#realtime-api-overview).\n- You can receive the \"previous\" data for updates and deletes by setting the table's `REPLICA IDENTITY` to `FULL` (e.g., `ALTER TABLE your_table REPLICA IDENTITY FULL;`).\n- Row level security is not applied to delete statements. When RLS is enabled and replica identity is set to full, only the primary key is sent to clients.",
"examples": [
{
@@ -41653,12 +42161,21 @@
{
"name": "filter",
"type": {
- "type": "intrinsic",
- "name": "string"
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
},
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -41671,6 +42188,27 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": {
@@ -41755,10 +42293,19 @@
},
{
"name": "filter",
- "type": "[Circular]",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
+ },
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -41768,6 +42315,24 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": "[Circular]",
@@ -41835,10 +42400,19 @@
},
{
"name": "filter",
- "type": "[Circular]",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
+ },
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -41848,6 +42422,24 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": "[Circular]",
@@ -41915,10 +42507,19 @@
},
{
"name": "filter",
- "type": "[Circular]",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
+ },
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -41928,6 +42529,24 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": "[Circular]",
@@ -42012,10 +42631,19 @@
},
{
"name": "filter",
- "type": "[Circular]",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
+ },
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -42025,6 +42653,24 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": "[Circular]",
@@ -43317,6 +43963,11 @@
},
"isOptional": true
},
+ {
+ "name": "replication_ready",
+ "type": "[Circular]",
+ "isOptional": true
+ },
{
"name": "self",
"type": "[Circular]",
@@ -43326,7 +43977,7 @@
},
"isOptional": true,
"comment": {
- "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages"
+ "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages\nreplication_ready option instructs the server to emit a `system` event once the\nPostgres replication connection backing this channel is established and ready to\nstream changes. Listen for it with `channel.on('system', {}, (payload) => ...)`;\nthe payload's `status` is `'ok'` (`message: 'Replication connection established'`)\non success or `'error'` if the connection is not ready in time."
}
},
{
@@ -43765,6 +44416,589 @@
]
}
},
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.constructor": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.constructor",
+ "params": [],
+ "ret": {
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.build": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.build",
+ "params": [],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "comment": {
+ "shortText": "Serialize all conditions into the comma-separated (AND) filter string.\n\nConditions are joined by commas, which the server applies as `AND`. A scalar\nvalue (or single `in` element) that contains a reserved character — `,`,\n`(`, `)`, `\"`, `` — or surrounding whitespace is double-quoted and escaped\nthe way PostgREST does, so commas inside a value are preserved rather than\nread as a condition boundary."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.eq": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.eq",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` equals `value` (`column=eq.value`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.gt": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.gt",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is greater than `value` (`column=gt.value`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.gte": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.gte",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is greater than or equal to `value` (`column=gte.value`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.ilike": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.ilike",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "pattern",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` matches the case-insensitive `pattern` (`column=ilike.pattern`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.imatch": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.imatch",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "pattern",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` matches the case-insensitive POSIX regex `pattern` (`column=imatch.pattern`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.in": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.in",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "values",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ {
+ "type": "intrinsic",
+ "name": "number"
+ },
+ {
+ "type": "intrinsic",
+ "name": "boolean"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is one of `values` (`column=in.(a,b,c)`).\nRequires at least one value; duplicates are removed. An element containing a\nreserved character is double-quoted (`in.(\"a,b\",c)`), so commas inside an\nelement are preserved. `null` is intentionally not accepted (`IN (null)`\nnever matches in SQL) — use `is`/`not('col','is',null)` for null checks."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.is": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.is",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeIsFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` `IS` the given value (`column=is.null`).\nAccepts `null`, a boolean, or the keywords `'null' | 'true' | 'false' | 'unknown'`."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.isDistinct": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.isDistinct",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is distinct from `value` (`column=isdistinct.value`). NULL-safe inequality."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.like": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.like",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "pattern",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` matches the case-sensitive `pattern` (`column=like.pattern`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.lt": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.lt",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is less than `value` (`column=lt.value`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.lte": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.lte",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is less than or equal to `value` (`column=lte.value`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.match": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.match",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "pattern",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` matches the POSIX regex `pattern` (`column=match.pattern`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.neq": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.neq",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` does not equal `value` (`column=neq.value`)."
+ }
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.not": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.not",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "operator",
+ "type": {
+ "type": "literal",
+ "value": "in"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ {
+ "type": "intrinsic",
+ "name": "number"
+ },
+ {
+ "type": "intrinsic",
+ "name": "boolean"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Negate any operator with the `not.` prefix (`column=not.operator.value`).\n`in` takes an array, `is` takes an `IS` keyword/boolean/null, and every\nother operator takes a scalar value.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\npostgresChangesFilter().not('status', 'in', ['draft', 'archived'])\n// → status=not.in.(draft,archived)\npostgresChangesFilter().not('deleted_at', 'is', null)\n// → deleted_at=not.is.null\n```"
+ }
+ ]
+ },
+ "altSignatures": [
+ {
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "operator",
+ "type": {
+ "type": "literal",
+ "value": "is"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeIsFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ }
+ },
+ {
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "operator",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "literal",
+ "value": "eq"
+ },
+ {
+ "type": "literal",
+ "value": "neq"
+ },
+ {
+ "type": "literal",
+ "value": "lt"
+ },
+ {
+ "type": "literal",
+ "value": "lte"
+ },
+ {
+ "type": "literal",
+ "value": "gt"
+ },
+ {
+ "type": "literal",
+ "value": "gte"
+ },
+ {
+ "type": "literal",
+ "value": "like"
+ },
+ {
+ "type": "literal",
+ "value": "ilike"
+ },
+ {
+ "type": "literal",
+ "value": "match"
+ },
+ {
+ "type": "literal",
+ "value": "imatch"
+ },
+ {
+ "type": "literal",
+ "value": "isdistinct"
+ }
+ ]
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ }
+ }
+ ]
+ },
+ "@supabase/realtime-js.RealtimePostgresFilterBuilder.toString": {
+ "name": "@supabase/realtime-js.RealtimePostgresFilterBuilder.toString",
+ "params": [],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "comment": {
+ "shortText": "Alias for build; lets the builder be used wherever a string is expected."
+ }
+ },
"@supabase/realtime-js.RealtimePresence.constructor": {
"name": "@supabase/realtime-js.RealtimePresence.constructor",
"params": [
@@ -44306,6 +45540,26 @@
}
}
},
+ "@supabase/realtime-js.postgresChangesFilter": {
+ "name": "@supabase/realtime-js.postgresChangesFilter",
+ "params": [],
+ "ret": {
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ },
+ "comment": {
+ "shortText": "Create a RealtimePostgresFilterBuilder for composing a Postgres\nChanges `filter`. Conditions are combined with `AND`.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nimport { postgresChangesFilter } from '@supabase/realtime-js'\n\nchannel.on('postgres_changes', {\n event: 'UPDATE',\n schema: 'public',\n table: 'orders',\n filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'),\n}, (payload) => { ... })\n```"
+ }
+ ]
+ }
+ },
"@supabase/storage-js.StorageApiError.constructor": {
"name": "@supabase/storage-js.StorageApiError.constructor",
"params": [
@@ -45237,6 +46491,177 @@
]
}
},
+ "@supabase/storage-js.StorageClient.purgeBucketCache": {
+ "name": "@supabase/storage-js.StorageClient.purgeBucketCache",
+ "params": [
+ {
+ "name": "id",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ "comment": {
+ "shortText": "The unique identifier of the bucket you would like to purge from cache."
+ }
+ },
+ {
+ "name": "options",
+ "type": {
+ "type": "object",
+ "name": "PurgeCacheOptions",
+ "properties": [
+ {
+ "name": "transformations",
+ "type": {
+ "type": "literal",
+ "value": true
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "If true, purges only the transformations (resized/formatted variants) for the object or bucket,\nleaving the original cached file intact. If omitted, purges all cached versions."
+ }
+ }
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Optional purge cache options."
+ }
+ },
+ {
+ "name": "parameters",
+ "type": {
+ "type": "object",
+ "name": "FetchParameters",
+ "properties": [
+ {
+ "name": "cache",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "literal",
+ "value": "default"
+ },
+ {
+ "type": "literal",
+ "value": "no-store"
+ },
+ {
+ "type": "literal",
+ "value": "reload"
+ },
+ {
+ "type": "literal",
+ "value": "no-cache"
+ },
+ {
+ "type": "literal",
+ "value": "force-cache"
+ },
+ {
+ "type": "literal",
+ "value": "only-if-cached"
+ }
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Controls how the request interacts with the browser's HTTP cache.\n- 'default': Use standard cache behavior\n- 'no-store': Bypass cache entirely (useful in Edge Functions)\n- 'reload': Bypass cache but update it with response\n- 'no-cache': Validate with server before using cached response\n- 'force-cache': Use cache even if stale\n- 'only-if-cached': Only use cache, fail if not cached"
+ }
+ },
+ {
+ "name": "signal",
+ "type": {
+ "type": "nameOnly",
+ "name": "AbortSignal"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Pass in an AbortController's signal to cancel the request."
+ }
+ }
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Optional fetch parameters such as an `AbortController` signal."
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "promise",
+ "name": "Promise",
+ "awaited": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "object",
+ "properties": [
+ {
+ "name": "data",
+ "type": {
+ "type": "object",
+ "properties": [
+ {
+ "name": "message",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "error",
+ "type": {
+ "type": "literal",
+ "value": null
+ }
+ }
+ ]
+ },
+ {
+ "type": "object",
+ "properties": [
+ {
+ "name": "data",
+ "type": {
+ "type": "literal",
+ "value": null
+ }
+ },
+ {
+ "name": "error",
+ "type": {
+ "type": "nameOnly",
+ "name": "StorageError"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "comment": {
+ "shortText": "Purges the CDN cache for an entire bucket.\n\nMaps to `DELETE /cdn/{bucket}` on the Storage API. The server\nissues a CDN invalidation for the bucket and returns `{ message: 'success' }`.\n\n**Requires the `service_role` key.** The underlying endpoint enforces\n`service_role` JWT — calls made with the anon key or a user JWT will be\nrejected by the server.\n\n**Hosted CDN feature.** On self-hosted Supabase, the Storage service must\nhave `CDN_PURGE_ENDPOINT_URL` configured and the `purgeCache` tenant\nfeature enabled, otherwise the server returns an error.",
+ "examples": [
+ {
+ "id": "purge-cache-for-an-entire-bucket",
+ "name": "Purge cache for an entire bucket",
+ "code": "```js\nconst { data, error } = await supabase\n .storage\n .purgeBucketCache('avatars')\n```"
+ },
+ {
+ "id": "purge-only-transformations-for-an-entire-bucket",
+ "name": "Purge only transformations for an entire bucket",
+ "code": "```js\nconst { data, error } = await supabase\n .storage\n .purgeBucketCache('avatars', { transformations: true })\n```"
+ }
+ ]
+ }
+ },
"@supabase/storage-js.StorageClient.setHeader": {
"name": "@supabase/storage-js.StorageClient.setHeader",
"params": [
@@ -47642,6 +49067,156 @@
]
}
},
+ "@supabase/storage-js.packages/StorageBucketApi.default.purgeBucketCache": {
+ "name": "@supabase/storage-js.packages/StorageBucketApi.default.purgeBucketCache",
+ "params": [
+ {
+ "name": "id",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ "comment": {
+ "shortText": "The unique identifier of the bucket you would like to purge from cache."
+ }
+ },
+ {
+ "name": "options",
+ "type": {
+ "type": "object",
+ "name": "PurgeCacheOptions",
+ "properties": [
+ {
+ "name": "transformations",
+ "type": "[Circular]",
+ "isOptional": true,
+ "comment": {
+ "shortText": "If true, purges only the transformations (resized/formatted variants) for the object or bucket,\nleaving the original cached file intact. If omitted, purges all cached versions."
+ }
+ }
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Optional purge cache options."
+ }
+ },
+ {
+ "name": "parameters",
+ "type": {
+ "type": "object",
+ "name": "FetchParameters",
+ "properties": [
+ {
+ "name": "cache",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Controls how the request interacts with the browser's HTTP cache.\n- 'default': Use standard cache behavior\n- 'no-store': Bypass cache entirely (useful in Edge Functions)\n- 'reload': Bypass cache but update it with response\n- 'no-cache': Validate with server before using cached response\n- 'force-cache': Use cache even if stale\n- 'only-if-cached': Only use cache, fail if not cached"
+ }
+ },
+ {
+ "name": "signal",
+ "type": {
+ "type": "nameOnly",
+ "name": "AbortSignal"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Pass in an AbortController's signal to cancel the request."
+ }
+ }
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Optional fetch parameters such as an `AbortController` signal."
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "promise",
+ "name": "Promise",
+ "awaited": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "object",
+ "properties": [
+ {
+ "name": "data",
+ "type": {
+ "type": "object",
+ "properties": [
+ {
+ "name": "message",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "error",
+ "type": {
+ "type": "literal",
+ "value": null
+ }
+ }
+ ]
+ },
+ {
+ "type": "object",
+ "properties": [
+ {
+ "name": "data",
+ "type": {
+ "type": "literal",
+ "value": null
+ }
+ },
+ {
+ "name": "error",
+ "type": {
+ "type": "nameOnly",
+ "name": "StorageError"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "comment": {
+ "shortText": "Purges the CDN cache for an entire bucket.\n\nMaps to `DELETE /cdn/{bucket}` on the Storage API. The server\nissues a CDN invalidation for the bucket and returns `{ message: 'success' }`.\n\n**Requires the `service_role` key.** The underlying endpoint enforces\n`service_role` JWT — calls made with the anon key or a user JWT will be\nrejected by the server.\n\n**Hosted CDN feature.** On self-hosted Supabase, the Storage service must\nhave `CDN_PURGE_ENDPOINT_URL` configured and the `purgeCache` tenant\nfeature enabled, otherwise the server returns an error.",
+ "examples": [
+ {
+ "id": "purge-cache-for-an-entire-bucket",
+ "name": "Purge cache for an entire bucket",
+ "code": "```js\nconst { data, error } = await supabase\n .storage\n .purgeBucketCache('avatars')\n```"
+ },
+ {
+ "id": "purge-only-transformations-for-an-entire-bucket",
+ "name": "Purge only transformations for an entire bucket",
+ "code": "```js\nconst { data, error } = await supabase\n .storage\n .purgeBucketCache('avatars', { transformations: true })\n```"
+ }
+ ]
+ }
+ },
"@supabase/storage-js.packages/StorageBucketApi.default.setHeader": {
"name": "@supabase/storage-js.packages/StorageBucketApi.default.setHeader",
"params": [
@@ -48594,30 +50169,12 @@
"type": {
"type": "union",
"subTypes": [
- {
- "type": "literal",
- "value": "default"
- },
- {
- "type": "literal",
- "value": "no-store"
- },
- {
- "type": "literal",
- "value": "reload"
- },
- {
- "type": "literal",
- "value": "no-cache"
- },
- {
- "type": "literal",
- "value": "force-cache"
- },
- {
- "type": "literal",
- "value": "only-if-cached"
- }
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
]
},
"isOptional": true,
@@ -49975,6 +51532,156 @@
]
}
},
+ "@supabase/storage-js.packages/StorageFileApi.default.purgeCache": {
+ "name": "@supabase/storage-js.packages/StorageFileApi.default.purgeCache",
+ "params": [
+ {
+ "name": "path",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ "comment": {
+ "shortText": "The path (relative to the bucket) of the object to purge, e.g. `folder/avatar.png`."
+ }
+ },
+ {
+ "name": "options",
+ "type": {
+ "type": "object",
+ "name": "PurgeCacheOptions",
+ "properties": [
+ {
+ "name": "transformations",
+ "type": "[Circular]",
+ "isOptional": true,
+ "comment": {
+ "shortText": "If true, purges only the transformations (resized/formatted variants) for the object or bucket,\nleaving the original cached file intact. If omitted, purges all cached versions."
+ }
+ }
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Optional purge cache options."
+ }
+ },
+ {
+ "name": "parameters",
+ "type": {
+ "type": "object",
+ "name": "FetchParameters",
+ "properties": [
+ {
+ "name": "cache",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]",
+ "[Circular]"
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Controls how the request interacts with the browser's HTTP cache.\n- 'default': Use standard cache behavior\n- 'no-store': Bypass cache entirely (useful in Edge Functions)\n- 'reload': Bypass cache but update it with response\n- 'no-cache': Validate with server before using cached response\n- 'force-cache': Use cache even if stale\n- 'only-if-cached': Only use cache, fail if not cached"
+ }
+ },
+ {
+ "name": "signal",
+ "type": {
+ "type": "nameOnly",
+ "name": "AbortSignal"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Pass in an AbortController's signal to cancel the request."
+ }
+ }
+ ]
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Optional fetch parameters such as an `AbortController` signal."
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "promise",
+ "name": "Promise",
+ "awaited": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "object",
+ "properties": [
+ {
+ "name": "data",
+ "type": {
+ "type": "object",
+ "properties": [
+ {
+ "name": "message",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "error",
+ "type": {
+ "type": "literal",
+ "value": null
+ }
+ }
+ ]
+ },
+ {
+ "type": "object",
+ "properties": [
+ {
+ "name": "data",
+ "type": {
+ "type": "literal",
+ "value": null
+ }
+ },
+ {
+ "name": "error",
+ "type": {
+ "type": "nameOnly",
+ "name": "StorageError"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "comment": {
+ "shortText": "Purges the CDN cache for a single object in this bucket.\n\nMaps to `DELETE /cdn/{bucket}/{path}` on the Storage API. The server\nissues a CDN invalidation for the object and returns `{ message: 'success' }`.\n\n**Requires the `service_role` key.** The underlying endpoint enforces\n`service_role` JWT — calls made with the anon key or a user JWT will be\nrejected by the server.\n\n**Hosted CDN feature.** On self-hosted Supabase, the Storage service must\nhave `CDN_PURGE_ENDPOINT_URL` configured and the `purgeCache` tenant\nfeature enabled, otherwise the server returns an error.\n\nOperates on a single object path. There is no wildcard or recursion: pass\nthe exact path of the object you want invalidated.",
+ "examples": [
+ {
+ "id": "purge-a-single-cached-object",
+ "name": "Purge a single cached object",
+ "code": "```js\nconst { data, error } = await supabase\n .storage\n .from('avatars')\n .purgeCache('folder/avatar1.png')\n```"
+ },
+ {
+ "id": "purge-only-transformations-for-a-single-object",
+ "name": "Purge only transformations for a single object",
+ "code": "```js\nconst { data, error } = await supabase\n .storage\n .from('avatars')\n .purgeCache('folder/avatar1.png', { transformations: true })\n```"
+ }
+ ]
+ }
+ },
"@supabase/storage-js.packages/StorageFileApi.default.remove": {
"name": "@supabase/storage-js.packages/StorageFileApi.default.remove",
"params": [
@@ -78996,6 +80703,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range column to filter on"
}
},
{
@@ -79027,6 +80737,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range value to filter with"
}
}
],
@@ -79036,6 +80749,39 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for jsonb, array, and range columns. Match only rows where\nevery element appearing in `column` is contained by `value`.",
+ "examples": [
+ {
+ "id": "on-array-columns",
+ "name": "On array columns",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('classes')\n .select('name')\n .containedBy('days', ['monday', 'tuesday', 'wednesday', 'friday'])\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n classes (\n id int8 primary key,\n name text,\n days text[]\n );\n\ninsert into\n classes (id, name, days)\nvalues\n (1, 'Chemistry', array['monday', 'friday']),\n (2, 'History', array['monday', 'wednesday', 'thursday']);\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"name\": \"Chemistry\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ },
+ {
+ "id": "on-range-columns",
+ "name": "On range columns",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('reservations')\n .select()\n .containedBy('during', '[2000-01-01 00:00, 2000-01-01 23:59)')\n```",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"room_name\": \"Emerald\",\n \"during\": \"[\\\"2000-01-01 13:00:00\\\",\\\"2000-01-01 15:00:00\\\")\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ },
+ {
+ "id": "on-jsonb-columns",
+ "name": "On `jsonb` columns",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('users')\n .select('name')\n .containedBy('address', {})\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Michael', '{ \"postcode\": 90210, \"street\": \"Melrose Place\" }'),\n (2, 'Jane', '{}');\n```"
+ },
+ "response": "```json\n {\n \"data\": [\n {\n \"name\": \"Jane\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n }\n\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -79044,6 +80790,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range column to filter on"
}
},
{
@@ -79075,6 +80824,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range value to filter with"
}
}
],
@@ -79095,6 +80847,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range column to filter on"
}
},
{
@@ -79126,6 +80881,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range value to filter with"
}
}
],
@@ -79135,6 +80893,39 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for jsonb, array, and range columns. Match only rows where\n`column` contains every element appearing in `value`.",
+ "examples": [
+ {
+ "id": "on-array-columns",
+ "name": "On array columns",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('issues')\n .select()\n .contains('tags', ['is:open', 'priority:low'])\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n issues (\n id int8 primary key,\n title text,\n tags text[]\n );\n\ninsert into\n issues (id, title, tags)\nvalues\n (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']),\n (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']);\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"title\": \"Cache invalidation is not working\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ },
+ {
+ "id": "on-range-columns",
+ "name": "On range columns",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('reservations')\n .select()\n .contains('during', '[2000-01-01 13:00, 2000-01-01 13:30)')\n```",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"room_name\": \"Emerald\",\n \"during\": \"[\\\"2000-01-01 13:00:00\\\",\\\"2000-01-01 15:00:00\\\")\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ },
+ {
+ "id": "on-jsonb-columns",
+ "name": "On `jsonb` columns",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('users')\n .select('name')\n .contains('address', { postcode: 90210 })\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Michael', '{ \"postcode\": 90210, \"street\": \"Melrose Place\" }'),\n (2, 'Jane', '{}');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"name\": \"Michael\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -79143,6 +80934,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range column to filter on"
}
},
{
@@ -79174,6 +80968,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The jsonb, array, or range value to filter with"
}
}
],
@@ -79388,6 +81185,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79500,6 +81300,9 @@
"value": "not.imatch"
}
]
+ },
+ "comment": {
+ "shortText": "The operator to filter with, following PostgREST syntax"
}
},
{
@@ -79507,6 +81310,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -79516,6 +81322,30 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows which satisfy the filter. This is an escape hatch - you\nshould use the specific filter methods wherever possible.\n\nUnlike most filters, `opearator` and `value` are used as-is and need to\nfollow [PostgREST\nsyntax](https://postgrest.org/en/stable/api.html#operators). You also need\nto make sure they are properly sanitized.",
+ "text": "filter() expects you to use the raw PostgREST syntax for the filter values.\n\n```ts\n.filter('id', 'in', '(5,6,7)') // Use `()` for `in` filter\n.filter('arraycol', 'cs', '{\"a\",\"b\"}') // Use `cs` for `contains()`, `{}` for array values\n```",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('characters')\n .select()\n .filter('name', 'in', '(\"Han\",\"Yoda\")')\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Luke'),\n (2, 'Leia'),\n (3, 'Han');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 3,\n \"name\": \"Han\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ },
+ {
+ "id": "on-a-referenced-table",
+ "name": "On a referenced table",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('orchestral_sections')\n .select(`\n name,\n instruments!inner (\n name\n )\n `)\n .filter('instruments.name', 'eq', 'flute')\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 2, 'flute'),\n (2, 1, 'violin');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"name\": \"woodwinds\",\n \"instruments\": [\n {\n \"name\": \"flute\"\n }\n ]\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -79524,6 +81354,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79531,6 +81364,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The operator to filter with, following PostgREST syntax"
}
},
{
@@ -79538,6 +81374,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -79571,6 +81410,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79578,6 +81420,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -79587,6 +81432,21 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` is greater than `value`.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('characters')\n .select()\n .gt('id', 2)\n```",
+ "description": "When using [reserved words](https://www.postgresql.org/docs/current/sql-keywords-appendix.html) for column names you need\nto add double quotes e.g. `.gt('\"order\"', 2)`",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Luke'),\n (2, 'Leia'),\n (3, 'Han');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 3,\n \"name\": \"Han\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -79595,6 +81455,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79602,6 +81465,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -79622,6 +81488,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79629,6 +81498,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -79638,6 +81510,20 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` is greater than or equal to `value`.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('characters')\n .select()\n .gte('id', 2)\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Luke'),\n (2, 'Leia'),\n (3, 'Han');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 2,\n \"name\": \"Leia\"\n },\n {\n \"id\": 3,\n \"name\": \"Han\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -79646,6 +81532,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79653,6 +81542,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -79673,6 +81565,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79680,6 +81575,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The pattern to match with"
}
}
],
@@ -79689,6 +81587,20 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` matches `pattern` case-insensitively.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('characters')\n .select()\n .ilike('name', '%lu%')\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Luke'),\n (2, 'Leia'),\n (3, 'Han');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"name\": \"Luke\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -79697,6 +81609,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79704,6 +81619,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The pattern to match with"
}
}
],
@@ -79724,6 +81642,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79734,6 +81655,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -79743,6 +81667,9 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` matches all of `patterns` case-insensitively."
+ },
"altSignatures": [
{
"params": [
@@ -79751,6 +81678,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79761,6 +81691,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -79781,6 +81714,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79791,6 +81727,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -79800,6 +81739,9 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` matches any of `patterns` case-insensitively."
+ },
"altSignatures": [
{
"params": [
@@ -79808,6 +81750,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79818,6 +81763,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -79882,10 +81830,16 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
- "name": "value"
+ "name": "value",
+ "comment": {
+ "shortText": "The value to filter with"
+ }
}
],
"ret": {
@@ -79894,6 +81848,21 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` IS `value`.\n\nFor non-boolean columns, this is only relevant for checking if the value of\n`column` is NULL by setting `value` to `null`.\n\nFor boolean columns, you can also set `value` to `true` or `false` and it\nwill behave the same way as `.eq()`.",
+ "examples": [
+ {
+ "id": "checking-for-nullness-true-or-false",
+ "name": "Checking for nullness, true or false",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('countries')\n .select()\n .is('name', null)\n```",
+ "description": "Using the `eq()` filter doesn't work when filtering for `null`.\n\nInstead, you need to use `is()`.",
+ "data": {
+ "sql": "```sql\ncreate table\n countries (id int8 primary key, name text);\n\ninsert into\n countries (id, name)\nvalues\n (1, 'null'),\n (2, null);\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 2,\n \"name\": \"null\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -79902,6 +81871,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79918,6 +81890,9 @@
"name": "boolean"
}
]
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -79968,6 +81943,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79975,6 +81953,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The pattern to match with"
}
}
],
@@ -79984,6 +81965,20 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` matches `pattern` case-sensitively.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('characters')\n .select()\n .like('name', '%Lu%')\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Luke'),\n (2, 'Leia'),\n (3, 'Han');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"name\": \"Luke\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -79992,6 +81987,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -79999,6 +81997,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The pattern to match with"
}
}
],
@@ -80019,6 +82020,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80029,6 +82033,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -80038,6 +82045,9 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` matches all of `patterns` case-sensitively."
+ },
"altSignatures": [
{
"params": [
@@ -80046,6 +82056,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80056,6 +82069,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -80076,6 +82092,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80086,6 +82105,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -80095,6 +82117,9 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` matches any of `patterns` case-sensitively."
+ },
"altSignatures": [
{
"params": [
@@ -80103,6 +82128,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80113,6 +82141,9 @@
"type": "intrinsic",
"name": "string"
}
+ },
+ "comment": {
+ "shortText": "The patterns to match with"
}
}
],
@@ -80211,6 +82242,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80218,6 +82252,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -80227,6 +82264,20 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` is less than `value`.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('characters')\n .select()\n .lt('id', 2)\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Luke'),\n (2, 'Leia'),\n (3, 'Han');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"name\": \"Luke\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -80235,6 +82286,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80242,6 +82296,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -80262,6 +82319,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80269,6 +82329,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -80278,6 +82341,20 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` is less than or equal to `value`.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('characters')\n .select()\n .lte('id', 2)\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Luke'),\n (2, 'Leia'),\n (3, 'Han');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"name\": \"Luke\"\n },\n {\n \"id\": 2,\n \"name\": \"Leia\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -80286,6 +82363,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80293,6 +82373,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with"
}
}
],
@@ -80321,6 +82404,9 @@
"type": "nameOnly",
"name": "Row['ColumnName']"
}
+ },
+ "comment": {
+ "shortText": "The object to filter with, with column names as keys mapped\nto their filter values"
}
}
],
@@ -80330,6 +82416,20 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where each column in `query` keys is equal to its\nassociated value. Shorthand for multiple `.eq()`s.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('characters')\n .select('name')\n .match({ id: 2, name: 'Leia' })\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n characters (id int8 primary key, name text);\n\ninsert into\n characters (id, name)\nvalues\n (1, 'Luke'),\n (2, 'Leia'),\n (3, 'Han');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"name\": \"Leia\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -80346,6 +82446,9 @@
"type": "intrinsic",
"name": "unknown"
}
+ },
+ "comment": {
+ "shortText": "The object to filter with, with column names as keys mapped\nto their filter values"
}
}
],
@@ -80445,6 +82548,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80452,6 +82558,9 @@
"type": {
"type": "literal",
"value": "is"
+ },
+ "comment": {
+ "shortText": "The operator to be negated to filter with, following\nPostgREST syntax"
}
},
{
@@ -80459,9 +82568,15 @@
"type": {
"type": "literal",
"value": null
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
+ "comment": {
+ "shortText": "Match only rows which doesn't satisfy the filter.\n\nUnlike most filters, `opearator` and `value` are used as-is and need to\nfollow [PostgREST\nsyntax](https://postgrest.org/en/stable/api.html#operators). You also need\nto make sure they are properly sanitized."
+ },
"altSignatures": [
{
"params": [
@@ -80470,6 +82585,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80477,6 +82595,9 @@
"type": {
"type": "nameOnly",
"name": "FilterOperator"
+ },
+ "comment": {
+ "shortText": "The operator to be negated to filter with, following\nPostgREST syntax"
}
},
{
@@ -80484,6 +82605,9 @@
"type": {
"type": "nameOnly",
"name": "Row['ColumnName']"
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -80501,6 +82625,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -80508,6 +82635,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The operator to be negated to filter with, following\nPostgREST syntax"
}
},
{
@@ -80515,6 +82645,9 @@
"type": {
"type": "intrinsic",
"name": "unknown"
+ },
+ "comment": {
+ "shortText": "The value to filter with, following PostgREST syntax"
}
}
],
@@ -80656,6 +82789,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to order by"
}
},
{
@@ -80669,7 +82805,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, the result will be in ascending order"
+ }
},
{
"name": "nullsFirst",
@@ -80677,7 +82816,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, `null`s appear first. If `false`,\n`null`s appear last."
+ }
},
{
"name": "referencedTable",
@@ -80685,11 +82827,17 @@
"type": "intrinsic",
"name": "undefined"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Set this to order a referenced table by\nits columns"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -80698,6 +82846,9 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Order the query result by `column`.\n\nYou can call this method multiple times to order by multiple columns.\n\nYou can order referenced tables, but it only affects the ordering of the\nparent table if you use `!inner` in the query."
+ },
"altSignatures": [
{
"params": [
@@ -80706,6 +82857,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to order by"
}
},
{
@@ -80719,7 +82873,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, the result will be in ascending order"
+ }
},
{
"name": "nullsFirst",
@@ -80727,7 +82884,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, `null`s appear first. If `false`,\n`null`s appear last."
+ }
},
{
"name": "referencedTable",
@@ -80735,11 +82895,17 @@
"type": "intrinsic",
"name": "string"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Set this to order a referenced table by\nits columns"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -80859,6 +83025,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The array or range column to filter on"
}
},
{
@@ -80878,6 +83047,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The array or range value to filter with"
}
}
],
@@ -80887,6 +83059,30 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for array and range columns. Match only rows where\n`column` and `value` have an element in common.",
+ "examples": [
+ {
+ "id": "on-array-columns",
+ "name": "On array columns",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('issues')\n .select('title')\n .overlaps('tags', ['is:closed', 'severity:high'])\n```",
+ "data": {
+ "sql": "```sql\ncreate table\n issues (\n id int8 primary key,\n title text,\n tags text[]\n );\n\ninsert into\n issues (id, title, tags)\nvalues\n (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']),\n (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']);\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"title\": \"Cache invalidation is not working\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ },
+ {
+ "id": "on-range-columns",
+ "name": "On range columns",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('reservations')\n .select()\n .overlaps('during', '[2000-01-01 12:45, 2000-01-01 13:15)')\n```",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"room_name\": \"Emerald\",\n \"during\": \"[\\\"2000-01-01 13:00:00\\\",\\\"2000-01-01 15:00:00\\\")\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -80895,6 +83091,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The array or range column to filter on"
}
},
{
@@ -80914,6 +83113,9 @@
}
}
]
+ },
+ "comment": {
+ "shortText": "The array or range value to filter with"
}
}
],
@@ -81062,6 +83264,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81069,6 +83274,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81078,6 +83286,21 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for range columns. Match only rows where `column` is\nmutually exclusive to `range` and there can be no element between the two\nranges.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('reservations')\n .select()\n .rangeAdjacent('during', '[2000-01-01 12:00, 2000-01-01 13:00)')\n```",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"room_name\": \"Emerald\",\n \"during\": \"[\\\"2000-01-01 13:00:00\\\",\\\"2000-01-01 15:00:00\\\")\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -81086,6 +83309,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81093,6 +83319,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81113,6 +83342,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81120,6 +83352,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81129,6 +83364,21 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for range columns. Match only rows where every element in\n`column` is greater than any element in `range`.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('reservations')\n .select()\n .rangeGt('during', '[2000-01-02 08:00, 2000-01-02 09:00)')\n```",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```"
+ },
+ "response": "```json\n {\n \"data\": [\n {\n \"id\": 2,\n \"room_name\": \"Topaz\",\n \"during\": \"[\\\"2000-01-02 09:00:00\\\",\\\"2000-01-02 10:00:00\\\")\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n }\n\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -81137,6 +83387,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81144,6 +83397,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81164,6 +83420,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81171,6 +83430,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81180,6 +83442,21 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for range columns. Match only rows where every element in\n`column` is either contained in `range` or greater than any element in\n`range`.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('reservations')\n .select()\n .rangeGte('during', '[2000-01-02 08:30, 2000-01-02 09:30)')\n```",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```"
+ },
+ "response": "```json\n {\n \"data\": [\n {\n \"id\": 2,\n \"room_name\": \"Topaz\",\n \"during\": \"[\\\"2000-01-02 09:00:00\\\",\\\"2000-01-02 10:00:00\\\")\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n }\n\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -81188,6 +83465,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81195,6 +83475,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81215,6 +83498,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81222,6 +83508,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81231,6 +83520,21 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for range columns. Match only rows where every element in\n`column` is less than any element in `range`.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('reservations')\n .select()\n .rangeLt('during', '[2000-01-01 15:00, 2000-01-01 16:00)')\n```",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"id\": 1,\n \"room_name\": \"Emerald\",\n \"during\": \"[\\\"2000-01-01 13:00:00\\\",\\\"2000-01-01 15:00:00\\\")\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -81239,6 +83543,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81246,6 +83553,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81266,6 +83576,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81273,6 +83586,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81282,6 +83598,21 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for range columns. Match only rows where every element in\n`column` is either contained in `range` or less than any element in\n`range`.",
+ "examples": [
+ {
+ "id": "with-select",
+ "name": "With `select()`",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('reservations')\n .select()\n .rangeLte('during', '[2000-01-01 14:00, 2000-01-01 16:00)')\n```",
+ "description": "Postgres supports a number of [range\ntypes](https://www.postgresql.org/docs/current/rangetypes.html). You\ncan filter on range columns using the string representation of range\nvalues.",
+ "data": {
+ "sql": "```sql\ncreate table\n reservations (\n id int8 primary key,\n room_name text,\n during tsrange\n );\n\ninsert into\n reservations (id, room_name, during)\nvalues\n (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'),\n (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)');\n```"
+ },
+ "response": "```json\n {\n \"data\": [\n {\n \"id\": 1,\n \"room_name\": \"Emerald\",\n \"during\": \"[\\\"2000-01-01 13:00:00\\\",\\\"2000-01-01 15:00:00\\\")\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n }\n\n```"
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -81290,6 +83621,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range column to filter on"
}
},
{
@@ -81297,6 +83631,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The range to filter with"
}
}
],
@@ -81317,6 +83654,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -81324,6 +83664,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The PostgreSQL regular expression pattern to match with"
}
}
],
@@ -81333,6 +83676,9 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` matches the PostgreSQL regex `pattern`\ncase-insensitively (using the `~*` operator)."
+ },
"altSignatures": [
{
"params": [
@@ -81341,6 +83687,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -81348,6 +83697,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The PostgreSQL regular expression pattern to match with"
}
}
],
@@ -81368,6 +83720,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -81375,6 +83730,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The PostgreSQL regular expression pattern to match with"
}
}
],
@@ -81384,6 +83742,9 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Match only rows where `column` matches the PostgreSQL regex `pattern`\ncase-sensitively (using the `~` operator)."
+ },
"altSignatures": [
{
"params": [
@@ -81392,6 +83753,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to filter on"
}
},
{
@@ -81399,6 +83763,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The PostgreSQL regular expression pattern to match with"
}
}
],
@@ -81616,6 +83983,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The text or tsvector column to filter on"
}
},
{
@@ -81623,6 +83993,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The query text to match with"
}
},
{
@@ -81636,7 +84009,10 @@
"type": "intrinsic",
"name": "string"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "The text search configuration to use"
+ }
},
{
"name": "type",
@@ -81657,11 +84033,17 @@
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Change how the `query` text is interpreted"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -81670,6 +84052,39 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Only relevant for text and tsvector columns. Match only rows where\n`column` matches the query string in `query`.",
+ "text": "- For more information, see [Postgres full text search](/docs/guides/database/full-text-search).",
+ "examples": [
+ {
+ "id": "text-search",
+ "name": "Text search",
+ "code": "```ts\nconst result = await supabase\n .from(\"texts\")\n .select(\"content\")\n .textSearch(\"content\", `'eggs' & 'ham'`, {\n config: \"english\",\n });\n```",
+ "data": {
+ "sql": "```sql\ncreate table texts (\n id bigint\n primary key\n generated always as identity,\n content text\n);\n\ninsert into texts (content) values\n ('Four score and seven years ago'),\n ('The road goes ever on and on'),\n ('Green eggs and ham')\n;\n```"
+ },
+ "response": "```json\n{\n \"data\": [\n {\n \"content\": \"Green eggs and ham\"\n }\n ],\n \"status\": 200,\n \"statusText\": \"OK\"\n}\n```"
+ },
+ {
+ "id": "basic-normalization",
+ "name": "Basic normalization",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('quotes')\n .select('catchphrase')\n .textSearch('catchphrase', `'fat' & 'cat'`, {\n type: 'plain',\n config: 'english'\n })\n```",
+ "description": "Uses PostgreSQL's `plainto_tsquery` function."
+ },
+ {
+ "id": "full-normalization",
+ "name": "Full normalization",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('quotes')\n .select('catchphrase')\n .textSearch('catchphrase', `'fat' & 'cat'`, {\n type: 'phrase',\n config: 'english'\n })\n```",
+ "description": "Uses PostgreSQL's `phraseto_tsquery` function."
+ },
+ {
+ "id": "websearch",
+ "name": "Websearch",
+ "code": "```ts\nconst { data, error } = await supabase\n .from('quotes')\n .select('catchphrase')\n .textSearch('catchphrase', `'fat or cat'`, {\n type: 'websearch',\n config: 'english'\n })\n```",
+ "description": "Uses PostgreSQL's `websearch_to_tsquery` function.\nThis function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used\nwith advanced operators.\n\n- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.\n- `\"quoted text\"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.\n- `OR`: the word “or” will be converted to the | operator.\n- `-`: a dash will be converted to the ! operator."
+ }
+ ]
+ },
"altSignatures": [
{
"params": [
@@ -81678,6 +84093,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The text or tsvector column to filter on"
}
},
{
@@ -81685,6 +84103,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The query text to match with"
}
},
{
@@ -81698,7 +84119,10 @@
"type": "intrinsic",
"name": "string"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "The text search configuration to use"
+ }
},
{
"name": "type",
@@ -81719,11 +84143,17 @@
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Change how the `query` text is interpreted"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -83166,6 +85596,9 @@
"type": {
"type": "nameOnly",
"name": "ColumnName"
+ },
+ "comment": {
+ "shortText": "The column to order by"
}
},
{
@@ -83179,7 +85612,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, the result will be in ascending order"
+ }
},
{
"name": "nullsFirst",
@@ -83187,7 +85623,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, `null`s appear first. If `false`,\n`null`s appear last."
+ }
},
{
"name": "referencedTable",
@@ -83195,11 +85634,17 @@
"type": "intrinsic",
"name": "undefined"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Set this to order a referenced table by\nits columns"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -83208,6 +85653,9 @@
"name": "this"
}
},
+ "comment": {
+ "shortText": "Order the query result by `column`.\n\nYou can call this method multiple times to order by multiple columns.\n\nYou can order referenced tables, but it only affects the ordering of the\nparent table if you use `!inner` in the query."
+ },
"altSignatures": [
{
"params": [
@@ -83216,6 +85664,9 @@
"type": {
"type": "intrinsic",
"name": "string"
+ },
+ "comment": {
+ "shortText": "The column to order by"
}
},
{
@@ -83229,7 +85680,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, the result will be in ascending order"
+ }
},
{
"name": "nullsFirst",
@@ -83237,7 +85691,10 @@
"type": "intrinsic",
"name": "boolean"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "If `true`, `null`s appear first. If `false`,\n`null`s appear last."
+ }
},
{
"name": "referencedTable",
@@ -83245,11 +85702,17 @@
"type": "intrinsic",
"name": "string"
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Set this to order a referenced table by\nits columns"
+ }
}
]
},
- "isOptional": true
+ "isOptional": true,
+ "comment": {
+ "shortText": "Named parameters"
+ }
}
],
"ret": {
@@ -83847,6 +86310,14 @@
},
"isOptional": true
},
+ {
+ "name": "replication_ready",
+ "type": {
+ "type": "intrinsic",
+ "name": "boolean"
+ },
+ "isOptional": true
+ },
{
"name": "self",
"type": {
@@ -83859,7 +86330,7 @@
},
"isOptional": true,
"comment": {
- "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages"
+ "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages\nreplication_ready option instructs the server to emit a `system` event once the\nPostgres replication connection backing this channel is established and ready to\nstream changes. Listen for it with `channel.on('system', {}, (payload) => ...)`;\nthe payload's `status` is `'ok'` (`message: 'Replication connection established'`)\non success or `'error'` if the connection is not ready in time."
}
},
{
@@ -84100,7 +86571,7 @@
}
},
"comment": {
- "shortText": "Creates an event handler that listens to changes."
+ "shortText": "Listen for presence events on this channel — when peers join, leave, or\nsync presence state."
},
"altSignatures": [
{
@@ -84422,12 +86893,21 @@
{
"name": "filter",
"type": {
- "type": "intrinsic",
- "name": "string"
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
},
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -84440,6 +86920,27 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": {
@@ -84524,10 +87025,19 @@
},
{
"name": "filter",
- "type": "[Circular]",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
+ },
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -84537,6 +87047,24 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": "[Circular]",
@@ -84604,10 +87132,19 @@
},
{
"name": "filter",
- "type": "[Circular]",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
+ },
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -84617,6 +87154,24 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": "[Circular]",
@@ -84684,10 +87239,19 @@
},
{
"name": "filter",
- "type": "[Circular]",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
+ },
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -84697,6 +87261,24 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": "[Circular]",
@@ -84781,10 +87363,19 @@
},
{
"name": "filter",
- "type": "[Circular]",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ "[Circular]",
+ {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ ]
+ },
"isOptional": true,
"comment": {
- "shortText": "Receive database changes when filter is matched."
+ "shortText": "Receive database changes only when the filter is matched.\n\nA filter is a `column=operator.value` expression, e.g. `id=eq.1` or\n`title=like.%foo%`. See RealtimePostgresChangesFilterOperator for\nthe available operators.\n\nMultiple filters can be combined with commas; they are applied as an `AND`\ncondition: `filter: 'id=gt.0,id=lt.100'`.\n\nAny operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.\n\nThe server splits conditions on commas outside quotes/parentheses. To\ninclude a reserved character (`,`, `(`, `)`) in a value, wrap it in double\nquotes PostgREST-style: `name=eq.\"a,b\"`. The RealtimePostgresFilterBuilder\ndoes this quoting for you.\n\nInstead of a raw string you can pass a RealtimePostgresFilterBuilder\n(via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the\nSDK serializes it to a string automatically."
}
},
{
@@ -84794,6 +87385,24 @@
"shortText": "The database schema to listen to."
}
},
+ {
+ "name": "select",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Restrict the change payload to a subset of columns instead of receiving the\nfull row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)\nand the data transferred per event.\n\nThe listed columns must be selectable by the subscribing role.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nchannel.on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'users',\n select: ['id', 'first_name'],\n}, (payload) => {\n // payload.new only contains { id, first_name }\n})\n```"
+ }
+ ]
+ }
+ },
{
"name": "table",
"type": "[Circular]",
@@ -85441,6 +88050,10 @@
"type": {
"type": "union",
"subTypes": [
+ {
+ "type": "literal",
+ "value": "postgres_changes"
+ },
{
"type": "literal",
"value": "broadcast"
@@ -85448,10 +88061,6 @@
{
"type": "literal",
"value": "presence"
- },
- {
- "type": "literal",
- "value": "postgres_changes"
}
]
},
@@ -86091,6 +88700,11 @@
},
"isOptional": true
},
+ {
+ "name": "replication_ready",
+ "type": "[Circular]",
+ "isOptional": true
+ },
{
"name": "self",
"type": "[Circular]",
@@ -86100,7 +88714,7 @@
},
"isOptional": true,
"comment": {
- "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages"
+ "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages\nreplication_ready option instructs the server to emit a `system` event once the\nPostgres replication connection backing this channel is established and ready to\nstream changes. Listen for it with `channel.on('system', {}, (payload) => ...)`;\nthe payload's `status` is `'ok'` (`message: 'Replication connection established'`)\non success or `'error'` if the connection is not ready in time."
}
},
{
@@ -86541,6 +89155,589 @@
]
}
},
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.constructor": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.constructor",
+ "params": [],
+ "ret": {
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.build": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.build",
+ "params": [],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "comment": {
+ "shortText": "Serialize all conditions into the comma-separated (AND) filter string.\n\nConditions are joined by commas, which the server applies as `AND`. A scalar\nvalue (or single `in` element) that contains a reserved character — `,`,\n`(`, `)`, `\"`, `` — or surrounding whitespace is double-quoted and escaped\nthe way PostgREST does, so commas inside a value are preserved rather than\nread as a condition boundary."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.eq": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.eq",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` equals `value` (`column=eq.value`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.gt": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.gt",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is greater than `value` (`column=gt.value`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.gte": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.gte",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is greater than or equal to `value` (`column=gte.value`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.ilike": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.ilike",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "pattern",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` matches the case-insensitive `pattern` (`column=ilike.pattern`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.imatch": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.imatch",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "pattern",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` matches the case-insensitive POSIX regex `pattern` (`column=imatch.pattern`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.in": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.in",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "values",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ {
+ "type": "intrinsic",
+ "name": "number"
+ },
+ {
+ "type": "intrinsic",
+ "name": "boolean"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is one of `values` (`column=in.(a,b,c)`).\nRequires at least one value; duplicates are removed. An element containing a\nreserved character is double-quoted (`in.(\"a,b\",c)`), so commas inside an\nelement are preserved. `null` is intentionally not accepted (`IN (null)`\nnever matches in SQL) — use `is`/`not('col','is',null)` for null checks."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.is": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.is",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeIsFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` `IS` the given value (`column=is.null`).\nAccepts `null`, a boolean, or the keywords `'null' | 'true' | 'false' | 'unknown'`."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.isDistinct": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.isDistinct",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is distinct from `value` (`column=isdistinct.value`). NULL-safe inequality."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.like": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.like",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "pattern",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` matches the case-sensitive `pattern` (`column=like.pattern`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.lt": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.lt",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is less than `value` (`column=lt.value`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.lte": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.lte",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` is less than or equal to `value` (`column=lte.value`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.match": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.match",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "pattern",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` matches the POSIX regex `pattern` (`column=match.pattern`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.neq": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.neq",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Match rows where `column` does not equal `value` (`column=neq.value`)."
+ }
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.not": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.not",
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "operator",
+ "type": {
+ "type": "literal",
+ "value": "in"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "intrinsic",
+ "name": "string"
+ },
+ {
+ "type": "intrinsic",
+ "name": "number"
+ },
+ {
+ "type": "intrinsic",
+ "name": "boolean"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ },
+ "comment": {
+ "shortText": "Negate any operator with the `not.` prefix (`column=not.operator.value`).\n`in` takes an array, `is` takes an `IS` keyword/boolean/null, and every\nother operator takes a scalar value.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\npostgresChangesFilter().not('status', 'in', ['draft', 'archived'])\n// → status=not.in.(draft,archived)\npostgresChangesFilter().not('deleted_at', 'is', null)\n// → deleted_at=not.is.null\n```"
+ }
+ ]
+ },
+ "altSignatures": [
+ {
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "operator",
+ "type": {
+ "type": "literal",
+ "value": "is"
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeIsFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ }
+ },
+ {
+ "params": [
+ {
+ "name": "column",
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ {
+ "name": "operator",
+ "type": {
+ "type": "union",
+ "subTypes": [
+ {
+ "type": "literal",
+ "value": "match"
+ },
+ {
+ "type": "literal",
+ "value": "eq"
+ },
+ {
+ "type": "literal",
+ "value": "neq"
+ },
+ {
+ "type": "literal",
+ "value": "gt"
+ },
+ {
+ "type": "literal",
+ "value": "gte"
+ },
+ {
+ "type": "literal",
+ "value": "lt"
+ },
+ {
+ "type": "literal",
+ "value": "lte"
+ },
+ {
+ "type": "literal",
+ "value": "like"
+ },
+ {
+ "type": "literal",
+ "value": "ilike"
+ },
+ {
+ "type": "literal",
+ "value": "isdistinct"
+ },
+ {
+ "type": "literal",
+ "value": "imatch"
+ }
+ ]
+ }
+ },
+ {
+ "name": "value",
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimeFilterValue"
+ }
+ }
+ ],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "this"
+ }
+ }
+ }
+ ]
+ },
+ "@supabase/supabase-js.RealtimePostgresFilterBuilder.toString": {
+ "name": "@supabase/supabase-js.RealtimePostgresFilterBuilder.toString",
+ "params": [],
+ "ret": {
+ "type": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "comment": {
+ "shortText": "Alias for build; lets the builder be used wherever a string is expected."
+ }
+ },
"@supabase/supabase-js.RealtimePresence.constructor": {
"name": "@supabase/supabase-js.RealtimePresence.constructor",
"params": [
@@ -87441,6 +90638,11 @@
},
"isOptional": true
},
+ {
+ "name": "replication_ready",
+ "type": "[Circular]",
+ "isOptional": true
+ },
{
"name": "self",
"type": "[Circular]",
@@ -87450,7 +90652,7 @@
},
"isOptional": true,
"comment": {
- "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages"
+ "shortText": "self option enables client to receive message it broadcast\nack option instructs server to acknowledge that broadcast message was received\nreplay option instructs server to replay broadcast messages\nreplication_ready option instructs the server to emit a `system` event once the\nPostgres replication connection backing this channel is established and ready to\nstream changes. Listen for it with `channel.on('system', {}, (payload) => ...)`;\nthe payload's `status` is `'ok'` (`message: 'Replication connection established'`)\non success or `'error'` if the connection is not ready in time."
}
},
{
@@ -89837,6 +93039,20 @@
"shortText": "OAuth client secret (write-only, not returned in responses)"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_url",
"type": {
@@ -90094,6 +93310,20 @@
"shortText": "Timestamp when the provider was created"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_document",
"type": {
@@ -90566,6 +93796,17 @@
"shortText": "Timestamp when the provider was created"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_document",
"type": {
@@ -90939,6 +94180,17 @@
"shortText": "Timestamp when the provider was created"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_document",
"type": {
@@ -91319,6 +94571,20 @@
"shortText": "OAuth client secret (write-only, not returned in responses)"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": {
+ "type": "intrinsic",
+ "name": "string"
+ }
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_url",
"type": {
@@ -91523,6 +94789,17 @@
"shortText": "Timestamp when the provider was created"
}
},
+ {
+ "name": "custom_claims_allowlist",
+ "type": {
+ "type": "array",
+ "elemType": "[Circular]"
+ },
+ "isOptional": true,
+ "comment": {
+ "shortText": "Allowlist of raw identity provider claim keys to copy verbatim into the\nuser's `custom_claims` field (within `identity_data` and\n`raw_user_meta_data`), e.g. `[\"groups\", \"org_id\", \"mail\"]`. This is an\nopt-in allowlist that defaults to empty (no claims captured) and operates\nindependently from `attribute_mapping`."
+ }
+ },
{
"name": "discovery_document",
"type": {
@@ -97631,6 +100908,26 @@
"shortText": "Implements a global exclusive lock using the Navigator LockManager API. It\nis available on all browsers released after 2022-03-15 with Safari being the\nlast one to release support. If the API is not available, this function will\nthrow. Make sure you check availablility before configuring GoTrueClient.\n\nYou can turn on debugging by setting the `supabase.gotrue-js.locks.debug`\nlocal storage item to `true`.\n\nInternals:\n\nSince the LockManager API does not preserve stack traces for the async\nfunction passed in the `request` method, a trick is used where acquiring the\nlock releases a previously started promise to run the operation in the `fn`\nfunction. The lock waits for that promise to finish (with or without error),\nwhile the function will finally wait for the result anyway."
}
},
+ "@supabase/supabase-js.postgresChangesFilter": {
+ "name": "@supabase/supabase-js.postgresChangesFilter",
+ "params": [],
+ "ret": {
+ "type": {
+ "type": "nameOnly",
+ "name": "RealtimePostgresFilterBuilder"
+ }
+ },
+ "comment": {
+ "shortText": "Create a RealtimePostgresFilterBuilder for composing a Postgres\nChanges `filter`. Conditions are combined with `AND`.",
+ "examples": [
+ {
+ "id": "example-1",
+ "name": "Example 1",
+ "code": "```ts\nimport { postgresChangesFilter } from '@supabase/realtime-js'\n\nchannel.on('postgres_changes', {\n event: 'UPDATE',\n schema: 'public',\n table: 'orders',\n filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'),\n}, (payload) => { ... })\n```"
+ }
+ ]
+ }
+ },
"@supabase/supabase-js.processLock": {
"name": "@supabase/supabase-js.processLock",
"params": [
diff --git a/apps/docs/scripts/build-reference-content.test.ts b/apps/docs/scripts/build-reference-content.test.ts
index deaa4afdd74..b9ab38ac3a0 100644
--- a/apps/docs/scripts/build-reference-content.test.ts
+++ b/apps/docs/scripts/build-reference-content.test.ts
@@ -1,6 +1,26 @@
-import { describe, expect, it } from 'vitest'
+import { beforeAll, describe, expect, it } from 'vitest'
import { collectReferenceContent } from './build-reference-content'
+import { generateDartReferenceDump } from './generate-dart-reference'
+
+function serialize(content: {
+ bySlug: unknown
+ flat: unknown
+ sections: unknown
+ functionsList: unknown
+ typeSpec: unknown
+}): string {
+ const seen = new WeakSet()
+ const breakCycles = (_key: string, value: unknown) => {
+ if (value && typeof value === 'object') {
+ if (seen.has(value as object)) return '[Circular]'
+ seen.add(value as object)
+ }
+ return value
+ }
+ const { bySlug, flat, sections, functionsList, typeSpec } = content
+ return JSON.stringify({ bySlug, flat, sections, functionsList, typeSpec }, breakCycles, 2)
+}
/**
* Regression guard for the new reference-content pipeline. Snapshots the five
@@ -15,22 +35,42 @@ import { collectReferenceContent } from './build-reference-content'
* doesn't collapse deep / cyclic structures (typeSpec contains
* self-referencing builder types) into `[Object]` placeholders — which would
* make param renames, signature changes, and JSDoc edits invisible.
+ *
+ * ---
+ *
+ * Update on Jun 30th, 2026. Skipping this until we figure out a better way to
+ * avoid downloaded typedoc files to block build on unrelated PRs.
*/
-describe('build-reference-content — javascript/v2', () => {
+describe.skip('build-reference-content: javascript/v2', () => {
it('matches snapshot', async () => {
- const { bySlug, flat, sections, functionsList, typeSpec } = await collectReferenceContent(
- 'javascript',
- 'v2'
+ const content = await collectReferenceContent('javascript', 'v2')
+ await expect(serialize(content)).toMatchFileSnapshot(
+ './__snapshots__/build-reference-content.v2.json'
+ )
+ })
+})
+
+/**
+ * Dart/v2 has no upstream TypeDoc dump; its source is regenerated from the
+ * committed `spec/supabase_dart_v2.yml` by `generate-dart-reference.ts`. We run
+ * that converter first so the snapshot covers the full conversion + build path
+ * (YAML to dump to content) and surfaces any drift in either step.
+ *
+ * ---
+ *
+ * Update on Jun 30th, 2026. Skipping this for the same reason as the
+ * javascript/v2 suite above, until snapshot updates are decoupled from
+ * unrelated builds.
+ */
+describe.skip('build-reference-content: dart/v2', () => {
+ beforeAll(async () => {
+ await generateDartReferenceDump()
+ })
+
+ it('matches snapshot', async () => {
+ const content = await collectReferenceContent('dart', 'v2')
+ await expect(serialize(content)).toMatchFileSnapshot(
+ './__snapshots__/build-reference-content.dart.v2.json'
)
- const seen = new WeakSet()
- const breakCycles = (_key: string, value: unknown) => {
- if (value && typeof value === 'object') {
- if (seen.has(value as object)) return '[Circular]'
- seen.add(value as object)
- }
- return value
- }
- const json = JSON.stringify({ bySlug, flat, sections, functionsList, typeSpec }, breakCycles, 2)
- await expect(json).toMatchFileSnapshot('./__snapshots__/build-reference-content.v2.json')
})
})
diff --git a/apps/docs/scripts/build-reference-content.ts b/apps/docs/scripts/build-reference-content.ts
index 3fe27d4a053..eca936d097a 100644
--- a/apps/docs/scripts/build-reference-content.ts
+++ b/apps/docs/scripts/build-reference-content.ts
@@ -18,6 +18,10 @@ import matter from 'gray-matter'
import {
buildMap,
+ KIND_CLASS,
+ KIND_INTERFACE,
+ KIND_PROPERTY,
+ KIND_TYPE_ALIAS,
KIND_VARIABLE,
normalizeComment,
parseSignature,
@@ -63,6 +67,8 @@ interface Declaration {
parameters?: Param[]
type?: unknown
flags?: { isOptional?: boolean; isConst?: boolean }
+ // Non-TypeDoc field: pre-rendered legacy function content (see FunctionEntry.content).
+ content?: Record
}
interface FunctionEntry {
@@ -70,6 +76,14 @@ interface FunctionEntry {
category: string
subcategory: string | null
$ref: string
+ /**
+ * Optional pre-rendered content (description, notes, params, examples) carried
+ * directly on a declaration. TypeDoc never emits this; it exists so non-TypeDoc
+ * sources adapted "as a pre-step" (e.g. the Dart YAML converter) can ship the
+ * legacy function shape straight onto the functions.json entry without a
+ * typeSpec round-trip. Absent for real TypeDoc dumps, so JS output is unchanged.
+ */
+ content?: Record
}
interface FunctionsEntry {
@@ -355,6 +369,52 @@ function collectFunctions(
isConst: node.flags?.isConst ?? false,
}
out.typeSpec.variables[ref] = variableEntry
+ } else if (node.kind === KIND_TYPE_ALIAS && node.type) {
+ // Type alias — store description and underlying type definition
+ const variableEntry: VariableTypes = {
+ name: ref,
+ type: parseType(node.type, idMap),
+ comment: node.comment ? normalizeComment(node.comment as any) : undefined,
+ isConst: false,
+ }
+ out.typeSpec.variables[ref] = variableEntry
+ } else if (node.kind === KIND_INTERFACE && node.children) {
+ // Interface — store as a method-like entry so the renderer shows properties as a param table
+ const params = (node.children as any[])
+ .filter((child: any) => child.kind === KIND_PROPERTY)
+ .map((prop: any) => ({
+ name: prop.name,
+ comment: prop.comment ? normalizeComment(prop.comment as any) : undefined,
+ isOptional: prop.flags?.isOptional ?? false,
+ type: prop.type ? parseType(prop.type, idMap) : undefined,
+ }))
+ const methodEntry: MethodTypes = {
+ name: ref,
+ params,
+ ret: undefined,
+ comment: node.comment ? normalizeComment(node.comment as any) : undefined,
+ }
+ out.typeSpec.methods[ref] = methodEntry
+ } else if (node.kind === KIND_CLASS && node.children) {
+ // Class — store as a method-like entry so the renderer shows the class
+ // description plus its public properties as a param table. Without this,
+ // classes fall through every branch and only their constructor child is
+ // stored, leaving the class ref (which functions.json points at) empty.
+ const params = (node.children as any[])
+ .filter((child: any) => child.kind === KIND_PROPERTY)
+ .map((prop: any) => ({
+ name: prop.name,
+ comment: prop.comment ? normalizeComment(prop.comment as any) : undefined,
+ isOptional: prop.flags?.isOptional ?? false,
+ type: prop.type ? parseType(prop.type, idMap) : undefined,
+ }))
+ const methodEntry: MethodTypes = {
+ name: ref,
+ params,
+ ret: undefined,
+ comment: node.comment ? normalizeComment(node.comment as any) : undefined,
+ }
+ out.typeSpec.methods[ref] = methodEntry
}
}
@@ -363,7 +423,7 @@ function collectFunctions(
if (category) {
const subcategory = readTagFromDeclOrSignature(node, '@subcategory')
const $ref = normalizeRefPath([ctx.pkg, ...ctx.path, node.name].join('.'))
- out.functions.push({ name: node.name, category, subcategory, $ref })
+ out.functions.push({ name: node.name, category, subcategory, $ref, content: node.content })
}
}
@@ -519,7 +579,9 @@ function buildBySlug(
items.push(entry)
// functions.json `id` must match the bySlug entry's `id` (not the slug) —
// the renderer in Reference.sections.tsx does `fns.find(f => f.id === section.id)`.
- functionsList.push({ id: entry.id, $ref: fn.$ref })
+ // `fn.content`, when present, carries the legacy function shape for non-TypeDoc
+ // sources; spread it so the renderer reads those fields without a typeSpec.
+ functionsList.push({ id: entry.id, $ref: fn.$ref, ...(fn.content ?? {}) })
}
// Sort items alphabetically (case-insensitive) within categories and within
diff --git a/apps/docs/scripts/generate-dart-reference.ts b/apps/docs/scripts/generate-dart-reference.ts
new file mode 100644
index 00000000000..d8119284727
--- /dev/null
+++ b/apps/docs/scripts/generate-dart-reference.ts
@@ -0,0 +1,253 @@
+/**
+ * Generate the Dart/Flutter reference dump consumed by the new reference
+ * pipeline (`scripts/build-reference-content.ts`).
+ *
+ * Dart has no upstream TypeDoc output, so this script is the "pre-step" the
+ * reference README describes: it adapts the hand-authored legacy spec
+ * (`spec/supabase_dart_v2.yml`) plus the shared section tree
+ * (`spec/common-client-libs-sections.json`) into a TypeDoc-shaped JSON dump at
+ * `spec/reference/dart/v2/supabase_flutter.json`.
+ *
+ * Each Dart method becomes a `variant: 'declaration'` node tagged with
+ * `@category` / `@subcategory` (so the build groups it into the right section)
+ * and carries the legacy function shape (description, notes, params, examples)
+ * on a non-TypeDoc `content` field. `build-reference-content.ts` spreads that
+ * straight onto the functions.json entry, so the renderer shows params,
+ * examples, and notes exactly as the legacy YAML did, with no typeSpec
+ * round-trip.
+ *
+ * Overview/header entries (e.g. "Using filters", "Auth MFA") and the top-level
+ * markdown sections (introduction, installing, upgrade-guide, initializing) are
+ * not emitted here. They live as hand-authored partials under
+ * `spec/reference/dart/v2/partials/`, matching how the JavaScript lib is set up.
+ *
+ * The dump is gitignored (like every other reference dump); only `config.json`
+ * and `partials/` are committed. Run via `pnpm codegen:references:new` (the
+ * ensure step regenerates this file when it is missing).
+ *
+ * Usage: pnpm tsx scripts/generate-dart-reference.ts
+ */
+
+import { mkdir, readFile, writeFile } from 'node:fs/promises'
+import { dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { parse } from 'yaml'
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+const DOCS_DIR = join(__dirname, '..')
+const YAML_PATH = join(DOCS_DIR, 'spec/supabase_dart_v2.yml')
+const SECTIONS_PATH = join(DOCS_DIR, 'spec/common-client-libs-sections.json')
+const VERSION_DIR = join(DOCS_DIR, 'spec/reference/dart/v2')
+const CONFIG_PATH = join(VERSION_DIR, 'config.json')
+const OUT_PATH = join(VERSION_DIR, 'supabase_flutter.json')
+
+const LIB_ID = 'reference_dart_v2'
+
+/**
+ * Ids whose YAML entry is a category/subcategory overview rather than a method.
+ * They are rendered through committed JSON partials (keyed by the subcategory
+ * title slug), so they are skipped when building method declarations.
+ */
+const HEADER_IDS = new Set([
+ 'using-filters',
+ 'using-modifiers',
+ 'auth-mfa-api',
+ 'passkey-api',
+ 'admin-api',
+ 'admin-passkey-api',
+ 'file-buckets',
+])
+
+/** Top-level entries handled by markdown / top-level JSON partials, not methods. */
+const SKIP_IDS = new Set(['introduction', 'installing', 'upgrade-guide', 'initializing'])
+
+/**
+ * Dart-specific ids that are absent from the shared (JS-centric) section tree.
+ * Map them to the category/subcategory they belong to.
+ */
+const SECTION_OVERRIDE: Record = {
+ 'max-affected': { category: 'Database', subcategory: 'Using modifiers' },
+ 'link-identity-with-id-token': { category: 'Auth', subcategory: null },
+ 'auth-reset-password-for-email': { category: 'Auth', subcategory: null },
+}
+
+/** Method-name overrides for titles that don't yield a clean identifier. */
+const NAME_OVERRIDE: Record = {
+ explain: 'explain',
+}
+
+interface SectionNode {
+ id?: string
+ title?: string
+ type?: string
+ excludes?: string[]
+ items?: SectionNode[]
+}
+
+interface DartFunction {
+ id: string
+ title: string
+ description?: string
+ notes?: string
+ params?: unknown[]
+ examples?: unknown[]
+}
+
+/** Walk the shared section tree, recording each id's category/subcategory for dart v2. */
+function buildSectionMap(
+ sections: SectionNode[]
+): Map {
+ const map = new Map()
+ const included = (n: SectionNode) => !(n.excludes ?? []).includes(LIB_ID)
+ const walk = (nodes: SectionNode[], category: string | null, subcategory: string | null) => {
+ for (const node of nodes) {
+ if (!included(node)) continue
+ if (node.type === 'category') {
+ walk(node.items ?? [], node.title ?? '', null)
+ } else if (node.items && node.items.length) {
+ walk(node.items, category, node.title ?? null)
+ } else if (node.id) {
+ map.set(node.id, { category: category ?? '', subcategory })
+ }
+ }
+ }
+ walk(sections, null, null)
+ return map
+}
+
+/**
+ * Derive a Dart method identifier from a legacy title:
+ * 'signUp()' -> 'signUp'
+ * 'Fetch data: select()' -> 'select'
+ * 'from.upload()' -> 'upload'
+ * 'mfa.enroll()' -> 'enroll'
+ * 'on().subscribe()' -> 'subscribe'
+ */
+function deriveName(title: string): string {
+ let name = String(title)
+ .trim()
+ .replace(/^['"]|['"]$/g, '')
+ if (name.includes(': ')) name = name.split(': ').pop() as string
+ name = name.replace(/\([^)]*\)/g, '')
+ name = name.split('.').filter(Boolean).pop() ?? name
+ return name.trim()
+}
+
+function textTag(tag: string, text: string) {
+ return { tag, content: [{ kind: 'text', text }] }
+}
+
+type NavigationPrefixes = Record
+
+/**
+ * Recomputes the slug `build-reference-content.ts` will assign to a method, so
+ * collisions are caught here (and reported by id) rather than silently dropped
+ * by the build's first-wins dedup. Mirrors that script's `functionPrefix` /
+ * slug logic, keyed on the nearest container (subcategory, else category).
+ */
+function functionSlug(
+ name: string,
+ category: string,
+ subcategory: string | null,
+ navigationPrefixes: NavigationPrefixes
+): string {
+ const key = subcategory ?? category
+ const override = navigationPrefixes[key]
+ const prefix = override === false ? null : typeof override === 'string' ? override : slugify(key)
+ const nameLower = name.toLowerCase()
+ return prefix === null ? nameLower : `${prefix}-${nameLower}`
+}
+
+function slugify(value: string): string {
+ return value.toLowerCase().trim().replace(/\s+/g, '-')
+}
+
+export async function generateDartReferenceDump(): Promise<{ methodCount: number }> {
+ const doc = parse(await readFile(YAML_PATH, 'utf-8')) as { functions: DartFunction[] }
+ const sections = JSON.parse(await readFile(SECTIONS_PATH, 'utf-8')) as SectionNode[]
+ const config = JSON.parse(await readFile(CONFIG_PATH, 'utf-8')) as {
+ navigationPrefixes?: NavigationPrefixes
+ }
+ const navigationPrefixes = config.navigationPrefixes ?? {}
+ const sectionMap = buildSectionMap(sections)
+
+ let nextId = 1
+ const children: unknown[] = []
+ const slugOwners = new Map()
+ const skipped: string[] = []
+
+ for (const fn of doc.functions) {
+ if (SKIP_IDS.has(fn.id) || HEADER_IDS.has(fn.id)) continue
+
+ const section = SECTION_OVERRIDE[fn.id] ?? sectionMap.get(fn.id)
+ if (!section) {
+ skipped.push(fn.id)
+ continue
+ }
+
+ const name = NAME_OVERRIDE[fn.id] ?? deriveName(fn.title)
+ if (!/^[A-Za-z][A-Za-z0-9]*$/.test(name)) {
+ throw new Error(`Dart converter: id "${fn.id}" yielded an invalid method name "${name}"`)
+ }
+
+ const slug = functionSlug(name, section.category, section.subcategory, navigationPrefixes)
+ const owner = slugOwners.get(slug)
+ if (owner) {
+ throw new Error(`Dart converter: ids "${owner}" and "${fn.id}" both map to slug "${slug}"`)
+ }
+ slugOwners.set(slug, fn.id)
+
+ const blockTags = [textTag('@category', section.category)]
+ if (section.subcategory) blockTags.push(textTag('@subcategory', section.subcategory))
+
+ const content: Record = {}
+ if (fn.description) content.description = fn.description
+ if (fn.notes) content.notes = fn.notes
+ if (fn.params?.length) content.params = fn.params
+ if (fn.examples?.length) content.examples = fn.examples
+
+ children.push({
+ id: nextId++,
+ name,
+ variant: 'declaration',
+ kind: 2048,
+ flags: {},
+ comment: { summary: [], blockTags },
+ content,
+ })
+ }
+
+ const dump = {
+ id: 0,
+ name: 'supabase_flutter',
+ variant: 'project',
+ kind: 1,
+ flags: {},
+ children,
+ }
+
+ await mkdir(dirname(OUT_PATH), { recursive: true })
+ await writeFile(OUT_PATH, JSON.stringify(dump, null, 2))
+
+ if (skipped.length) {
+ throw new Error(
+ `Dart converter: ${skipped.length} ids have no section mapping (add them to ` +
+ `common-client-libs-sections.json or SECTION_OVERRIDE): ${skipped.join(', ')}`
+ )
+ }
+
+ console.log(
+ `[dart/v2] wrote ${children.length} method declarations to ${OUT_PATH.replace(DOCS_DIR + '/', '')}`
+ )
+
+ return { methodCount: children.length }
+}
+
+// Only run when invoked as a script (via `tsx`); importing from a test must not
+// trigger the side-effecting file write.
+if (import.meta.url === `file://${process.argv[1]}`) {
+ generateDartReferenceDump().catch((err) => {
+ console.error(err)
+ process.exit(1)
+ })
+}
diff --git a/apps/docs/scripts/search/sources/index.ts b/apps/docs/scripts/search/sources/index.ts
index e8fb408c0c5..3682cb505b3 100644
--- a/apps/docs/scripts/search/sources/index.ts
+++ b/apps/docs/scripts/search/sources/index.ts
@@ -52,13 +52,15 @@ export async function fetchJsLibReferenceSource() {
}
export async function fetchDartLibReferenceSource() {
- return new ClientLibReferenceLoader(
- 'dart-lib',
- '/reference/dart',
- { title: 'Dart Reference', language: 'Dart' },
- 'spec/supabase_dart_v2.yml',
- 'spec/common-client-libs-sections.json'
- ).load()
+ // Dart v2 is driven by the new reference pipeline. Ingest search sources from
+ // the generated `content/reference/dart/v2/` outputs so embeddings never
+ // drift from what the renderer shows.
+ return loadClientLibReferenceFromNewPipeline({
+ source: 'dart-lib',
+ path: '/reference/dart',
+ meta: { title: 'Dart Reference', language: 'Dart' },
+ contentDir: 'content/reference/dart/v2',
+ })
}
export async function fetchPythonLibReferenceSource() {
diff --git a/apps/docs/spec/Makefile b/apps/docs/spec/Makefile
index c7e125bffa4..205cdd3d6e6 100644
--- a/apps/docs/spec/Makefile
+++ b/apps/docs/spec/Makefile
@@ -1,6 +1,8 @@
REPO_DIR=$(shell pwd)
GENERATOR_DIR=../../../packages/generator
+.PHONY: run download download.api.v1 download.storage.v1 download.tsdoc.v2 download.server.v1 transform dereference.api.v1 dereference.auth.v1 dereference.storage.v0 generate generate.sections.api.v1 format
+
run: download transform generate format
@@ -9,7 +11,7 @@ run: download transform generate format
###############################################################################
# comment out download.auth.v1 temporarily, we're manually creating the file
# download: download.api.v1 download.auth.v1 download.storage.v1 download.tsdoc.v2
-download: download.api.v1 download.storage.v1 download.tsdoc.v2
+download: download.api.v1 download.storage.v1 download.tsdoc.v2 download.server.v1
download.api.v1:
curl -sS https://api.supabase.com/api/v1-json > $(REPO_DIR)/api_v1_openapi.json
@@ -44,6 +46,9 @@ download.tsdoc.v2:
curl -sS https://supabase.github.io/supabase-js/storage-js/v2/spec.json > $(REPO_DIR)/reference/javascript/v2/storage.json
curl -sS https://supabase.github.io/supabase-js/functions-js/v2/spec.json > $(REPO_DIR)/reference/javascript/v2/functions.json
+download.server.v1:
+ curl -sSf https://supabase.github.io/server/spec.json > $(REPO_DIR)/reference/server/v1/server.json
+
download.analytics.v0:
curl -sS https://logflare.app/api/openapi > $(REPO_DIR)/analytics_v0_openapi.json
diff --git a/apps/docs/spec/api_v1_openapi.json b/apps/docs/spec/api_v1_openapi.json
index 1af817aed15..d77b7729a32 100644
--- a/apps/docs/spec/api_v1_openapi.json
+++ b/apps/docs/spec/api_v1_openapi.json
@@ -4442,7 +4442,67 @@
},
"/v1/projects/{ref}/analytics/endpoints/logs.all": {
"get": {
- "description": "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer:~:text=logs%20from%20the-,Sources,-drop%2Ddown%3A) for all available sources. \n",
+ "deprecated": true,
+ "description": "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer:~:text=logs%20from%20the-,Sources,-drop%2Ddown%3A) for all available sources.\n",
+ "operationId": "v1-get-project-logs-all",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ },
+ {
+ "name": "sql",
+ "required": false,
+ "in": "query",
+ "description": "Custom SQL query to execute on the logs. See [querying logs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer) for more details.",
+ "schema": { "type": "string" }
+ },
+ {
+ "name": "iso_timestamp_start",
+ "required": false,
+ "in": "query",
+ "schema": { "format": "date-time", "example": "2025-03-01T00:00:00Z", "type": "string" }
+ },
+ {
+ "name": "iso_timestamp_end",
+ "required": false,
+ "in": "query",
+ "schema": { "format": "date-time", "example": "2025-03-01T23:59:59Z", "type": "string" }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": { "schema": { "$ref": "#/components/schemas/AnalyticsResponse" } }
+ }
+ },
+ "401": { "description": "Unauthorized" },
+ "402": { "description": "Usage exceeded. Enable additional usage to continue querying" },
+ "403": { "description": "Forbidden action" },
+ "429": { "description": "Rate limit exceeded" }
+ },
+ "security": [{ "bearer": [] }, { "fga_permissions": ["analytics_logs_read"] }],
+ "summary": "Gets project's logs",
+ "tags": ["Analytics"],
+ "x-badges": [{ "name": "OAuth scope: analytics:read", "position": "after" }],
+ "x-endpoint-owners": ["analytics"],
+ "x-oauth-scope": "analytics:read"
+ }
+ },
+ "/v1/projects/{ref}/analytics/endpoints/logs": {
+ "get": {
+ "deprecated": false,
+ "description": "Executes an SQL or LQL query on the project's unified logs stream.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nFilter by the `source` column to specify specific log sources, such as edge_logs, postgres_logs, etc.\n\nNote: SQL must be written in **ClickHouse SQL dialect**.\n",
"operationId": "v1-get-project-logs",
"parameters": [
{
@@ -4486,11 +4546,12 @@
}
},
"401": { "description": "Unauthorized" },
+ "402": { "description": "Usage exceeded. Enable additional usage to continue querying" },
"403": { "description": "Forbidden action" },
"429": { "description": "Rate limit exceeded" }
},
"security": [{ "bearer": [] }, { "fga_permissions": ["analytics_logs_read"] }],
- "summary": "Gets project's logs",
+ "summary": "Gets all project's logs in a single log stream",
"tags": ["Analytics"],
"x-badges": [{ "name": "OAuth scope: analytics:read", "position": "after" }],
"x-endpoint-owners": ["analytics"],
@@ -5310,7 +5371,7 @@
"401": { "description": "Unauthorized" },
"403": { "description": "Forbidden action" },
"429": { "description": "Rate limit exceeded" },
- "500": { "description": "Failed to upsert database migration" }
+ "500": { "description": "Failed to update JIT access" }
},
"security": [{ "bearer": [] }, { "fga_permissions": ["database_jit_write"] }],
"summary": "Updates a user mapping for JIT access",
@@ -5357,6 +5418,137 @@
"x-endpoint-owners": ["security"]
}
},
+ "/v1/projects/{ref}/database/jit/invite": {
+ "post": {
+ "description": "Invites the external user and sets initial roles that can be assumed and for how long",
+ "operationId": "v1-invite-external-jit-access",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/InviteExternalUserJitAccessBody" }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/InviteExternalUserJitResponse" }
+ }
+ }
+ },
+ "401": { "description": "Unauthorized" },
+ "403": { "description": "Forbidden action" },
+ "429": { "description": "Rate limit exceeded" },
+ "500": { "description": "Failed to invite external user" }
+ },
+ "security": [{ "bearer": [] }, { "fga_permissions": ["database_jit_write"] }],
+ "summary": "Invites an external user to a database for JIT access",
+ "tags": ["Database"],
+ "x-endpoint-owners": ["security"]
+ }
+ },
+ "/v1/projects/{ref}/database/jit/invite/accept": {
+ "post": {
+ "description": "Accepts the invitation to JIT database access",
+ "operationId": "v1-accept-invite-external-jit-access",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/AcceptInviteExternalUserJitAccessBody" }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": { "schema": { "$ref": "#/components/schemas/JitAccessResponse" } }
+ }
+ },
+ "500": { "description": "Failed to accept invitation" }
+ },
+ "security": [{ "bearer": [] }],
+ "summary": "Accepts invitation for JIT database access",
+ "tags": ["Database"],
+ "x-endpoint-owners": ["security"]
+ }
+ },
+ "/v1/projects/{ref}/database/jit/invite/{invite_id}": {
+ "delete": {
+ "description": "Revokes and deletes the invitation",
+ "operationId": "v1-delete-invite-external-jit-access",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ },
+ {
+ "name": "invite_id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "format": "uuid",
+ "example": "55555555-5555-4555-8555-555555555555",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": { "description": "" },
+ "401": { "description": "Unauthorized" },
+ "403": { "description": "Forbidden action" },
+ "429": { "description": "Rate limit exceeded" },
+ "500": { "description": "Failed to revoke invite for external user" }
+ },
+ "security": [{ "bearer": [] }, { "fga_permissions": ["database_jit_write"] }],
+ "summary": "Deletes the invite for an external user to a database for JIT access",
+ "tags": ["Database"],
+ "x-endpoint-owners": ["security"]
+ }
+ },
"/v1/projects/{ref}/database/jit/{user_id}": {
"delete": {
"description": "Remove JIT mappings of a user, revoking all JIT database access",
@@ -8553,11 +8745,7 @@
"state": { "type": "string", "enum": ["unavailable"] },
"unavailableReason": {
"type": "string",
- "enum": [
- "manual_migration_required",
- "postgres_upgrade_required",
- "temporarily_unavailable"
- ]
+ "enum": ["postgres_upgrade_required", "temporarily_unavailable"]
}
},
"required": ["state", "unavailableReason"]
@@ -8740,9 +8928,20 @@
"nullable": true,
"description": "If `null`, the value is automatically configured based on compute size."
},
+ "db_pool_acquisition_timeout": {
+ "type": "integer",
+ "nullable": true,
+ "description": "If `null`, the value is automatically configured to 10."
+ },
"jwt_secret": { "type": "string" }
},
- "required": ["db_schema", "max_rows", "db_extra_search_path", "db_pool"]
+ "required": [
+ "db_schema",
+ "max_rows",
+ "db_extra_search_path",
+ "db_pool",
+ "db_pool_acquisition_timeout"
+ ]
},
"V1UpdatePostgrestConfigBody": {
"type": "object",
@@ -8750,7 +8949,8 @@
"db_extra_search_path": { "type": "string" },
"db_schema": { "type": "string" },
"max_rows": { "type": "integer", "minimum": 0, "maximum": 1000000 },
- "db_pool": { "type": "integer", "minimum": 0, "maximum": 1000 }
+ "db_pool": { "type": "integer", "minimum": 0, "maximum": 1000 },
+ "db_pool_acquisition_timeout": { "type": "integer", "minimum": 0, "maximum": 60 }
},
"example": { "db_schema": "public,storage", "db_pool": 20, "max_rows": 1000 }
},
@@ -8764,9 +8964,20 @@
"type": "integer",
"nullable": true,
"description": "If `null`, the value is automatically configured based on compute size."
+ },
+ "db_pool_acquisition_timeout": {
+ "type": "integer",
+ "nullable": true,
+ "description": "If `null`, the value is automatically configured to 10."
}
},
- "required": ["db_schema", "max_rows", "db_extra_search_path", "db_pool"]
+ "required": [
+ "db_schema",
+ "max_rows",
+ "db_extra_search_path",
+ "db_pool",
+ "db_pool_acquisition_timeout"
+ ]
},
"V1ProjectRefResponse": {
"type": "object",
@@ -10976,7 +11187,7 @@
}
}
},
- "required": ["user_id", "user_roles"]
+ "required": ["user_roles"]
},
"AuthorizeJitAccessBody": {
"type": "object",
@@ -11030,44 +11241,94 @@
"items": {
"type": "array",
"items": {
- "type": "object",
- "properties": {
- "user_id": { "type": "string", "format": "uuid" },
- "user_roles": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "role": { "type": "string", "minLength": 1 },
- "expires_at": { "type": "number" },
- "allowed_networks": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "user_id": { "type": "string", "format": "uuid" },
+ "primary_email": { "type": "string", "nullable": true },
+ "invite_id": { "type": "null" },
+ "expires_at": { "type": "null" },
+ "user_roles": {
+ "type": "array",
+ "items": {
"type": "object",
"properties": {
- "allowed_cidrs": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": { "cidr": { "type": "string" } },
- "required": ["cidr"]
+ "role": { "type": "string", "minLength": 1 },
+ "expires_at": { "type": "number" },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "cidr": { "type": "string" } },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "cidr": { "type": "string" } },
+ "required": ["cidr"]
+ }
+ }
}
},
- "allowed_cidrs_v6": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": { "cidr": { "type": "string" } },
- "required": ["cidr"]
+ "branches_only": { "type": "boolean" }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["user_id", "primary_email", "invite_id", "expires_at", "user_roles"]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "user_id": { "type": "null" },
+ "primary_email": { "type": "string" },
+ "invite_id": { "type": "string", "format": "uuid" },
+ "expires_at": { "type": "string" },
+ "user_roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": { "type": "string", "minLength": 1 },
+ "expires_at": { "type": "number" },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "cidr": { "type": "string" } },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "cidr": { "type": "string" } },
+ "required": ["cidr"]
+ }
+ }
}
- }
- }
- },
- "branches_only": { "type": "boolean" }
- },
- "required": ["role"]
- }
+ },
+ "branches_only": { "type": "boolean" }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["user_id", "primary_email", "invite_id", "expires_at", "user_roles"]
}
- },
- "required": ["user_id", "user_roles"]
+ ]
}
}
},
@@ -11124,6 +11385,107 @@
]
}
},
+ "InviteExternalUserJitAccessBody": {
+ "type": "object",
+ "properties": {
+ "email": { "type": "string", "format": "email", "minLength": 1 },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": { "type": "string", "minLength": 1 },
+ "expires_at": { "type": "number" },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "cidr": { "type": "string" } },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "cidr": { "type": "string" } },
+ "required": ["cidr"]
+ }
+ }
+ }
+ },
+ "branches_only": { "type": "boolean" }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["email", "roles"],
+ "example": {
+ "email": "external-user@somedomain.xyz",
+ "roles": [
+ {
+ "role": "postgres",
+ "expires_at": 1740787200,
+ "allowed_networks": { "allowed_cidrs": [{ "cidr": "203.0.113.0/24" }] },
+ "branches_only": false
+ }
+ ]
+ }
+ },
+ "InviteExternalUserJitResponse": {
+ "type": "object",
+ "properties": {
+ "email": { "type": "string", "format": "email" },
+ "invite_id": { "type": "string", "format": "uuid" },
+ "user_roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": { "type": "string", "minLength": 1 },
+ "expires_at": { "type": "number" },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "cidr": { "type": "string" } },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "cidr": { "type": "string" } },
+ "required": ["cidr"]
+ }
+ }
+ }
+ },
+ "branches_only": { "type": "boolean" }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["email", "invite_id", "user_roles"]
+ },
+ "AcceptInviteExternalUserJitAccessBody": {
+ "type": "object",
+ "properties": {
+ "email": { "type": "string", "format": "email", "minLength": 1 },
+ "token": { "type": "string", "minLength": 1 }
+ },
+ "required": ["email", "token"],
+ "example": { "email": "external-user@somedomain.xyz", "token": "" }
+ },
"FunctionResponse": {
"type": "object",
"properties": {
@@ -11423,6 +11785,11 @@
"properties": { "enabled": { "type": "boolean" } },
"required": ["enabled"]
},
+ "purgeCache": {
+ "type": "object",
+ "properties": { "enabled": { "type": "boolean" } },
+ "required": ["enabled"]
+ },
"icebergCatalog": {
"type": "object",
"properties": {
@@ -11443,7 +11810,13 @@
"required": ["enabled", "maxBuckets", "maxIndexes"]
}
},
- "required": ["imageTransformation", "s3Protocol", "icebergCatalog", "vectorBuckets"]
+ "required": [
+ "imageTransformation",
+ "s3Protocol",
+ "purgeCache",
+ "icebergCatalog",
+ "vectorBuckets"
+ ]
},
"capabilities": {
"type": "object",
@@ -11492,6 +11865,11 @@
"properties": { "enabled": { "type": "boolean" } },
"required": ["enabled"]
},
+ "purgeCache": {
+ "type": "object",
+ "properties": { "enabled": { "type": "boolean" } },
+ "required": ["enabled"]
+ },
"icebergCatalog": {
"type": "object",
"properties": {
@@ -12444,6 +12822,7 @@
"storage.image_transformations",
"storage.vector_buckets",
"storage.iceberg_catalog",
+ "storage.purge_cache",
"security.audit_logs_days",
"security.questionnaire",
"security.soc2_report",
@@ -12492,7 +12871,9 @@
"assistant.advance_model",
"integrations.github_connections",
"dedicated_pooler",
- "observability.dashboard_advanced_metrics"
+ "observability.dashboard_advanced_metrics",
+ "api.members.invitations",
+ "api.members.roles"
]
},
"type": { "type": "string", "enum": ["boolean", "numeric", "set"] }
@@ -12542,9 +12923,10 @@
"user_name": { "type": "string" },
"email": { "type": "string" },
"role_name": { "type": "string" },
- "mfa_enabled": { "type": "boolean" }
+ "mfa_enabled": { "type": "boolean" },
+ "avatar_url": { "type": "string", "nullable": true }
},
- "required": ["user_id", "user_name", "role_name", "mfa_enabled"]
+ "required": ["user_id", "user_name", "role_name", "mfa_enabled", "avatar_url"]
},
"V1OrganizationSlugResponse": {
"type": "object",
diff --git a/apps/docs/spec/api_v2_openapi.json b/apps/docs/spec/api_v2_openapi.json
index a576b8f8cf3..3f82b25778f 100644
--- a/apps/docs/spec/api_v2_openapi.json
+++ b/apps/docs/spec/api_v2_openapi.json
@@ -266,6 +266,133 @@
"x-endpoint-owners": ["management-api"]
}
},
+ "/v2/projects/{ref}/private-link/associations": {
+ "get": {
+ "operationId": "v2-list-private-link-associations",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/V2ListPrivateLinkAssociationsResponse" }
+ }
+ }
+ },
+ "401": { "description": "Unauthorized" },
+ "403": { "description": "Forbidden action" },
+ "429": { "description": "Rate limit exceeded" },
+ "500": { "description": "Failed to retrieve AWS accounts for project" }
+ },
+ "security": [{ "bearer": [] }, { "fga_permissions": ["project_admin_read"] }],
+ "summary": "List AWS accounts attached to the project PrivateLink share",
+ "tags": ["Projects"],
+ "x-endpoint-owners": ["platform-networking", "management-api"]
+ },
+ "post": {
+ "description": "Adds an AWS account to the project's PrivateLink configuration and schedules the AWS resources to be created.",
+ "operationId": "v2-create-private-link-association",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/V2CreatePrivateLinkAssociationRequest" }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/V2PrivateLinkAssociationResponse" }
+ }
+ }
+ },
+ "401": { "description": "Unauthorized" },
+ "402": {
+ "description": "This feature requires the Team, or Enterprise organization plan."
+ },
+ "403": { "description": "Forbidden action" },
+ "429": { "description": "Rate limit exceeded" },
+ "500": { "description": "Failed to add AWS account to PrivateLink share" }
+ },
+ "security": [{ "bearer": [] }, { "fga_permissions": ["project_admin_write"] }],
+ "summary": "Add an AWS account to the project PrivateLink share",
+ "tags": ["Projects"],
+ "x-allowed-plans": ["Team", "Enterprise"],
+ "x-badges": [{ "name": "Only available on Team, Enterprise", "position": "before" }],
+ "x-endpoint-owners": ["platform-networking", "management-api"]
+ }
+ },
+ "/v2/projects/{ref}/private-link/associations/aws-account/{aws_account_id}": {
+ "delete": {
+ "description": "Removes an AWS account from the project's PrivateLink configuration. Cleans up the associated AWS resources.",
+ "operationId": "v2-delete-private-link-association",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ },
+ {
+ "name": "aws_account_id",
+ "required": false,
+ "in": "path",
+ "description": "AWS account ID used in PrivateLink association",
+ "schema": { "type": "string" }
+ }
+ ],
+ "responses": {
+ "204": { "description": "" },
+ "401": { "description": "Unauthorized" },
+ "403": { "description": "Forbidden action" },
+ "429": { "description": "Rate limit exceeded" },
+ "500": { "description": "Failed to remove AWS account from PrivateLink share" }
+ },
+ "security": [{ "bearer": [] }, { "fga_permissions": ["project_admin_write"] }],
+ "summary": "Remove an AWS account from the project PrivateLink share",
+ "tags": ["Projects"],
+ "x-endpoint-owners": ["platform-networking", "management-api"]
+ }
+ },
"/v2/organizations/{slug}/members": {
"get": {
"description": "Returns a cursor-paginated list of organization members including their roles and project-scoped permissions.",
@@ -329,10 +456,10 @@
"x-oauth-scope": "organizations:read"
}
},
- "/v2/organizations/{slug}/roles": {
- "get": {
- "description": "Returns a cursor-paginated list of organization roles including both org-scoped and project-scoped roles.",
- "operationId": "v2-list-organization-roles",
+ "/v2/organizations/{slug}/members/{user_id}/roles": {
+ "patch": {
+ "description": "Assigns an org-wide role when projects is omitted, or creates a project-scoped assignment when projects is provided. Uses an org-level role template id from GET /v2/organizations/{slug}/roles. Stale role assignments are automatically cleaned up: if a role no longer has any projects, it is deleted; overlapping project assignments in other roles are automatically removed to avoid duplication.",
+ "operationId": "v2-assign-organization-member-role",
"parameters": [
{
"name": "slug",
@@ -346,31 +473,57 @@
}
},
{
- "name": "page",
- "required": false,
- "in": "query",
- "schema": {
- "properties": {
- "size": { "type": "integer", "minimum": 1, "maximum": 100, "required": false },
- "after": { "type": "string", "required": false },
- "before": { "type": "string", "required": false }
- },
- "type": "object"
+ "name": "user_id",
+ "required": true,
+ "in": "path",
+ "schema": { "format": "uuid", "type": "string" }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/V2AssignOrganizationMemberRoleRequest" }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/OrganizationMemberRoleResponse" }
+ }
}
},
+ "401": { "description": "Unauthorized" },
+ "402": { "description": "This feature requires the Enterprise organization plan." },
+ "403": { "description": "Forbidden action" },
+ "429": { "description": "Rate limit exceeded" },
+ "500": { "description": "Failed to assign organization member role" }
+ },
+ "security": [{ "bearer": [] }, { "fga_permissions": ["organization_admin_write"] }],
+ "summary": "Assign or change an organization member role",
+ "tags": ["Organizations"],
+ "x-allowed-plans": ["Enterprise"],
+ "x-badges": [{ "name": "Only available on Enterprise", "position": "before" }],
+ "x-endpoint-owners": ["management-api"]
+ }
+ },
+ "/v2/organizations/{slug}/roles": {
+ "get": {
+ "description": "Returns a list of org-level roles for the organization.",
+ "operationId": "v2-list-organization-roles",
+ "parameters": [
{
- "name": "filter",
- "required": false,
- "in": "query",
+ "name": "slug",
+ "required": true,
+ "in": "path",
+ "description": "Organization slug",
"schema": {
- "properties": {
- "scope": {
- "type": "string",
- "enum": ["organization", "project"],
- "required": false
- }
- },
- "type": "object"
+ "pattern": "^[\\w-]+$",
+ "example": "tsrqponmlkjihgfedcba",
+ "type": "string"
}
}
],
@@ -394,6 +547,57 @@
"x-endpoint-owners": ["management-api"],
"x-oauth-scope": "organizations:read"
}
+ },
+ "/v2/organizations/{slug}/members/invitations": {
+ "post": {
+ "description": "Creates member invitations for an organization. Each invitation can have different role and project scope settings.",
+ "operationId": "v2-create-organization-invitations",
+ "parameters": [
+ {
+ "name": "slug",
+ "required": true,
+ "in": "path",
+ "description": "Organization slug",
+ "schema": {
+ "pattern": "^[\\w-]+$",
+ "example": "tsrqponmlkjihgfedcba",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/V2CreateInvitationsRequest" }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/V2CreateInvitationsResponse" }
+ }
+ }
+ },
+ "401": { "description": "Unauthorized" },
+ "402": { "description": "This feature requires the Enterprise organization plan." },
+ "403": { "description": "Forbidden action" },
+ "429": { "description": "Rate limit exceeded" }
+ },
+ "security": [{ "bearer": [] }, { "fga_permissions": ["members_write"] }],
+ "summary": "Creates organization invitations",
+ "tags": ["Organizations Members Invitations"],
+ "x-allowed-plans": ["Enterprise"],
+ "x-badges": [
+ { "name": "OAuth scope: organizations:write", "position": "after" },
+ { "name": "Only available on Enterprise", "position": "before" }
+ ],
+ "x-endpoint-owners": ["management-api"],
+ "x-oauth-scope": "organizations:write"
+ }
}
},
"info": {
@@ -1023,6 +1227,150 @@
},
"required": ["data"]
},
+ "V2ListPrivateLinkAssociationsResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "id": { "type": "string" },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID this PrivateLink share is associated with."
+ },
+ "account_name": {
+ "type": "string",
+ "description": "Human-readable name for the AWS account."
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "CREATING",
+ "READY",
+ "ASSOCIATION_REQUEST_EXPIRED",
+ "ASSOCIATION_ACCEPTED",
+ "CREATION_FAILED",
+ "DELETING"
+ ],
+ "description": "\n - `CREATING`: The PrivateLink resources and the Association are in the process of being created. The PrivateLink Share cannot be accepted yet.\n - `READY`: The PrivateLink resources have been created and the PrivateLink Share can be accepted for the duration of 12h after sharing. See `shared_at`.\n - `ASSOCIATION_REQUEST_EXPIRED`: The PrivateLink Share has not been accepted within the 12h time limit. This association can now be deleted.\n - `ASSOCIATION_ACCEPTED`: The PrivateLink Share was successfully accepted.\n - `CREATION_FAILED`: The PrivateLink resources failed to create. This likely means something went wrong on Supabase's and and support should be contacted.\n - `DELETING`: The PrivateLink resources and the Association are in the process of being deleted. The PrivateLink Share cannot be accepted yet.\n"
+ },
+ "shared_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "The time and date at which the AWS Resource Share Association was requested from Supabase. `null` means that the association was not yet requested while the PrivateLink Association is pending."
+ }
+ },
+ "required": ["aws_account_id", "status", "shared_at"]
+ }
+ },
+ "required": ["type", "id", "attributes"]
+ }
+ }
+ },
+ "required": ["data"]
+ },
+ "V2CreatePrivateLinkAssociationRequest": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID to add to the project PrivateLink share."
+ },
+ "account_name": {
+ "type": "string",
+ "maxLength": 128,
+ "description": "Optional human-readable name for the AWS account."
+ }
+ },
+ "required": ["aws_account_id"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ },
+ "required": ["data"]
+ },
+ "V2PrivateLinkAssociationResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "id": { "type": "string" },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID this PrivateLink share is associated with."
+ },
+ "account_name": {
+ "type": "string",
+ "description": "Human-readable name for the AWS account."
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "CREATING",
+ "READY",
+ "ASSOCIATION_REQUEST_EXPIRED",
+ "ASSOCIATION_ACCEPTED",
+ "CREATION_FAILED",
+ "DELETING"
+ ],
+ "description": "\n - `CREATING`: The PrivateLink resources and the Association are in the process of being created. The PrivateLink Share cannot be accepted yet.\n - `READY`: The PrivateLink resources have been created and the PrivateLink Share can be accepted for the duration of 12h after sharing. See `shared_at`.\n - `ASSOCIATION_REQUEST_EXPIRED`: The PrivateLink Share has not been accepted within the 12h time limit. This association can now be deleted.\n - `ASSOCIATION_ACCEPTED`: The PrivateLink Share was successfully accepted.\n - `CREATION_FAILED`: The PrivateLink resources failed to create. This likely means something went wrong on Supabase's and and support should be contacted.\n - `DELETING`: The PrivateLink resources and the Association are in the process of being deleted. The PrivateLink Share cannot be accepted yet.\n"
+ },
+ "shared_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "The time and date at which the AWS Resource Share Association was requested from Supabase. `null` means that the association was not yet requested while the PrivateLink Association is pending."
+ }
+ },
+ "required": ["aws_account_id", "status", "shared_at"]
+ }
+ },
+ "required": ["type", "id", "attributes"]
+ }
+ },
+ "required": ["data"]
+ },
"V2ListMembersResponse": {
"type": "object",
"properties": {
@@ -1058,16 +1406,20 @@
"type": "boolean",
"description": "Whether this member is a Single Sign-On user"
},
+ "avatar_url": {
+ "type": "string",
+ "nullable": true,
+ "description": "Member's avatar URL"
+ },
"roles": {
"type": "array",
"items": {
"type": "object",
"properties": {
- "id": { "type": "integer", "description": "Role ID." },
"name": {
"type": "string",
"description": "Role name. For project-scoped roles this is the base role name.",
- "example": "Developer"
+ "example": "developer"
},
"scope": {
"type": "string",
@@ -1087,12 +1439,19 @@
"description": "Project refs this role is scoped to. Empty array for org-level roles."
}
},
- "required": ["id", "name", "scope", "projects"]
+ "required": ["name", "scope", "projects"]
},
"description": "Roles assigned to this member. Includes both org-level and project-scoped roles."
}
},
- "required": ["username", "primary_email", "mfa_enabled", "is_sso_user", "roles"]
+ "required": [
+ "username",
+ "primary_email",
+ "mfa_enabled",
+ "is_sso_user",
+ "avatar_url",
+ "roles"
+ ]
}
},
"required": ["type", "id", "attributes"]
@@ -1131,6 +1490,93 @@
},
"required": ["data", "links"]
},
+ "V2AssignOrganizationMemberRoleRequest": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_member_role"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "enum": ["owner", "administrator", "developer", "read-only"],
+ "description": "Role name to assign. Must be one of: owner, administrator, developer, read-only. Must be on a Team or Enterprise plan to use the read-only role.",
+ "example": "developer"
+ },
+ "projects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "ref": {
+ "type": "string",
+ "description": "Project ref",
+ "example": "abcjuqabhgwjjutfvtpa"
+ }
+ },
+ "required": ["ref"]
+ },
+ "minItems": 1,
+ "description": "The projects to assign a project-scoped role for. If omitted, assigns an org-wide role."
+ }
+ },
+ "required": ["role"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ },
+ "required": ["data"]
+ },
+ "OrganizationMemberRoleResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_member_role"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Role name. For project-scoped assignments this is the base role name.",
+ "example": "developer"
+ },
+ "scope": {
+ "type": "string",
+ "enum": ["organization", "project"],
+ "description": "Whether this role applies org-wide or is scoped to specific projects for the user."
+ },
+ "projects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": { "ref": { "type": "string" }, "name": { "type": "string" } },
+ "required": ["ref", "name"]
+ },
+ "description": "Project refs this role is scoped to. Empty array for org-level roles."
+ }
+ },
+ "required": ["name", "scope", "projects"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ },
+ "required": ["data"]
+ },
"V2ListRolesResponse": {
"type": "object",
"properties": {
@@ -1144,73 +1590,161 @@
"enum": ["organization_role"],
"description": "Resource type."
},
- "id": { "type": "string" },
+ "id": {},
"attributes": {
"type": "object",
"properties": {
"name": {
"type": "string",
- "description": "Role name. For project-scoped roles this is the base role name.",
- "example": "Developer"
- },
- "scope": {
+ "description": "Role name.",
+ "example": "developer"
+ }
+ },
+ "required": ["name"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ }
+ },
+ "required": ["data"]
+ },
+ "V2CreateInvitationsRequest": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_invitation"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "email": { "type": "string", "format": "email" },
+ "role": {
"type": "string",
- "enum": ["organization", "project"],
- "description": "Whether this role applies org-wide or is scoped to specific projects for the user."
- },
- "base_role_id": {
- "type": "integer",
- "nullable": true,
- "description": "null for org-level roles; the base role ID for project-scoped roles."
+ "enum": ["owner", "administrator", "developer", "read-only"],
+ "description": "Role name to assign. Must be on a Team or Enterprise plan to use the read-only role.",
+ "example": "developer"
},
"projects": {
"type": "array",
"items": {
"type": "object",
- "properties": { "ref": { "type": "string" }, "name": { "type": "string" } },
- "required": ["ref", "name"]
+ "properties": {
+ "ref": {
+ "type": "string",
+ "description": "Project ref",
+ "example": "abcjuqabhgwjjutfvtpa"
+ }
+ },
+ "required": ["ref"]
},
- "description": "Project refs this role is scoped to. Empty array for org-level roles."
- }
+ "minItems": 1,
+ "description": "The projects to limit a user to. If omitted, user will have org-wide access with the provided role."
+ },
+ "require_sso": { "type": "boolean" }
},
- "required": ["name", "scope", "base_role_id", "projects"]
+ "required": ["email", "role"]
}
},
- "required": ["type", "id", "attributes"]
- }
- },
- "links": {
- "type": "object",
- "properties": {
- "first": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the first page if available.",
- "example": "/v2/organizations/my-org/roles?page[size]=10"
- },
- "prev": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the previous page.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[before]=019adf7d-4513-74c5-bb9a-f1bc0f7a95d7"
- },
- "next": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the next page.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[after]=019adf7d-4513-7062-b292-78b86cc470a4"
- },
- "last": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the last page if available.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[after]=019adf7d-4513-71ba-b264-21900edb4295"
- }
+ "required": ["type", "attributes"]
},
- "required": ["prev", "next"]
+ "minItems": 1,
+ "maxItems": 50
}
},
- "required": ["data", "links"]
+ "required": ["data"]
+ },
+ "V2CreateInvitationsResponse": {
+ "type": "object",
+ "properties": {
+ "error": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "string" },
+ "code": { "type": "string" },
+ "message": { "type": "string" },
+ "description": { "type": "string" },
+ "links": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "href": { "type": "string" },
+ "rel": { "type": "string" },
+ "title": { "type": "string" },
+ "type": { "type": "string" },
+ "describedby": { "type": "string" },
+ "meta": { "type": "object", "additionalProperties": {} }
+ },
+ "required": ["href"]
+ }
+ },
+ "meta": { "type": "object", "additionalProperties": {} },
+ "issues": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "string" },
+ "code": { "type": "string" },
+ "message": { "type": "string" },
+ "description": { "type": "string" },
+ "links": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "href": { "type": "string" },
+ "rel": { "type": "string" },
+ "title": { "type": "string" },
+ "type": { "type": "string" },
+ "describedby": { "type": "string" },
+ "meta": { "type": "object", "additionalProperties": {} }
+ },
+ "required": ["href"]
+ }
+ },
+ "meta": {
+ "type": "object",
+ "properties": { "email": { "type": "string", "format": "email" } },
+ "required": ["email"]
+ }
+ },
+ "required": ["code", "message", "meta"]
+ }
+ }
+ },
+ "required": ["code", "message"]
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_invitation"],
+ "description": "Resource type."
+ },
+ "id": {},
+ "attributes": {
+ "type": "object",
+ "properties": { "email": { "type": "string", "format": "email" } },
+ "required": ["email"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ }
+ },
+ "required": ["data"]
}
}
}
diff --git a/apps/docs/spec/common-api-sections.json b/apps/docs/spec/common-api-sections.json
index 254629246b8..ffe92d96efb 100644
--- a/apps/docs/spec/common-api-sections.json
+++ b/apps/docs/spec/common-api-sections.json
@@ -52,6 +52,12 @@
"slug": "v1-get-project-logs",
"type": "operation"
},
+ {
+ "id": "v1-get-project-logs-all",
+ "title": "Get project logs all",
+ "slug": "v1-get-project-logs-all",
+ "type": "operation"
+ },
{
"id": "v1-get-project-usage-api-count",
"title": "Get project usage api count",
@@ -220,6 +226,12 @@
"type": "category",
"title": "Database",
"items": [
+ {
+ "id": "v1-accept-invite-external-jit-access",
+ "title": "Accept invite external jit access",
+ "slug": "v1-accept-invite-external-jit-access",
+ "type": "operation"
+ },
{
"id": "v1-apply-a-migration",
"title": "Apply a migration",
@@ -238,6 +250,12 @@
"slug": "v1-create-login-role",
"type": "operation"
},
+ {
+ "id": "v1-delete-invite-external-jit-access",
+ "title": "Delete invite external jit access",
+ "slug": "v1-delete-invite-external-jit-access",
+ "type": "operation"
+ },
{
"id": "v1-delete-jit-access",
"title": "Delete jit access",
@@ -340,6 +358,12 @@
"slug": "v1-get-ssl-enforcement-config",
"type": "operation"
},
+ {
+ "id": "v1-invite-external-jit-access",
+ "title": "Invite external jit access",
+ "slug": "v1-invite-external-jit-access",
+ "type": "operation"
+ },
{
"id": "v1-list-all-backups",
"title": "List all backups",
@@ -706,6 +730,12 @@
"type": "category",
"title": "Organizations",
"items": [
+ {
+ "id": "v2-assign-organization-member-role",
+ "title": "Assign organization member role",
+ "slug": "v2-assign-organization-member-role",
+ "type": "operation"
+ },
{
"id": "v1-create-an-organization",
"title": "Create an organization",
@@ -750,6 +780,18 @@
}
]
},
+ {
+ "type": "category",
+ "title": "Organizations Members Invitations",
+ "items": [
+ {
+ "id": "v2-create-organization-invitations",
+ "title": "Create organization invitations",
+ "slug": "v2-create-organization-invitations",
+ "type": "operation"
+ }
+ ]
+ },
{
"type": "category",
"title": "Profile",
@@ -778,6 +820,12 @@
"slug": "v1-create-a-project",
"type": "operation"
},
+ {
+ "id": "v2-create-private-link-association",
+ "title": "Create private link association",
+ "slug": "v2-create-private-link-association",
+ "type": "operation"
+ },
{
"id": "v1-delete-a-project",
"title": "Delete a project",
@@ -790,6 +838,12 @@
"slug": "v1-delete-network-bans",
"type": "operation"
},
+ {
+ "id": "v2-delete-private-link-association",
+ "title": "Delete private link association",
+ "slug": "v2-delete-private-link-association",
+ "type": "operation"
+ },
{
"id": "v1-get-all-projects-for-organization",
"title": "Get all projects for organization",
@@ -874,6 +928,12 @@
"slug": "v1-list-available-restore-versions",
"type": "operation"
},
+ {
+ "id": "v2-list-private-link-associations",
+ "title": "List private link associations",
+ "slug": "v2-list-private-link-associations",
+ "type": "operation"
+ },
{
"id": "v1-modify-database-disk",
"title": "Modify database disk",
diff --git a/apps/docs/spec/reference/README.md b/apps/docs/spec/reference/README.md
index 8c64e956a63..9602a13174e 100644
--- a/apps/docs/spec/reference/README.md
+++ b/apps/docs/spec/reference/README.md
@@ -135,6 +135,30 @@ Key tags the walker reads:
Anything without `@category` is collected into `typeSpec.json` (for cross-reference
by `$ref`) but does **not** appear in the navigation/section list.
+### Non-TypeDoc sources
+
+#### Dart/Flutter
+
+Not every SDK ships TypeDoc. Dart is adapted "as a pre-step": the committed
+legacy spec [`spec/supabase_dart_v2.yml`](../supabase_dart_v2.yml) plus the
+shared section tree (`spec/common-client-libs-sections.json`) are converted into
+a TypeDoc-shaped dump by
+[`scripts/generate-dart-reference.ts`](../../scripts/generate-dart-reference.ts).
+Each Dart method becomes a `variant: 'declaration'` node tagged with
+`@category` / `@subcategory` (so it groups like any TypeDoc declaration) and
+carries the legacy function shape (description, notes, params, examples) on a
+non-TypeDoc `content` field. `build-reference-content.ts` spreads that `content`
+straight onto the functions.json entry, so the renderer shows params, examples,
+and notes exactly as the YAML did, with no typeSpec round-trip. The `content`
+field is absent for real TypeDoc dumps, so JavaScript output is unchanged.
+
+The generated dump (`spec/reference/dart/v2/supabase_flutter.json`) is gitignored
+like every other dump; `pnpm codegen:references:new` regenerates it from the
+committed YAML before the content build (and a `make generate.dart.v2` target
+mirrors that for manual runs). Overview/header blocks (e.g. "Using filters",
+"Auth MFA") and the top-level markdown sections live as hand-authored partials
+under `spec/reference/dart/v2/partials/`, exactly like the JavaScript lib.
+
`$ref` values are constructed as `...`,
following TypeDoc's module (kind 2), namespace (kind 4), class (kind 128) and
interface (kind 256) nesting. The `index` module segment is stripped via
@@ -361,10 +385,7 @@ to the constant in
[`features/docs/Reference.constants.ts`](../../features/docs/Reference.constants.ts):
```ts
-export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set([
- 'javascript-v2',
- // 'dart-v2', ← uncomment when ready
-])
+export const SUPPORTS_NEW_REFERENCE_PROCESS = new Set(['javascript-v2', 'dart-v2'])
```
The same set drives every runtime read that depends on the new layout:
@@ -392,8 +413,16 @@ The same set drives every runtime read that depends on the new layout:
directly. Other libs still go through `ClientLibReferenceLoader` against
their YAML. To migrate another lib, swap the same way once it's in
`SUPPORTS_NEW_REFERENCE_PROCESS`.
+- The public/LLMs markdown generator
+ [`internals/generate-reference-markdown.ts`](../../internals/generate-reference-markdown.ts)
+ keys each lib on its own `kind` (`sdk-legacy` reads
+ `features/docs/generated/..*.json`; `sdk-new` reads
+ `content/reference///*.json`). This switch is independent of
+ `SUPPORTS_NEW_REFERENCE_PROCESS`, so a migrated lib must also be flipped to
+ `sdk-new` here, otherwise it reads legacy files the generator above no longer
+ produces and the build fails.
-No other call sites in the render path need to change.
+No other call sites need to change.
> ⚠️ Don't move the constant. `Reference.utils.ts` transitively pulls in
> `next/navigation`, which crashes `tsx --conditions=react-server` (used by
diff --git a/apps/docs/spec/reference/dart/v2/config.json b/apps/docs/spec/reference/dart/v2/config.json
new file mode 100644
index 00000000000..6f94523553b
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/config.json
@@ -0,0 +1,9 @@
+{
+ "categoryOrder": ["Database", "Auth", "Edge Functions", "Realtime", "Storage"],
+ "partialsOrder": ["introduction", "installing", "initializing", "upgrade-guide"],
+ "navigationPrefixes": {
+ "Database": false,
+ "Realtime": false,
+ "Edge Functions": "functions"
+ }
+}
diff --git a/apps/docs/spec/reference/dart/v2/partials/auth-admin.json b/apps/docs/spec/reference/dart/v2/partials/auth-admin.json
new file mode 100644
index 00000000000..8b83dec1d55
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/auth-admin.json
@@ -0,0 +1,13 @@
+{
+ "id": "auth-admin",
+ "title": "Auth Admin",
+ "notes": "- Any method under the `supabase.auth.admin` namespace requires a `secret` key.\n- These methods are considered admin methods and should be called on a trusted server. Never expose your `secret` key in the Flutter app.\n",
+ "examples": [
+ {
+ "id": "create-auth-admin-client",
+ "name": "Create server-side auth client",
+ "isSpotlight": true,
+ "code": "```dart\nfinal supabase = SupabaseClient(supabaseUrl, secretKey);\n```\n"
+ }
+ ]
+}
diff --git a/apps/docs/spec/reference/dart/v2/partials/auth-mfa.json b/apps/docs/spec/reference/dart/v2/partials/auth-mfa.json
new file mode 100644
index 00000000000..0bd76132415
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/auth-mfa.json
@@ -0,0 +1,5 @@
+{
+ "id": "auth-mfa",
+ "title": "Auth MFA",
+ "notes": "This section contains methods commonly used for Multi-Factor Authentication (MFA) and are invoked behind the `supabase.auth.mfa` namespace.\n\nCurrently, Supabase supports time-based one-time password (TOTP) and phone verification code as the 2nd factor. Recovery codes are not supported but users can enroll multiple factors, with an upper limit of 10..\n\nHaving a 2nd factor for recovery frees the user of the burden of having to store their recovery codes somewhere. It also reduces the attack surface since multiple recovery codes are usually generated compared to just having 1 backup factor.\n\nLearn more about implementing MFA on your application on our guide [here](https://supabase.com/docs/guides/auth/auth-mfa#overview).\n"
+}
diff --git a/apps/docs/spec/reference/dart/v2/partials/auth-passkey.json b/apps/docs/spec/reference/dart/v2/partials/auth-passkey.json
new file mode 100644
index 00000000000..0ef7219b3e4
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/auth-passkey.json
@@ -0,0 +1,5 @@
+{
+ "id": "auth-passkey",
+ "title": "Auth Passkey",
+ "notes": "This section contains methods for WebAuthn passkey registration, authentication, and management. Methods are invoked behind the `supabase.auth.passkey` namespace.\n\nThese methods expose the server side of the WebAuthn ceremony. The client side (the FaceID/TouchID/security key prompt) has to be performed with a platform passkey API: `navigator.credentials.create()`/`get()` on web, or a passkey plugin on iOS/Android/macOS. Options and credentials are exchanged as `Map` in the W3C WebAuthn Level 3 JSON format.\n\nFor a one-call alternative that runs the full ceremony, see [`signInWithPasskey()`](/docs/reference/dart/auth-signinwithpasskey) and [`registerPasskey()`](/docs/reference/dart/auth-registerpasskey) on `supabase_flutter`.\n\nPasskey support is a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.\n"
+}
diff --git a/apps/docs/spec/reference/dart/v2/partials/file-buckets.json b/apps/docs/spec/reference/dart/v2/partials/file-buckets.json
new file mode 100644
index 00000000000..ce5d2ef6ace
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/file-buckets.json
@@ -0,0 +1,5 @@
+{
+ "id": "file-buckets",
+ "title": "File Buckets",
+ "notes": "This section contains methods for working with File Buckets.\n"
+}
diff --git a/apps/docs/spec/reference/dart/v2/partials/initializing.json b/apps/docs/spec/reference/dart/v2/partials/initializing.json
new file mode 100644
index 00000000000..165a6b0c31c
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/initializing.json
@@ -0,0 +1,117 @@
+{
+ "id": "initializing",
+ "title": "Initializing",
+ "description": "You can initialize Supabase with the static `initialize()` method of the `Supabase` class.\n\nThe Supabase client is your entrypoint to the rest of the Supabase functionality\nand is the easiest way to interact with everything we offer within the Supabase ecosystem.\n",
+ "params": [
+ {
+ "name": "url",
+ "isOptional": false,
+ "type": "string",
+ "description": "The unique Supabase URL which is supplied when you create a new project in your project dashboard."
+ },
+ {
+ "name": "publishableKey",
+ "isOptional": false,
+ "type": "string",
+ "description": "The publishable (anon) key supplied when you create a new project in your project dashboard. Use this for client-side apps. The deprecated `anonKey` parameter is still accepted but `publishableKey` takes precedence when both are supplied."
+ },
+ {
+ "name": "headers",
+ "isOptional": true,
+ "type": "Map",
+ "description": "Custom header to be passed to the Supabase client."
+ },
+ {
+ "name": "httpClient",
+ "isOptional": true,
+ "type": "Client",
+ "description": "Custom http client to be used by the Supabase client."
+ },
+ {
+ "name": "authOptions",
+ "isOptional": true,
+ "type": "FlutterAuthClientOptions",
+ "description": "Options to change the Auth behaviors.",
+ "subContent": [
+ {
+ "name": "authFlowType",
+ "isOptional": true,
+ "type": "AuthFlowType",
+ "description": "Whether to use the `pkce` flow or the `implicit` flow. Defaults to `pkce`."
+ },
+ {
+ "name": "localStorage",
+ "isOptional": true,
+ "type": "LocalStorage",
+ "description": "Parameter to override the local storage to store auth tokens."
+ },
+ {
+ "name": "autoRefreshToken",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to automatically refresh the token when it expires. Defaults to `true`."
+ }
+ ]
+ },
+ {
+ "name": "postgrestOptions",
+ "isOptional": true,
+ "type": "PostgrestClientOptions",
+ "description": "Options to change the Postgrest behaviors.",
+ "subContent": [
+ {
+ "name": "schema",
+ "isOptional": true,
+ "type": "String",
+ "description": "Schema to query with the Supabase client. Defaults to `public`."
+ }
+ ]
+ },
+ {
+ "name": "realtimeClientOptions",
+ "isOptional": true,
+ "type": "RealtimeClientOptions",
+ "description": "Options to change the Realtime behaviors.",
+ "subContent": [
+ {
+ "name": "logLevel",
+ "isOptional": true,
+ "type": "RealtimeLogLevel",
+ "description": "Level of realtime server logs to to be logged."
+ }
+ ]
+ },
+ {
+ "name": "storageOptions",
+ "isOptional": true,
+ "type": "StorageClientOptions",
+ "description": "Options to change the Storage behaviors.",
+ "subContent": [
+ {
+ "name": "retryAttempts",
+ "isOptional": true,
+ "type": "int",
+ "description": "The number of times to retry a failed upload request. Defaults to `0`."
+ },
+ {
+ "name": "useNewHostname",
+ "isOptional": true,
+ "type": "bool",
+ "description": "Whether to rewrite legacy storage URLs to use the dedicated storage host (`[.storage.supabase.co`). Set to `true` only if your project has the dedicated storage host enabled. Defaults to `false`."
+ }
+ ]
+ }
+ ],
+ "examples": [
+ {
+ "id": "flutter-initialize",
+ "name": "For Flutter",
+ "code": "```dart\nFuture] main() async {\n await Supabase.initialize(\n url: 'https://xyzcompany.supabase.co',\n publishableKey: 'your-publishable-key',\n );\n\n runApp(MyApp());\n}\n\n// Get a reference your Supabase client\nfinal supabase = Supabase.instance.client;\n```\n"
+ },
+ {
+ "id": "for-other-dart-projects",
+ "name": "For other Dart projects",
+ "code": "```dart\nfinal supabase = SupabaseClient(\n 'https://xyzcompany.supabase.co',\n 'your-secret-key', // use your secret key for server-side usage\n);\n```\n"
+ }
+ ]
+}
diff --git a/apps/docs/spec/reference/dart/v2/partials/installing.mdx b/apps/docs/spec/reference/dart/v2/partials/installing.mdx
new file mode 100644
index 00000000000..d45311e3577
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/installing.mdx
@@ -0,0 +1,72 @@
+---
+title: 'Installing'
+---
+
+### Install from pub.dev
+
+
+
+
+ You can install Supabase package from [pub.dev](https://pub.dev/packages/supabase_flutter)
+
+
+
+
+
+
+
+
+ ```sh Terminal
+ flutter pub add supabase_flutter
+ ```
+
+
+
+
+ ```sh Terminal
+ dart pub add supabase
+ ```
+
+
+
+
+
+
+
+### Enable Data API access
+
+
+
+
+ supabase_flutter uses the Data API to query and mutate your Postgres data. You first need to grant Data API roles permissions to access your tables and functions.
+
+ In [Data API integrations settings](/dashboard/project/_/integrations/data_api/settings), expose the specific tables and functions you want to access. To automatically grant access for new tables and functions in `public`, enable **Default privileges for new entities**.
+
+ Alternatively, use SQL to grant the required permissions:
+
+
+
+
+
+ ```sql
+ -- Before granting access to client roles, make sure RLS is enabled
+ -- and create the policies required for each role's allowed operations.
+ alter table public.your_table enable row level security;
+ -- create policy ... on public.your_table ...;
+
+ -- Grant least-privilege access to tables after RLS and policies are in place
+ grant select on public.your_table to anon;
+ grant select, insert, update, delete on public.your_table to authenticated;
+ grant all on public.your_table to service_role;
+
+ -- Grant execute on functions after verifying any table access they rely on
+ grant execute on function public.your_function to authenticated, service_role;
+ ```
+
+
+
diff --git a/apps/docs/spec/reference/dart/v2/partials/introduction.mdx b/apps/docs/spec/reference/dart/v2/partials/introduction.mdx
new file mode 100644
index 00000000000..a1b21c70f2b
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/introduction.mdx
@@ -0,0 +1,7 @@
+---
+title: 'Introduction'
+---
+
+This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
+
+We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
diff --git a/apps/docs/spec/reference/dart/v2/partials/passkey-admin.json b/apps/docs/spec/reference/dart/v2/partials/passkey-admin.json
new file mode 100644
index 00000000000..da427fe193d
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/passkey-admin.json
@@ -0,0 +1,5 @@
+{
+ "id": "passkey-admin",
+ "title": "Passkey Admin",
+ "notes": "Contains passkey administration methods, accessed under the `supabase.auth.admin.passkey` namespace. Requires a `secret` key.\n\nPasskey support is a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.\n"
+}
diff --git a/apps/docs/spec/reference/dart/v2/partials/upgrade-guide.mdx b/apps/docs/spec/reference/dart/v2/partials/upgrade-guide.mdx
new file mode 100644
index 00000000000..07cb3ddcf54
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/upgrade-guide.mdx
@@ -0,0 +1,815 @@
+---
+title: 'Upgrade guide'
+---
+
+Although `supabase_flutter` v2 brings a few breaking changes, for the most part the public API should be the same with a few minor exceptions.
+We have brought numerous updates behind the scenes to make the SDK work more intuitively for Flutter and Dart developers.
+
+## Upgrade the client library
+
+
+
+
+ Make sure you are using v2 of the client library in your `pubspec.yaml` file.
+
+
+
+
+ ```yaml
+ supabase_flutter: ^2.0.0
+ ```
+
+
+
+
+
+
+
+ _Optionally_ passing custom configuration to `Supabase.initialize()` is now organized into separate objects:
+
+
+
+
+
+
+
+ ```dart main.dart
+ await Supabase.initialize(
+ url: supabaseUrl,
+ publishableKey: publishableKey,
+ authFlowType: AuthFlowType.pkce,
+ storageRetryAttempts: 10,
+ realtimeClientOptions: const RealtimeClientOptions(
+ logLevel: RealtimeLogLevel.info,
+ ),
+ );
+ ```
+
+
+
+
+ ```dart main.dart
+ await Supabase.initialize(
+ url: 'SUPABASE_URL',
+ publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
+ authOptions: const FlutterAuthClientOptions(
+ authFlowType: AuthFlowType.pkce,
+ ),
+ realtimeClientOptions: const RealtimeClientOptions(
+ logLevel: RealtimeLogLevel.info,
+ ),
+ storageOptions: const StorageClientOptions(
+ retryAttempts: 10,
+ ),
+ );
+ ```
+
+
+
+
+
+
+
+
+### Auth updates
+
+
+
+
+ #### Renaming Provider to OAuthProvider
+
+ `Provider` enum is renamed to `OAuthProvider`.
+ Previously the `Provider` symbol often collided with classes in the [provider](https://pub.dev/packages/provider) package and developers needed to add import prefixes to avoid collisions.
+ With the new update, developers can use Supabase and Provider in the same codebase without any import prefixes.
+
+
+
+
+
+
+ ```dart
+ await supabase.auth.signInWithOAuth(
+ Provider.google,
+ );
+ ```
+
+
+
+ ```dart
+ await supabase.auth.signInWithOAuth(
+ OAuthProvider.google,
+ );
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### Sign in with Apple method deprecated
+
+ We have removed the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) dependency in v2.
+ This is because not every developer needs to sign in with Apple, and we want to reduce the number of dependencies in the library.
+
+ With v2, you can import [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) as a separate dependency if you need to sign in with Apple.
+ We have also added `auth.generateRawNonce()` method to easily generate a secure nonce.
+
+
+
+
+
+ ```dart
+ await supabase.auth.signInWithApple();
+ ```
+
+
+
+ ```dart
+ Future signInWithApple() async {
+ final rawNonce = supabase.auth.generateRawNonce();
+ final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();
+
+ final credential = await SignInWithApple.getAppleIDCredential(
+ scopes: [
+ AppleIDAuthorizationScopes.email,
+ AppleIDAuthorizationScopes.fullName,
+ ],
+ nonce: hashedNonce,
+ );
+
+ final idToken = credential.identityToken;
+ if (idToken == null) {
+ throw const AuthException(
+ 'Could not find ID Token from generated credential.',
+ );
+ }
+
+ return signInWithIdToken(
+ provider: OAuthProvider.apple,
+ idToken: idToken,
+ nonce: rawNonce,
+ );
+ }
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### Initialization does not await for session refresh
+
+ In v1, `Supabase.initialize()` would await for the session to be refreshed before returning.
+ This caused delays in the app's launch time, especially when the app is opened in a poor network environment.
+
+ In v2, `Supabase.initialize()` returns immediately after obtaining the session from the local storage, which makes the app launch faster.
+ Because of this, there is no guarantee that the session is valid when the app starts.
+
+ If you need to make sure the session is valid, you can access the `isExpired` getter to check if the session is valid.
+ If the session is expired, you can listen to the `onAuthStateChange` event and wait for a new `tokenRefreshed` event to be fired.
+
+
+
+
+
+ ```dart
+ // Session is valid, no check required
+ final session = supabase.auth.currentSession;
+ ```
+
+
+
+ ```dart
+ final session = supabase.auth.currentSession;
+
+ // Check if the session is valid.
+ final isSessionExpired = session?.isExpired;
+ ```
+
+
+
+
+
+
+
+
+
+
+
+ #### Removing Flutter Webview dependency for OAuth sign in
+
+ In v1, on iOS you could pass a `BuildContext` to the `signInWithOAuth()` method to launch the OAuth flow in a Flutter Webview.
+
+ In v2, we have dropped the [webview_flutter](https://pub.dev/packages/webview_flutter) dependency in v2 to allow you to have full control over the UI of the OAuth flow.
+ We now have [native support for Google and Apple sign in](/docs/reference/dart/auth-signinwithidtoken), so opening an external browser is no longer needed on iOS.
+
+ Because of this update, we no longer need the `context` parameter, so we have removed the `context` parameter from the `signInWithOAuth()` method.
+
+
+
+
+
+
+ ```dart
+ // Opens a webview on iOS.
+ await supabase.auth.signInWithOAuth(
+ Provider.github,
+ authScreenLaunchMode: LaunchMode.inAppWebView,
+ context: context,
+ );
+ ```
+
+
+
+ ```dart
+ // Opens in app webview on iOS.
+ await supabase.auth.signInWithOAuth(
+ OAuthProvider.github,
+ authScreenLaunchMode: LaunchMode.inAppWebView,
+ );
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### PKCE is the default auth flow type
+
+ [PKCE flow](https://supabase.com/blog/supabase-auth-sso-pkce#introducing-pkce), which is a more secure method for obtaining sessions from deep links, is now the default auth flow for any authentication involving deep links.
+
+
+
+
+
+ ```dart
+ await Supabase.initialize(
+ url: 'SUPABASE_URL',
+ publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
+ authFlowType: AuthFlowType.implicit, // set to implicit by default
+ );
+ ```
+
+
+
+ ```dart
+ await Supabase.initialize(
+ url: 'SUPABASE_URL',
+ publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
+ authOptions: FlutterAuthClientOptions(
+ authFlowType: AuthFlowType.pkce, // set to pkce by default
+ )
+ );
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### Auth callback host name parameter removed
+
+ `Supabase.initialize()` no longer has the `authCallbackUrlHostname` parameter.
+ The `supabase_flutter` SDK will automatically detect auth callback URLs and handle them internally.
+
+
+
+
+
+ ```dart
+ await Supabase.initialize(
+ url: 'SUPABASE_URL',
+ publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
+ authCallbackUrlHostname: 'auth-callback',
+ );
+ ```
+
+
+
+ ```dart
+ await Supabase.initialize(
+ url: 'SUPABASE_URL',
+ publishableKey: 'SUPABASE_PUBLISHABLE_KEY',
+ );
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### SupabaseAuth class removed
+
+ The `SupabaseAuth` had an `initialSession` member, which was used to obtain the initial session upon app start.
+ This is now removed, and `currentSession` should be used to access the session at any time.
+
+
+
+
+
+ ```dart
+ // Use `initialSession` to obtain the initial session when the app starts.
+ final initialSession = await SupabaseAuth.initialSession;
+ ```
+
+
+
+ ```dart
+ // Use `currentSession` to access the session at any time.
+ final initialSession = await supabase.auth.currentSession;
+ ```
+
+
+
+
+
+
+
+### Data methods
+
+
+
+
+ #### Insert and return data
+
+ We made the query builder immutable, which means you can reuse the same query object to chain multiple filters and get the expected outcome.
+
+
+
+
+
+
+ ```dart
+ // If you declare a query and chain filters on it
+ final myQuery = supabase.from('my_table').select();
+
+ final foo = await myQuery.eq('some_col', 'foo');
+
+ // The `eq` filter above is applied in addition to the following filter
+ final bar = await myQuery.eq('another_col', 'bar');
+ ```
+
+
+
+ ```dart
+ // Now you can declare a query and reuse it.
+ final myQuery = supabase.from('my_table').select();
+
+ final foo = await myQuery.eq('some_col', 'foo');
+
+ // The `eq` filter above is not applied to the following result
+ final bar = await myQuery.eq('another_col', 'bar');
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### Renaming is and in filter
+
+ Because `is` and `in` are [reserved keywords](https://dart.dev/languages/keywords) in Dart, v1 used `is_` and `in_` as query filter names.
+ Users found the underscore confusing, so the query filters are now renamed to `isFilter` and `inFilter`.
+
+
+
+
+
+
+ ```dart
+ final data = await supabase
+ .from('users')
+ .select()
+ .is_('status', null);
+
+ final data = await supabase
+ .from('users')
+ .select()
+ .in_('status', ['ONLINE', 'OFFLINE']);
+ ```
+
+
+
+ ```dart
+ final data = await supabase
+ .from('users')
+ .select()
+ .isFilter('status', null);
+
+ final data = await supabase
+ .from('users')
+ .select()
+ .inFilter('status', ['ONLINE', 'OFFLINE']);
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### Deprecate FetchOption in favor of `count()` and `head()` methods
+
+ `FetchOption()` on `.select()` is now deprecated, and new `.count()` and `head()` methods are added to the query builder.
+
+ `count()` on `.select()` performs the select while also getting the count value, and `.count()` directly on `.from()` performs a head request resulting in only fetching the count value.
+
+
+
+
+
+
+ ```dart
+ // Request with count option
+ final res = await supabase.from('cities').select(
+ 'name',
+ const FetchOptions(
+ count: CountOption.exact,
+ ),
+ );
+
+ final data = res.data;
+ final count = res.count;
+
+ // Request with count and head option
+ // obtains the count value without fetching the data.
+ final res = await supabase.from('cities').select(
+ 'name',
+ const FetchOptions(
+ count: CountOption.exact,
+ head: true,
+ ),
+ );
+
+ final count = res.count;
+ ```
+
+
+
+ ```dart
+ // Request with count option
+ final res = await supabase
+ .from('cities')
+ .select('name')
+ .count(); // CountOption.exact is the default value
+
+ final data = res.data;
+ final int count = res.count;
+
+ // `.count()` directly on `.from()` performs a head request,
+ // obtaining the count value without fetching the data.
+ final int count = await supabase
+ .from('cities')
+ .count(); // CountOption.exact is the default value
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### PostgREST error codes
+
+ The `PostgrestException` instance thrown by the API methods has a `code` property. In v1, the `code` property contained the http status code.
+
+ In v2, the `code` property contains the [PostgREST error code](https://postgrest.org/en/stable/references/errors.html), which is more useful for debugging.
+
+
+
+
+
+
+ ```dart
+ try {
+ await supabase.from('countries').select();
+ } on PostgrestException catch (error) {
+ error.code; // Contains http status code
+ }
+ ```
+
+
+
+ ```dart
+ try {
+ await supabase.from('countries').select();
+ } on PostgrestException catch (error) {
+ error.code; // Contains PostgREST error code
+ }
+ ```
+
+
+
+
+
+
+
+ ### Realtime methods
+
+ Realtime methods contains the biggest breaking changes. Most of these changes are to make the interface more type safe.
+
+ We have removed the `.on()` method and replaced it with `.onPostgresChanges()`, `.onBroadcast()`, and three different presence methods.
+
+
+
+
+ #### Postgres Changes
+
+ Use the new `.onPostgresChanges()` method to listen to realtime changes in the database.
+
+ In v1, filters were not strongly typed because they took a `String` type. In v2, `filter` takes an object. Its properties are strictly typed to catch type errors.
+
+ The payload of the callback is now typed as well. In `v1`, the payload was returned as `dynamic`. It is now returned as a `PostgresChangePayload` object. The object contains the `oldRecord` and `newRecord` properties for accessing the data before and after the change.
+
+
+
+
+
+
+
+ ```dart
+ supabase.channel('my_channel').on(
+ RealtimeListenTypes.postgresChanges,
+ ChannelFilter(
+ event: '*',
+ schema: 'public',
+ table: 'messages',
+ filter: 'room_id=eq.200',
+ ),
+ (dynamic payload, [ref]) {
+ final Map newRecord = payload['new'];
+ final Map oldRecord = payload['old'];
+ },
+ ).subscribe();
+ ```
+
+
+
+
+ ```dart
+ supabase.channel('my_channel')
+ .onPostgresChanges(
+ event: PostgresChangeEvent.all,
+ schema: 'public',
+ table: 'messages',
+ filter: PostgresChangeFilter(
+ type: PostgresChangeFilterType.eq,
+ column: 'room_id',
+ value: 200,
+ ),
+ callback: (PostgresChangePayload payload) {
+ final Map newRecord = payload.newRecord;
+ final Map oldRecord = payload.oldRecord;
+ })
+ .subscribe();
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### Broadcast
+
+ Broadcast now uses the dedicated `.onBroadcast()` method, rather than the generic `.on()` method.
+ Because the method is specific to broadcast, it takes fewer properties.
+
+
+
+
+
+
+
+ ```dart
+ supabase.channel('my_channel').on(
+ RealtimeListenTypes.broadcast,
+ ChannelFilter(
+ event: 'position',
+ ),
+ (dynamic payload, [ref]) {
+ print(payload);
+ },
+ ).subscribe();
+ ```
+
+
+
+
+ ```dart
+ supabase
+ .channel('my_channel')
+ .onBroadcast(
+ event: 'position',
+ callback: (Map payload) {
+ print(payload);
+ })
+ .subscribe();
+ ```
+
+
+
+
+
+
+
+
+
+
+ #### Presence
+
+ Realtime Presence gets three different methods for listening to three different presence events: `sync`, `join`, and `leave`.
+ This allows the callback to be strictly typed.
+
+
+
+
+
+
+
+ ```dart
+ final channel = supabase.channel('room1');
+
+ channel.on(
+ RealtimeListenTypes.presence,
+ ChannelFilter(event: 'sync'),
+ (payload, [ref]) {
+ print('Synced presence state: ${channel.presenceState()}');
+ },
+ ).on(
+ RealtimeListenTypes.presence,
+ ChannelFilter(event: 'join'),
+ (payload, [ref]) {
+ print('Newly joined presences $payload');
+ },
+ ).on(
+ RealtimeListenTypes.presence,
+ ChannelFilter(event: 'leave'),
+ (payload, [ref]) {
+ print('Newly left presences: $payload');
+ },
+ ).subscribe(
+ (status, [error]) async {
+ if (status == 'SUBSCRIBED') {
+ await channel.track({'online_at': DateTime.now().toIso8601String()});
+ }
+ },
+ );
+ ```
+
+
+
+
+ ```dart
+ final channel = supabase.channel('room1');
+
+ channel.onPresenceSync(
+ (payload) {
+ print('Synced presence state: ${channel.presenceState()}');
+ },
+ ).onPresenceJoin(
+ (payload) {
+ print('Newly joined presences $payload');
+ },
+ ).onPresenceLeave(
+ (payload) {
+ print('Newly left presences: $payload');
+ },
+ ).subscribe(
+ (status, error) async {
+ if (status == RealtimeSubscribeStatus.subscribed) {
+ await channel
+ .track({'online_at': DateTime.now().toIso8601String()});
+ }
+ },
+ );
+ ```
+
+
+
+
+
+
diff --git a/apps/docs/spec/reference/dart/v2/partials/using-filters.json b/apps/docs/spec/reference/dart/v2/partials/using-filters.json
new file mode 100644
index 00000000000..83a61c9af66
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/using-filters.json
@@ -0,0 +1,44 @@
+{
+ "id": "using-filters",
+ "title": "Using filters",
+ "description": "Filters allow you to only return rows that match certain conditions.\n\nFilters can be used on `select()`, `update()`, `upsert()`, and `delete()` queries.\n\nIf a Database function returns a table response, you can also apply filters.\n",
+ "examples": [
+ {
+ "id": "applying-filters",
+ "name": "Applying Filters",
+ "description": "Filters must be applied after any of `select()`, `update()`, `upsert()`,\n`delete()`, and `rpc()` and before\n[modifiers](/docs/reference/dart/using-modifiers).\n",
+ "code": "```dart\nfinal data = await supabase\n .from('cities')\n .select('name, country_id')\n .eq('name', 'The Shire'); // Correct\n\nfinal data = await supabase\n .from('cities')\n .eq('name', 'The Shire') // Incorrect\n .select('name, country_id');\n```\n"
+ },
+ {
+ "id": "chaining-filters",
+ "name": "Chaining Filters",
+ "description": "Filters can be chained together to produce advanced queries as shown in the example code.\n",
+ "code": "```dart\nfinal data = await supabase\n .from('cities')\n .select('name, country_id')\n .gte('population', 1000)\n .lt('population', 10000)\n```\n"
+ },
+ {
+ "id": "conditional-chaining",
+ "name": "Conditional Chaining",
+ "description": "Filters can be built up one step at a time and then executed as shown in the example code.\n",
+ "code": "```dart\nfinal filterByName = null;\nfinal filterPopLow = 1000;\nfinal filterPopHigh = 10000;\n\nvar query = supabase\n .from('cities')\n .select('name, country_id');\n\nif (filterByName != null) query = query.eq('name', filterByName);\nif (filterPopLow != null) query = query.gte('population', filterPopLow);\nif (filterPopHigh != null) query = query.lt('population', filterPopHigh);\n\nfinal data = await query;\n```\n"
+ },
+ {
+ "id": "filter-by-value-within-json-column",
+ "name": "Filter by values within a JSON column",
+ "data": {
+ "sql": "```sql\ncreate table\n users (\n id int8 primary key,\n name text,\n address jsonb\n );\n\ninsert into\n users (id, name, address)\nvalues\n (1, 'Michael', '{ \"postcode\": 90210 }'),\n (2, 'Jane', null);\n```\n"
+ },
+ "response": "```json\n[\n {\n 'id': 1,\n 'name': 'Michael',\n 'address': {\n 'postcode': 90210\n }\n },\n]\n```\n",
+ "code": "```dart\nfinal data = await supabase\n .from('users')\n .select()\n .eq('address->postcode', 90210);\n```\n"
+ },
+ {
+ "id": "filter-referenced-tables",
+ "name": "Filter Referenced Tables",
+ "code": "```dart\nfinal data = await supabase\n .from('orchestral_sections')\n .select('''\n name,\n instruments!inner (\n name\n )\n ''')\n .eq('instruments.name', 'flute');\n```\n",
+ "data": {
+ "sql": "```sql\ncreate table\n orchestral_sections (id int8 primary key, name text);\ncreate table\n instruments (\n id int8 primary key,\n section_id int8 not null references orchestral_sections,\n name text\n );\n\ninsert into\n orchestral_sections (id, name)\nvalues\n (1, 'strings'),\n (2, 'woodwinds');\ninsert into\n instruments (id, section_id, name)\nvalues\n (1, 2, 'flute'),\n (2, 1, 'violin');\n```\n"
+ },
+ "response": "```json\n[\n {\n 'name': 'strings',\n 'instruments': [\n {\n 'name': 'flute'\n }\n ]\n },\n]\n```\n",
+ "description": "You can filter on referenced tables in your `select()` query using dot\nnotation.\n"
+ }
+ ]
+}
diff --git a/apps/docs/spec/reference/dart/v2/partials/using-modifiers.json b/apps/docs/spec/reference/dart/v2/partials/using-modifiers.json
new file mode 100644
index 00000000000..834f82fcf48
--- /dev/null
+++ b/apps/docs/spec/reference/dart/v2/partials/using-modifiers.json
@@ -0,0 +1,5 @@
+{
+ "id": "using-modifiers",
+ "title": "Using modifiers",
+ "description": "Filters work on the row level. That is, they allow you to return rows that\nonly match certain conditions without changing the shape of the rows.\nModifiers are everything that don't fit that definition—allowing you to\nchange the format of the response (e.g., returning a CSV string).\n\nModifiers must be specified after filters. Some modifiers only apply for\nqueries that return rows (e.g., `select()` or `rpc()` on a function that\nreturns a table response).\n"
+}
diff --git a/apps/docs/spec/reference/server/v1/config.json b/apps/docs/spec/reference/server/v1/config.json
new file mode 100644
index 00000000000..dc086ebe58f
--- /dev/null
+++ b/apps/docs/spec/reference/server/v1/config.json
@@ -0,0 +1,5 @@
+{
+ "categoryOrder": ["Middleware", "Primitives", "Adapters", "Errors", "Types"],
+ "partialsOrder": ["introduction", "installing"],
+ "navigationPrefixes": {}
+}
diff --git a/apps/docs/spec/reference/server/v1/partials/installing.mdx b/apps/docs/spec/reference/server/v1/partials/installing.mdx
new file mode 100644
index 00000000000..0f0df8a9b91
--- /dev/null
+++ b/apps/docs/spec/reference/server/v1/partials/installing.mdx
@@ -0,0 +1,84 @@
+---
+id: installing
+title: Installing
+slug: installing
+---
+
+### Install as a package
+
+
+
+
+ Install `@supabase/server` via your package manager.
+
+
+
+
+
+
+
+
+ ```sh Terminal
+ npm install @supabase/server
+ ```
+
+
+
+
+ ```sh Terminal
+ yarn add @supabase/server
+ ```
+
+
+
+
+ ```sh Terminal
+ pnpm add @supabase/server
+ ```
+
+
+
+
+
+
+
+### Use via JSR (Deno / Bun)
+
+
+
+
+ `@supabase/server` is also published to [JSR](https://jsr.io/@supabase/server) for Deno and Bun environments.
+
+
+
+
+
+
+
+
+ ```sh Terminal
+ deno add jsr:@supabase/server
+ ```
+
+
+
+
+ ```sh Terminal
+ bunx jsr add @supabase/server
+ ```
+
+
+
+
+
+
diff --git a/apps/docs/spec/reference/server/v1/partials/introduction.mdx b/apps/docs/spec/reference/server/v1/partials/introduction.mdx
new file mode 100644
index 00000000000..eaacb0c51dd
--- /dev/null
+++ b/apps/docs/spec/reference/server/v1/partials/introduction.mdx
@@ -0,0 +1,8 @@
+---
+id: introduction
+title: Introduction
+---
+
+`@supabase/server` is a framework-agnostic library for authenticating requests in server-side JavaScript environments. It verifies JWTs, resolves Supabase API keys, and creates pre-configured Supabase clients — exposing everything through a single `SupabaseContext` that is identical regardless of which adapter or primitive produced it.
+
+Adapters for Hono, H3, Elysia, and NestJS are included. You can also compose the lower-level primitives directly for custom frameworks or edge runtimes.
diff --git a/apps/docs/spec/supabase_dart_v2.yml b/apps/docs/spec/supabase_dart_v2.yml
index 4371c692617..66393e48d8f 100644
--- a/apps/docs/spec/supabase_dart_v2.yml
+++ b/apps/docs/spec/supabase_dart_v2.yml
@@ -888,12 +888,17 @@ functions:
notes: |
Signs the user in with a passkey (WebAuthn).
- Available on `supabase_flutter` 2.15.0 and later as an extension on `GoTrueClient`.
- - Drives the full WebAuthn ceremony end to end: starts the challenge with the Supabase server, prompts the user for biometrics or a security key via the platform passkey API, and verifies the credential with the server.
+ - Drives the full WebAuthn ceremony end to end: starts the challenge with the Supabase server, calls the `authenticator` you supply to prompt the user for biometrics or a security key, and verifies the credential with the server.
- Does not require an existing session. On success the session is persisted and an `AuthChangeEvent.signedIn` event is fired.
+ - `supabase_flutter` does not depend on a passkey plugin directly. Pass an implementation of `PasskeyAuthenticatorInterface`, such as the [`passkeys`](https://pub.dev/packages/passkeys) plugin's `PasskeyAuthenticator` (since `passkeys` `2.21.0`).
- For native flows or custom UI, use the lower-level [`auth.passkey`](/docs/reference/dart/auth-passkey-api) namespace instead.
- Passkeys are a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.
- Platform setup the library cannot perform (Associated Domains on iOS/macOS, Digital Asset Links on Android, the `passkeys` web SDK on web) is documented in the `supabase_flutter` package README.
params:
+ - name: authenticator
+ isOptional: false
+ type: PasskeyAuthenticatorInterface
+ description: Performs the platform passkey ceremony (FaceID/TouchID/security key). For example, a `PasskeyAuthenticator` from the `passkeys` package.
- name: captchaToken
isOptional: true
type: String
@@ -904,7 +909,11 @@ functions:
isSpotlight: true
code: |
```dart
- final AuthResponse res = await supabase.auth.signInWithPasskey();
+ import 'package:passkeys/authenticator.dart';
+
+ final authenticator = PasskeyAuthenticator();
+
+ final AuthResponse res = await supabase.auth.signInWithPasskey(authenticator);
final Session? session = res.session;
final User? user = res.user;
```
@@ -913,17 +922,27 @@ functions:
notes: |
Registers a new passkey (WebAuthn credential) for the signed in user.
- Available on `supabase_flutter` 2.15.0 and later as an extension on `GoTrueClient`.
- - Drives the full WebAuthn ceremony end to end: starts the registration with the Supabase server, prompts the user to create a credential on the device, and verifies it with the server.
+ - Drives the full WebAuthn ceremony end to end: starts the registration with the Supabase server, calls the `authenticator` you supply to create a credential on the device, and verifies it with the server.
- Requires a signed in (non-anonymous) user. If the user has verified MFA factors, the session has to be at `aal2` to manage passkeys.
+ - `supabase_flutter` does not depend on a passkey plugin directly. Pass an implementation of `PasskeyAuthenticatorInterface`, such as the [`passkeys`](https://pub.dev/packages/passkeys) plugin's `PasskeyAuthenticator` (since `passkeys` `2.21.0`).
- For native flows or custom UI, use the lower-level [`auth.passkey`](/docs/reference/dart/auth-passkey-api) namespace instead.
- Passkeys are a BETA feature and must be enabled for your project in the Supabase Dashboard under Authentication > Configuration > Passkeys.
+ params:
+ - name: authenticator
+ isOptional: false
+ type: PasskeyAuthenticatorInterface
+ description: Performs the platform passkey ceremony (FaceID/TouchID/security key). For example, a `PasskeyAuthenticator` from the `passkeys` package.
examples:
- id: register-passkey
name: Register a passkey for the current user
isSpotlight: true
code: |
```dart
- final Passkey passkey = await supabase.auth.registerPasskey();
+ import 'package:passkeys/authenticator.dart';
+
+ final authenticator = PasskeyAuthenticator();
+
+ final Passkey passkey = await supabase.auth.registerPasskey(authenticator);
```
- id: sign-out
title: 'signOut()'
@@ -2570,11 +2589,16 @@ functions:
notes: |
Delete a user.
- The `deleteUser()` method requires the user's ID, which maps to the `auth.users.id` column.
+ - When `shouldSoftDelete` is `true`, the user is soft-deleted: their record and associated data are retained but the user is marked as deleted. Defaults to `false`, which permanently removes the user.
params:
- name: id
isOptional: false
type: String
description: ID of the user to be deleted.
+ - name: shouldSoftDelete
+ isOptional: true
+ type: bool
+ description: If true, soft-deletes the user (keeps the record but marks it deleted). Defaults to false (permanent delete).
examples:
- id: removes-a-user
name: Removes a user
@@ -2584,6 +2608,16 @@ functions:
await supabase.auth.admin
.deleteUser('715ed5db-f090-4b8c-a067-640ecee36aa0');
```
+ - id: soft-delete-a-user
+ name: Soft-delete a user
+ code: |
+ ```dart
+ await supabase.auth.admin
+ .deleteUser(
+ '715ed5db-f090-4b8c-a067-640ecee36aa0',
+ shouldSoftDelete: true,
+ );
+ ```
- id: invite-user-by-email
title: 'inviteUserByEmail()'
notes: |
@@ -4615,6 +4649,10 @@ functions:
isOptional: false
type: int
description: The number of seconds until the signed URL expires. For example, 60 for a URL which is valid for one minute.
+ - name: download
+ isOptional: true
+ type: DownloadBehavior
+ description: Triggers the file to be downloaded rather than opened in the browser. Use `DownloadBehavior.withOriginalName` to keep the original file name or `DownloadBehavior.named('custom.png')` to override it.
- name: transform
isOptional: true
type: TransformOptions
@@ -4671,6 +4709,61 @@ functions:
```json
'https://example.supabase.co/storage/v1/object/sign/avatars/folder/avatar1.png?token=''
```
+ - id: create-signed-url-with-download
+ name: With download
+ code: |
+ ```dart
+ final String signedUrl = await supabase
+ .storage
+ .from('avatars')
+ .createSignedUrl(
+ 'avatar1.png',
+ 60,
+ download: DownloadBehavior.withOriginalName,
+ );
+ ```
+
+ - id: from-create-signed-upload-url
+ description: |
+ Creates a signed upload URL. Signed upload URLs can be used to upload files to a bucket without further authentication. They are valid for 2 hours.
+ title: 'from.createSignedUploadUrl()'
+ notes: |
+ - Policy permissions required:
+ - `buckets` permissions: none
+ - `objects` permissions: `insert`
+ - Refer to the [Storage guide](https://supabase.com/docs/guides/storage/security/access-control) on how access control works
+ params:
+ - name: path
+ isOptional: false
+ type: String
+ description: The file path, including the current file name. For example folder/image.png.
+ - name: upsert
+ isOptional: true
+ type: bool
+ description: If true, the signed URL allows overwriting an existing file at the path. Defaults to false.
+ examples:
+ - id: create-signed-upload-url
+ name: Create signed upload URL
+ isSpotlight: true
+ code: |
+ ```dart
+ final response = await supabase
+ .storage
+ .from('avatars')
+ .createSignedUploadUrl('folder/avatar1.png');
+ ```
+ - id: create-signed-upload-url-with-upsert
+ name: With upsert
+ code: |
+ ```dart
+ final response = await supabase
+ .storage
+ .from('avatars')
+ .createSignedUploadUrl(
+ 'folder/avatar1.png',
+ upsert: true,
+ );
+ ```
- id: from-get-public-url
description: |
@@ -4687,6 +4780,10 @@ functions:
isOptional: false
type: String
description: The path and name of the file to generate the public URL for. For example folder/image.png.
+ - name: download
+ isOptional: true
+ type: DownloadBehavior
+ description: Triggers the file to be downloaded rather than opened in the browser. Use `DownloadBehavior.withOriginalName` to keep the original file name or `DownloadBehavior.named('custom.png')` to override it.
- name: transform
isOptional: true
type: TransformOptions
@@ -4747,6 +4844,18 @@ functions:
```json
'https://example.supabase.co/storage/v1/object/public/public-bucket/folder/avatar1.png'
```
+ - id: returns-the-url-for-an-asset-with-download
+ name: Trigger download
+ code: |
+ ```dart
+ final String publicUrl = supabase
+ .storage
+ .from('public-bucket')
+ .getPublicUrl(
+ 'avatar1.png',
+ download: DownloadBehavior.withOriginalName,
+ );
+ ```
- id: from-download
description: |
diff --git a/apps/docs/spec/supabase_swift_v2.yml b/apps/docs/spec/supabase_swift_v2.yml
index 4fcb84eb1d3..043346624fa 100644
--- a/apps/docs/spec/supabase_swift_v2.yml
+++ b/apps/docs/spec/supabase_swift_v2.yml
@@ -4384,6 +4384,8 @@ functions:
It's best to only enable this for testing environments but if you wish to enable it for production you can provide additional protection by using a `pre-request` function.
Follow the [Performance Debugging Guide](/docs/guides/database/debugging-performance) to enable the functionality on your project.
+ notes: |
+ The `format` parameter accepts an `ExplainFormat` value — either `.text` (default, human-readable) or `.json` (machine-readable JSON).
examples:
- id: get-execution-plan
name: Get the execution plan
@@ -4463,6 +4465,21 @@ functions:
By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter.
hideCodeBlock: true
isSpotlight: false
+
+ - id: get-execution-plan-json-format
+ name: Get the execution plan as JSON
+ code: |
+ ```swift
+ try await supabase
+ .from("instruments")
+ .select()
+ .explain(format: .json)
+ .execute()
+ .value
+ ```
+ description: |
+ Use `format: .json` to get a machine-readable JSON execution plan. The `ExplainFormat` type accepts `.text` (default) or `.json`.
+ isSpotlight: false
- id: invoke
title: invoke()
description: |
@@ -5176,7 +5193,7 @@ functions:
.createBucket(
"avatars",
options: BucketOptions(
- public: false,
+ isPublic: false,
allowedMimeTypes: ["image/png"],
fileSizeLimit: 1024
)
@@ -5216,7 +5233,7 @@ functions:
.updateBucket(
"avatars",
options: BucketOptions(
- public: false,
+ isPublic: false,
fileSizeLimit: 1024,
allowedMimeTypes: ["image/png"]
)
@@ -5375,12 +5392,13 @@ functions:
let signedURL = try await supabase.storage
.from("avatars")
.createSignedURL(
- path: "folder/avatar1.png", expiresIn: 60,
- download: true
+ path: "folder/avatar1.png",
+ expiresIn: 60,
+ download: .withOriginalName
)
```
note: |
- You can also sepcify a `String` in the `download` parameter to define the file name for the downloaded asset.
+ Use `.withOriginalName` to download with the original file name, or `.named("custom.png")` to specify a custom file name. The `download: Bool` overload still compiles but is disfavored.
- id: from-create-signed-urls
title: from.createSignedUrls()
@@ -5519,11 +5537,11 @@ functions:
.from("public-bucket")
.getPublicURL(
path: "folder/avatar1.png",
- download: true
+ download: .withOriginalName
)
```
note: |
- You can also sepcify a `String` in the `download` parameter to define the file name for the downloaded asset.
+ Use `.withOriginalName` to download with the original file name, or `.named("custom.png")` to specify a custom file name. The `download: Bool` overload still compiles but is disfavored.
- id: from-download
title: from.download()
diff --git a/apps/docs/spec/transforms/api_v1_openapi_deparsed.json b/apps/docs/spec/transforms/api_v1_openapi_deparsed.json
index a52a6d0f5ef..c7576a9b540 100644
--- a/apps/docs/spec/transforms/api_v1_openapi_deparsed.json
+++ b/apps/docs/spec/transforms/api_v1_openapi_deparsed.json
@@ -5445,11 +5445,7 @@
},
"unavailableReason": {
"type": "string",
- "enum": [
- "manual_migration_required",
- "postgres_upgrade_required",
- "temporarily_unavailable"
- ]
+ "enum": ["postgres_upgrade_required", "temporarily_unavailable"]
}
},
"required": ["state", "unavailableReason"]
@@ -5560,11 +5556,7 @@
},
"unavailableReason": {
"type": "string",
- "enum": [
- "manual_migration_required",
- "postgres_upgrade_required",
- "temporarily_unavailable"
- ]
+ "enum": ["postgres_upgrade_required", "temporarily_unavailable"]
}
},
"required": ["state", "unavailableReason"]
@@ -6488,11 +6480,22 @@
"nullable": true,
"description": "If `null`, the value is automatically configured based on compute size."
},
+ "db_pool_acquisition_timeout": {
+ "type": "integer",
+ "nullable": true,
+ "description": "If `null`, the value is automatically configured to 10."
+ },
"jwt_secret": {
"type": "string"
}
},
- "required": ["db_schema", "max_rows", "db_extra_search_path", "db_pool"]
+ "required": [
+ "db_schema",
+ "max_rows",
+ "db_extra_search_path",
+ "db_pool",
+ "db_pool_acquisition_timeout"
+ ]
}
}
}
@@ -6568,6 +6571,11 @@
"type": "integer",
"minimum": 0,
"maximum": 1000
+ },
+ "db_pool_acquisition_timeout": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 60
}
},
"example": {
@@ -6600,9 +6608,20 @@
"type": "integer",
"nullable": true,
"description": "If `null`, the value is automatically configured based on compute size."
+ },
+ "db_pool_acquisition_timeout": {
+ "type": "integer",
+ "nullable": true,
+ "description": "If `null`, the value is automatically configured to 10."
}
},
- "required": ["db_schema", "max_rows", "db_extra_search_path", "db_pool"]
+ "required": [
+ "db_schema",
+ "max_rows",
+ "db_extra_search_path",
+ "db_pool",
+ "db_pool_acquisition_timeout"
+ ]
}
}
}
@@ -14949,7 +14968,159 @@
},
"/v1/projects/{ref}/analytics/endpoints/logs.all": {
"get": {
- "description": "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer:~:text=logs%20from%20the-,Sources,-drop%2Ddown%3A) for all available sources. \n",
+ "deprecated": true,
+ "description": "Executes a SQL query on the project's logs.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nNote: Unless the `sql` parameter is provided, only edge_logs will be queried. See the [log query docs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer:~:text=logs%20from%20the-,Sources,-drop%2Ddown%3A) for all available sources.\n",
+ "operationId": "v1-get-project-logs-all",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ },
+ {
+ "name": "sql",
+ "required": false,
+ "in": "query",
+ "description": "Custom SQL query to execute on the logs. See [querying logs](/docs/guides/telemetry/logs?queryGroups=product&product=postgres&queryGroups=source&source=edge_logs#querying-with-the-logs-explorer) for more details.",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "iso_timestamp_start",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "format": "date-time",
+ "example": "2025-03-01T00:00:00Z",
+ "type": "string"
+ }
+ },
+ {
+ "name": "iso_timestamp_end",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "format": "date-time",
+ "example": "2025-03-01T23:59:59Z",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "result": {
+ "type": "array",
+ "items": {}
+ },
+ "error": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "number"
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "domain": {
+ "type": "string"
+ },
+ "location": {
+ "type": "string"
+ },
+ "locationType": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "reason": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "domain",
+ "location",
+ "locationType",
+ "message",
+ "reason"
+ ]
+ }
+ },
+ "message": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string"
+ }
+ },
+ "required": ["code", "errors", "message", "status"]
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "402": {
+ "description": "Usage exceeded. Enable additional usage to continue querying"
+ },
+ "403": {
+ "description": "Forbidden action"
+ },
+ "429": {
+ "description": "Rate limit exceeded"
+ }
+ },
+ "security": [
+ {
+ "bearer": []
+ },
+ {
+ "fga_permissions": ["analytics_logs_read"]
+ }
+ ],
+ "summary": "Gets project's logs",
+ "tags": ["Analytics"],
+ "x-badges": [
+ {
+ "name": "OAuth scope: analytics:read",
+ "position": "after"
+ }
+ ],
+ "x-endpoint-owners": ["analytics"],
+ "x-oauth-scope": "analytics:read"
+ }
+ },
+ "/v1/projects/{ref}/analytics/endpoints/logs": {
+ "get": {
+ "deprecated": false,
+ "description": "Executes an SQL or LQL query on the project's unified logs stream.\n\nEither the `iso_timestamp_start` and `iso_timestamp_end` parameters must be provided.\nIf both are not provided, only the last 1 minute of logs will be queried.\nThe timestamp range must be no more than 24 hours and is rounded to the nearest minute. If the range is more than 24 hours, a validation error will be thrown.\n\nFilter by the `source` column to specify specific log sources, such as edge_logs, postgres_logs, etc.\n\nNote: SQL must be written in **ClickHouse SQL dialect**.\n",
"operationId": "v1-get-project-logs",
"parameters": [
{
@@ -15067,6 +15238,9 @@
"401": {
"description": "Unauthorized"
},
+ "402": {
+ "description": "Usage exceeded. Enable additional usage to continue querying"
+ },
"403": {
"description": "Forbidden action"
},
@@ -15082,7 +15256,7 @@
"fga_permissions": ["analytics_logs_read"]
}
],
- "summary": "Gets project's logs",
+ "summary": "Gets all project's logs in a single log stream",
"tags": ["Analytics"],
"x-badges": [
{
@@ -16650,7 +16824,7 @@
}
}
},
- "required": ["user_id", "user_roles"]
+ "required": ["user_roles"]
}
}
}
@@ -16987,7 +17161,7 @@
}
}
},
- "required": ["user_id", "user_roles"]
+ "required": ["user_roles"]
}
}
}
@@ -17002,7 +17176,7 @@
"description": "Rate limit exceeded"
},
"500": {
- "description": "Failed to upsert database migration"
+ "description": "Failed to update JIT access"
}
},
"security": [
@@ -17048,62 +17222,155 @@
"items": {
"type": "array",
"items": {
- "type": "object",
- "properties": {
- "user_id": {
- "type": "string",
- "format": "uuid"
- },
- "user_roles": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "role": {
- "type": "string",
- "minLength": 1
- },
- "expires_at": {
- "type": "number"
- },
- "allowed_networks": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "primary_email": {
+ "type": "string",
+ "nullable": true
+ },
+ "invite_id": {
+ "type": "null"
+ },
+ "expires_at": {
+ "type": "null"
+ },
+ "user_roles": {
+ "type": "array",
+ "items": {
"type": "object",
"properties": {
- "allowed_cidrs": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "cidr": {
- "type": "string"
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
}
},
- "required": ["cidr"]
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
}
},
- "allowed_cidrs_v6": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "cidr": {
- "type": "string"
+ "branches_only": {
+ "type": "boolean"
+ }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": [
+ "user_id",
+ "primary_email",
+ "invite_id",
+ "expires_at",
+ "user_roles"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "null"
+ },
+ "primary_email": {
+ "type": "string"
+ },
+ "invite_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "expires_at": {
+ "type": "string"
+ },
+ "user_roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
}
},
- "required": ["cidr"]
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
}
+ },
+ "branches_only": {
+ "type": "boolean"
}
- }
- },
- "branches_only": {
- "type": "boolean"
+ },
+ "required": ["role"]
}
- },
- "required": ["role"]
- }
+ }
+ },
+ "required": [
+ "user_id",
+ "primary_email",
+ "invite_id",
+ "expires_at",
+ "user_roles"
+ ]
}
- },
- "required": ["user_id", "user_roles"]
+ ]
}
}
},
@@ -17138,6 +17405,388 @@
"x-endpoint-owners": ["security"]
}
},
+ "/v1/projects/{ref}/database/jit/invite": {
+ "post": {
+ "description": "Invites the external user and sets initial roles that can be assumed and for how long",
+ "operationId": "v1-invite-external-jit-access",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email",
+ "minLength": 1
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
+ }
+ },
+ "branches_only": {
+ "type": "boolean"
+ }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["email", "roles"],
+ "example": {
+ "email": "external-user@somedomain.xyz",
+ "roles": [
+ {
+ "role": "postgres",
+ "expires_at": 1740787200,
+ "allowed_networks": {
+ "allowed_cidrs": [
+ {
+ "cidr": "203.0.113.0/24"
+ }
+ ]
+ },
+ "branches_only": false
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email"
+ },
+ "invite_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "user_roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
+ }
+ },
+ "branches_only": {
+ "type": "boolean"
+ }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["email", "invite_id", "user_roles"]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden action"
+ },
+ "429": {
+ "description": "Rate limit exceeded"
+ },
+ "500": {
+ "description": "Failed to invite external user"
+ }
+ },
+ "security": [
+ {
+ "bearer": []
+ },
+ {
+ "fga_permissions": ["database_jit_write"]
+ }
+ ],
+ "summary": "Invites an external user to a database for JIT access",
+ "tags": ["Database"],
+ "x-endpoint-owners": ["security"]
+ }
+ },
+ "/v1/projects/{ref}/database/jit/invite/accept": {
+ "post": {
+ "description": "Accepts the invitation to JIT database access",
+ "operationId": "v1-accept-invite-external-jit-access",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email",
+ "minLength": 1
+ },
+ "token": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": ["email", "token"],
+ "example": {
+ "email": "external-user@somedomain.xyz",
+ "token": ""
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "user_roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
+ }
+ },
+ "branches_only": {
+ "type": "boolean"
+ }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["user_roles"]
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Failed to accept invitation"
+ }
+ },
+ "security": [
+ {
+ "bearer": []
+ }
+ ],
+ "summary": "Accepts invitation for JIT database access",
+ "tags": ["Database"],
+ "x-endpoint-owners": ["security"]
+ }
+ },
+ "/v1/projects/{ref}/database/jit/invite/{invite_id}": {
+ "delete": {
+ "description": "Revokes and deletes the invitation",
+ "operationId": "v1-delete-invite-external-jit-access",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ },
+ {
+ "name": "invite_id",
+ "required": true,
+ "in": "path",
+ "schema": {
+ "format": "uuid",
+ "example": "55555555-5555-4555-8555-555555555555",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": ""
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden action"
+ },
+ "429": {
+ "description": "Rate limit exceeded"
+ },
+ "500": {
+ "description": "Failed to revoke invite for external user"
+ }
+ },
+ "security": [
+ {
+ "bearer": []
+ },
+ {
+ "fga_permissions": ["database_jit_write"]
+ }
+ ],
+ "summary": "Deletes the invite for an external user to a database for JIT access",
+ "tags": ["Database"],
+ "x-endpoint-owners": ["security"]
+ }
+ },
"/v1/projects/{ref}/database/jit/{user_id}": {
"delete": {
"description": "Remove JIT mappings of a user, revoking all JIT database access",
@@ -18976,6 +19625,15 @@
},
"required": ["enabled"]
},
+ "purgeCache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": ["enabled"]
+ },
"icebergCatalog": {
"type": "object",
"properties": {
@@ -19018,6 +19676,7 @@
"required": [
"imageTransformation",
"s3Protocol",
+ "purgeCache",
"icebergCatalog",
"vectorBuckets"
]
@@ -19139,6 +19798,15 @@
},
"required": ["enabled"]
},
+ "purgeCache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": ["enabled"]
+ },
"icebergCatalog": {
"type": "object",
"properties": {
@@ -22188,6 +22856,7 @@
"storage.image_transformations",
"storage.vector_buckets",
"storage.iceberg_catalog",
+ "storage.purge_cache",
"security.audit_logs_days",
"security.questionnaire",
"security.soc2_report",
@@ -22236,7 +22905,9 @@
"assistant.advance_model",
"integrations.github_connections",
"dedicated_pooler",
- "observability.dashboard_advanced_metrics"
+ "observability.dashboard_advanced_metrics",
+ "api.members.invitations",
+ "api.members.roles"
]
},
"type": {
@@ -22379,9 +23050,13 @@
},
"mfa_enabled": {
"type": "boolean"
+ },
+ "avatar_url": {
+ "type": "string",
+ "nullable": true
}
},
- "required": ["user_id", "user_name", "role_name", "mfa_enabled"]
+ "required": ["user_id", "user_name", "role_name", "mfa_enabled", "avatar_url"]
}
}
}
@@ -24616,11 +25291,7 @@
},
"unavailableReason": {
"type": "string",
- "enum": [
- "manual_migration_required",
- "postgres_upgrade_required",
- "temporarily_unavailable"
- ]
+ "enum": ["postgres_upgrade_required", "temporarily_unavailable"]
}
},
"required": ["state", "unavailableReason"]
@@ -24935,11 +25606,22 @@
"nullable": true,
"description": "If `null`, the value is automatically configured based on compute size."
},
+ "db_pool_acquisition_timeout": {
+ "type": "integer",
+ "nullable": true,
+ "description": "If `null`, the value is automatically configured to 10."
+ },
"jwt_secret": {
"type": "string"
}
},
- "required": ["db_schema", "max_rows", "db_extra_search_path", "db_pool"]
+ "required": [
+ "db_schema",
+ "max_rows",
+ "db_extra_search_path",
+ "db_pool",
+ "db_pool_acquisition_timeout"
+ ]
},
"V1UpdatePostgrestConfigBody": {
"type": "object",
@@ -24959,6 +25641,11 @@
"type": "integer",
"minimum": 0,
"maximum": 1000
+ },
+ "db_pool_acquisition_timeout": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 60
}
},
"example": {
@@ -24983,9 +25670,20 @@
"type": "integer",
"nullable": true,
"description": "If `null`, the value is automatically configured based on compute size."
+ },
+ "db_pool_acquisition_timeout": {
+ "type": "integer",
+ "nullable": true,
+ "description": "If `null`, the value is automatically configured to 10."
}
},
- "required": ["db_schema", "max_rows", "db_extra_search_path", "db_pool"]
+ "required": [
+ "db_schema",
+ "max_rows",
+ "db_extra_search_path",
+ "db_pool",
+ "db_pool_acquisition_timeout"
+ ]
},
"V1ProjectRefResponse": {
"type": "object",
@@ -29191,7 +29889,7 @@
}
}
},
- "required": ["user_id", "user_roles"]
+ "required": ["user_roles"]
},
"AuthorizeJitAccessBody": {
"type": "object",
@@ -29272,62 +29970,143 @@
"items": {
"type": "array",
"items": {
- "type": "object",
- "properties": {
- "user_id": {
- "type": "string",
- "format": "uuid"
- },
- "user_roles": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "role": {
- "type": "string",
- "minLength": 1
- },
- "expires_at": {
- "type": "number"
- },
- "allowed_networks": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "primary_email": {
+ "type": "string",
+ "nullable": true
+ },
+ "invite_id": {
+ "type": "null"
+ },
+ "expires_at": {
+ "type": "null"
+ },
+ "user_roles": {
+ "type": "array",
+ "items": {
"type": "object",
"properties": {
- "allowed_cidrs": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "cidr": {
- "type": "string"
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
}
},
- "required": ["cidr"]
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
}
},
- "allowed_cidrs_v6": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "cidr": {
- "type": "string"
+ "branches_only": {
+ "type": "boolean"
+ }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["user_id", "primary_email", "invite_id", "expires_at", "user_roles"]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "null"
+ },
+ "primary_email": {
+ "type": "string"
+ },
+ "invite_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "expires_at": {
+ "type": "string"
+ },
+ "user_roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
}
},
- "required": ["cidr"]
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
}
+ },
+ "branches_only": {
+ "type": "boolean"
}
- }
- },
- "branches_only": {
- "type": "boolean"
+ },
+ "required": ["role"]
}
- },
- "required": ["role"]
- }
+ }
+ },
+ "required": ["user_id", "primary_email", "invite_id", "expires_at", "user_roles"]
}
- },
- "required": ["user_id", "user_roles"]
+ ]
}
}
},
@@ -29409,6 +30188,163 @@
]
}
},
+ "InviteExternalUserJitAccessBody": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email",
+ "minLength": 1
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
+ }
+ },
+ "branches_only": {
+ "type": "boolean"
+ }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["email", "roles"],
+ "example": {
+ "email": "external-user@somedomain.xyz",
+ "roles": [
+ {
+ "role": "postgres",
+ "expires_at": 1740787200,
+ "allowed_networks": {
+ "allowed_cidrs": [
+ {
+ "cidr": "203.0.113.0/24"
+ }
+ ]
+ },
+ "branches_only": false
+ }
+ ]
+ }
+ },
+ "InviteExternalUserJitResponse": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email"
+ },
+ "invite_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "user_roles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "minLength": 1
+ },
+ "expires_at": {
+ "type": "number"
+ },
+ "allowed_networks": {
+ "type": "object",
+ "properties": {
+ "allowed_cidrs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ },
+ "allowed_cidrs_v6": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "cidr": {
+ "type": "string"
+ }
+ },
+ "required": ["cidr"]
+ }
+ }
+ }
+ },
+ "branches_only": {
+ "type": "boolean"
+ }
+ },
+ "required": ["role"]
+ }
+ }
+ },
+ "required": ["email", "invite_id", "user_roles"]
+ },
+ "AcceptInviteExternalUserJitAccessBody": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email",
+ "minLength": 1
+ },
+ "token": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required": ["email", "token"],
+ "example": {
+ "email": "external-user@somedomain.xyz",
+ "token": ""
+ }
+ },
"FunctionResponse": {
"type": "object",
"properties": {
@@ -29974,6 +30910,15 @@
},
"required": ["enabled"]
},
+ "purgeCache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": ["enabled"]
+ },
"icebergCatalog": {
"type": "object",
"properties": {
@@ -30013,7 +30958,13 @@
"required": ["enabled", "maxBuckets", "maxIndexes"]
}
},
- "required": ["imageTransformation", "s3Protocol", "icebergCatalog", "vectorBuckets"]
+ "required": [
+ "imageTransformation",
+ "s3Protocol",
+ "purgeCache",
+ "icebergCatalog",
+ "vectorBuckets"
+ ]
},
"capabilities": {
"type": "object",
@@ -30083,6 +31034,15 @@
},
"required": ["enabled"]
},
+ "purgeCache": {
+ "type": "object",
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ }
+ },
+ "required": ["enabled"]
+ },
"icebergCatalog": {
"type": "object",
"properties": {
@@ -31590,6 +32550,7 @@
"storage.image_transformations",
"storage.vector_buckets",
"storage.iceberg_catalog",
+ "storage.purge_cache",
"security.audit_logs_days",
"security.questionnaire",
"security.soc2_report",
@@ -31638,7 +32599,9 @@
"assistant.advance_model",
"integrations.github_connections",
"dedicated_pooler",
- "observability.dashboard_advanced_metrics"
+ "observability.dashboard_advanced_metrics",
+ "api.members.invitations",
+ "api.members.roles"
]
},
"type": {
@@ -31725,9 +32688,13 @@
},
"mfa_enabled": {
"type": "boolean"
+ },
+ "avatar_url": {
+ "type": "string",
+ "nullable": true
}
},
- "required": ["user_id", "user_name", "role_name", "mfa_enabled"]
+ "required": ["user_id", "user_name", "role_name", "mfa_enabled", "avatar_url"]
},
"V1OrganizationSlugResponse": {
"type": "object",
diff --git a/apps/docs/spec/transforms/api_v2_openapi_deparsed.json b/apps/docs/spec/transforms/api_v2_openapi_deparsed.json
index 45e90da5d75..7b21a0596db 100644
--- a/apps/docs/spec/transforms/api_v2_openapi_deparsed.json
+++ b/apps/docs/spec/transforms/api_v2_openapi_deparsed.json
@@ -1582,6 +1582,332 @@
"x-endpoint-owners": ["management-api"]
}
},
+ "/v2/projects/{ref}/private-link/associations": {
+ "get": {
+ "operationId": "v2-list-private-link-associations",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "id": {
+ "type": "string"
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID this PrivateLink share is associated with."
+ },
+ "account_name": {
+ "type": "string",
+ "description": "Human-readable name for the AWS account."
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "CREATING",
+ "READY",
+ "ASSOCIATION_REQUEST_EXPIRED",
+ "ASSOCIATION_ACCEPTED",
+ "CREATION_FAILED",
+ "DELETING"
+ ],
+ "description": "\n - `CREATING`: The PrivateLink resources and the Association are in the process of being created. The PrivateLink Share cannot be accepted yet.\n - `READY`: The PrivateLink resources have been created and the PrivateLink Share can be accepted for the duration of 12h after sharing. See `shared_at`.\n - `ASSOCIATION_REQUEST_EXPIRED`: The PrivateLink Share has not been accepted within the 12h time limit. This association can now be deleted.\n - `ASSOCIATION_ACCEPTED`: The PrivateLink Share was successfully accepted.\n - `CREATION_FAILED`: The PrivateLink resources failed to create. This likely means something went wrong on Supabase's and and support should be contacted.\n - `DELETING`: The PrivateLink resources and the Association are in the process of being deleted. The PrivateLink Share cannot be accepted yet.\n"
+ },
+ "shared_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "The time and date at which the AWS Resource Share Association was requested from Supabase. `null` means that the association was not yet requested while the PrivateLink Association is pending."
+ }
+ },
+ "required": ["aws_account_id", "status", "shared_at"]
+ }
+ },
+ "required": ["type", "id", "attributes"]
+ }
+ }
+ },
+ "required": ["data"]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden action"
+ },
+ "429": {
+ "description": "Rate limit exceeded"
+ },
+ "500": {
+ "description": "Failed to retrieve AWS accounts for project"
+ }
+ },
+ "security": [
+ {
+ "bearer": []
+ },
+ {
+ "fga_permissions": ["project_admin_read"]
+ }
+ ],
+ "summary": "List AWS accounts attached to the project PrivateLink share",
+ "tags": ["Projects"],
+ "x-endpoint-owners": ["platform-networking", "management-api"]
+ },
+ "post": {
+ "description": "Adds an AWS account to the project's PrivateLink configuration and schedules the AWS resources to be created.",
+ "operationId": "v2-create-private-link-association",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID to add to the project PrivateLink share."
+ },
+ "account_name": {
+ "type": "string",
+ "maxLength": 128,
+ "description": "Optional human-readable name for the AWS account."
+ }
+ },
+ "required": ["aws_account_id"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ },
+ "required": ["data"]
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "id": {
+ "type": "string"
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID this PrivateLink share is associated with."
+ },
+ "account_name": {
+ "type": "string",
+ "description": "Human-readable name for the AWS account."
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "CREATING",
+ "READY",
+ "ASSOCIATION_REQUEST_EXPIRED",
+ "ASSOCIATION_ACCEPTED",
+ "CREATION_FAILED",
+ "DELETING"
+ ],
+ "description": "\n - `CREATING`: The PrivateLink resources and the Association are in the process of being created. The PrivateLink Share cannot be accepted yet.\n - `READY`: The PrivateLink resources have been created and the PrivateLink Share can be accepted for the duration of 12h after sharing. See `shared_at`.\n - `ASSOCIATION_REQUEST_EXPIRED`: The PrivateLink Share has not been accepted within the 12h time limit. This association can now be deleted.\n - `ASSOCIATION_ACCEPTED`: The PrivateLink Share was successfully accepted.\n - `CREATION_FAILED`: The PrivateLink resources failed to create. This likely means something went wrong on Supabase's and and support should be contacted.\n - `DELETING`: The PrivateLink resources and the Association are in the process of being deleted. The PrivateLink Share cannot be accepted yet.\n"
+ },
+ "shared_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "The time and date at which the AWS Resource Share Association was requested from Supabase. `null` means that the association was not yet requested while the PrivateLink Association is pending."
+ }
+ },
+ "required": ["aws_account_id", "status", "shared_at"]
+ }
+ },
+ "required": ["type", "id", "attributes"]
+ }
+ },
+ "required": ["data"]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "402": {
+ "description": "This feature requires the Team, or Enterprise organization plan."
+ },
+ "403": {
+ "description": "Forbidden action"
+ },
+ "429": {
+ "description": "Rate limit exceeded"
+ },
+ "500": {
+ "description": "Failed to add AWS account to PrivateLink share"
+ }
+ },
+ "security": [
+ {
+ "bearer": []
+ },
+ {
+ "fga_permissions": ["project_admin_write"]
+ }
+ ],
+ "summary": "Add an AWS account to the project PrivateLink share",
+ "tags": ["Projects"],
+ "x-allowed-plans": ["Team", "Enterprise"],
+ "x-badges": [
+ {
+ "name": "Only available on Team, Enterprise",
+ "position": "before"
+ }
+ ],
+ "x-endpoint-owners": ["platform-networking", "management-api"]
+ }
+ },
+ "/v2/projects/{ref}/private-link/associations/aws-account/{aws_account_id}": {
+ "delete": {
+ "description": "Removes an AWS account from the project's PrivateLink configuration. Cleans up the associated AWS resources.",
+ "operationId": "v2-delete-private-link-association",
+ "parameters": [
+ {
+ "name": "ref",
+ "required": true,
+ "in": "path",
+ "description": "Project ref",
+ "schema": {
+ "minLength": 20,
+ "maxLength": 20,
+ "pattern": "^[a-z]+$",
+ "example": "abcdefghijklmnopqrst",
+ "type": "string"
+ }
+ },
+ {
+ "name": "aws_account_id",
+ "required": false,
+ "in": "path",
+ "description": "AWS account ID used in PrivateLink association",
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": ""
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden action"
+ },
+ "429": {
+ "description": "Rate limit exceeded"
+ },
+ "500": {
+ "description": "Failed to remove AWS account from PrivateLink share"
+ }
+ },
+ "security": [
+ {
+ "bearer": []
+ },
+ {
+ "fga_permissions": ["project_admin_write"]
+ }
+ ],
+ "summary": "Remove an AWS account from the project PrivateLink share",
+ "tags": ["Projects"],
+ "x-endpoint-owners": ["platform-networking", "management-api"]
+ }
+ },
"/v2/organizations/{slug}/members": {
"get": {
"description": "Returns a cursor-paginated list of organization members including their roles and project-scoped permissions.",
@@ -1687,19 +2013,20 @@
"type": "boolean",
"description": "Whether this member is a Single Sign-On user"
},
+ "avatar_url": {
+ "type": "string",
+ "nullable": true,
+ "description": "Member's avatar URL"
+ },
"roles": {
"type": "array",
"items": {
"type": "object",
"properties": {
- "id": {
- "type": "integer",
- "description": "Role ID."
- },
"name": {
"type": "string",
"description": "Role name. For project-scoped roles this is the base role name.",
- "example": "Developer"
+ "example": "developer"
},
"scope": {
"type": "string",
@@ -1723,7 +2050,7 @@
"description": "Project refs this role is scoped to. Empty array for org-level roles."
}
},
- "required": ["id", "name", "scope", "projects"]
+ "required": ["name", "scope", "projects"]
},
"description": "Roles assigned to this member. Includes both org-level and project-scoped roles."
}
@@ -1733,6 +2060,7 @@
"primary_email",
"mfa_enabled",
"is_sso_user",
+ "avatar_url",
"roles"
]
}
@@ -1806,10 +2134,10 @@
"x-oauth-scope": "organizations:read"
}
},
- "/v2/organizations/{slug}/roles": {
- "get": {
- "description": "Returns a cursor-paginated list of organization roles including both org-scoped and project-scoped roles.",
- "operationId": "v2-list-organization-roles",
+ "/v2/organizations/{slug}/members/{user_id}/roles": {
+ "patch": {
+ "description": "Assigns an org-wide role when projects is omitted, or creates a project-scoped assignment when projects is provided. Uses an org-level role template id from GET /v2/organizations/{slug}/roles. Stale role assignments are automatically cleaned up: if a role no longer has any projects, it is deleted; overlapping project assignments in other roles are automatically removed to avoid duplication.",
+ "operationId": "v2-assign-organization-member-role",
"parameters": [
{
"name": "slug",
@@ -1823,42 +2151,174 @@
}
},
{
- "name": "page",
- "required": false,
- "in": "query",
+ "name": "user_id",
+ "required": true,
+ "in": "path",
"schema": {
- "properties": {
- "size": {
- "type": "integer",
- "minimum": 1,
- "maximum": 100,
- "required": false
+ "format": "uuid",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_member_role"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "enum": ["owner", "administrator", "developer", "read-only"],
+ "description": "Role name to assign. Must be one of: owner, administrator, developer, read-only. Must be on a Team or Enterprise plan to use the read-only role.",
+ "example": "developer"
+ },
+ "projects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "ref": {
+ "type": "string",
+ "description": "Project ref",
+ "example": "abcjuqabhgwjjutfvtpa"
+ }
+ },
+ "required": ["ref"]
+ },
+ "minItems": 1,
+ "description": "The projects to assign a project-scoped role for. If omitted, assigns an org-wide role."
+ }
+ },
+ "required": ["role"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
},
- "after": {
- "type": "string",
- "required": false
- },
- "before": {
- "type": "string",
- "required": false
+ "required": ["data"]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_member_role"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Role name. For project-scoped assignments this is the base role name.",
+ "example": "developer"
+ },
+ "scope": {
+ "type": "string",
+ "enum": ["organization", "project"],
+ "description": "Whether this role applies org-wide or is scoped to specific projects for the user."
+ },
+ "projects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "ref": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["ref", "name"]
+ },
+ "description": "Project refs this role is scoped to. Empty array for org-level roles."
+ }
+ },
+ "required": ["name", "scope", "projects"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ },
+ "required": ["data"]
}
- },
- "type": "object"
+ }
}
},
+ "401": {
+ "description": "Unauthorized"
+ },
+ "402": {
+ "description": "This feature requires the Enterprise organization plan."
+ },
+ "403": {
+ "description": "Forbidden action"
+ },
+ "429": {
+ "description": "Rate limit exceeded"
+ },
+ "500": {
+ "description": "Failed to assign organization member role"
+ }
+ },
+ "security": [
{
- "name": "filter",
- "required": false,
- "in": "query",
+ "bearer": []
+ },
+ {
+ "fga_permissions": ["organization_admin_write"]
+ }
+ ],
+ "summary": "Assign or change an organization member role",
+ "tags": ["Organizations"],
+ "x-allowed-plans": ["Enterprise"],
+ "x-badges": [
+ {
+ "name": "Only available on Enterprise",
+ "position": "before"
+ }
+ ],
+ "x-endpoint-owners": ["management-api"]
+ }
+ },
+ "/v2/organizations/{slug}/roles": {
+ "get": {
+ "description": "Returns a list of org-level roles for the organization.",
+ "operationId": "v2-list-organization-roles",
+ "parameters": [
+ {
+ "name": "slug",
+ "required": true,
+ "in": "path",
+ "description": "Organization slug",
"schema": {
- "properties": {
- "scope": {
- "type": "string",
- "enum": ["organization", "project"],
- "required": false
- }
- },
- "type": "object"
+ "pattern": "^[\\w-]+$",
+ "example": "tsrqponmlkjihgfedcba",
+ "type": "string"
}
}
],
@@ -1880,82 +2340,24 @@
"enum": ["organization_role"],
"description": "Resource type."
},
- "id": {
- "type": "string"
- },
+ "id": {},
"attributes": {
"type": "object",
"properties": {
"name": {
"type": "string",
- "description": "Role name. For project-scoped roles this is the base role name.",
- "example": "Developer"
- },
- "scope": {
- "type": "string",
- "enum": ["organization", "project"],
- "description": "Whether this role applies org-wide or is scoped to specific projects for the user."
- },
- "base_role_id": {
- "type": "integer",
- "nullable": true,
- "description": "null for org-level roles; the base role ID for project-scoped roles."
- },
- "projects": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "ref": {
- "type": "string"
- },
- "name": {
- "type": "string"
- }
- },
- "required": ["ref", "name"]
- },
- "description": "Project refs this role is scoped to. Empty array for org-level roles."
+ "description": "Role name.",
+ "example": "developer"
}
},
- "required": ["name", "scope", "base_role_id", "projects"]
+ "required": ["name"]
}
},
- "required": ["type", "id", "attributes"]
+ "required": ["type", "attributes"]
}
- },
- "links": {
- "type": "object",
- "properties": {
- "first": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the first page if available.",
- "example": "/v2/organizations/my-org/roles?page[size]=10"
- },
- "prev": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the previous page.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[before]=019adf7d-4513-74c5-bb9a-f1bc0f7a95d7"
- },
- "next": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the next page.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[after]=019adf7d-4513-7062-b292-78b86cc470a4"
- },
- "last": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the last page if available.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[after]=019adf7d-4513-71ba-b264-21900edb4295"
- }
- },
- "required": ["prev", "next"]
}
},
- "required": ["data", "links"]
+ "required": ["data"]
}
}
}
@@ -1989,6 +2391,273 @@
"x-endpoint-owners": ["management-api"],
"x-oauth-scope": "organizations:read"
}
+ },
+ "/v2/organizations/{slug}/members/invitations": {
+ "post": {
+ "description": "Creates member invitations for an organization. Each invitation can have different role and project scope settings.",
+ "operationId": "v2-create-organization-invitations",
+ "parameters": [
+ {
+ "name": "slug",
+ "required": true,
+ "in": "path",
+ "description": "Organization slug",
+ "schema": {
+ "pattern": "^[\\w-]+$",
+ "example": "tsrqponmlkjihgfedcba",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_invitation"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email"
+ },
+ "role": {
+ "type": "string",
+ "enum": ["owner", "administrator", "developer", "read-only"],
+ "description": "Role name to assign. Must be on a Team or Enterprise plan to use the read-only role.",
+ "example": "developer"
+ },
+ "projects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "ref": {
+ "type": "string",
+ "description": "Project ref",
+ "example": "abcjuqabhgwjjutfvtpa"
+ }
+ },
+ "required": ["ref"]
+ },
+ "minItems": 1,
+ "description": "The projects to limit a user to. If omitted, user will have org-wide access with the provided role."
+ },
+ "require_sso": {
+ "type": "boolean"
+ }
+ },
+ "required": ["email", "role"]
+ }
+ },
+ "required": ["type", "attributes"]
+ },
+ "minItems": 1,
+ "maxItems": 50
+ }
+ },
+ "required": ["data"]
+ }
+ }
+ }
+ },
+ "responses": {
+ "201": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "error": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "code": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "links": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "href": {
+ "type": "string"
+ },
+ "rel": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ },
+ "describedby": {
+ "type": "string"
+ },
+ "meta": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": ["href"]
+ }
+ },
+ "meta": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "issues": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "code": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "links": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "href": {
+ "type": "string"
+ },
+ "rel": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ },
+ "describedby": {
+ "type": "string"
+ },
+ "meta": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": ["href"]
+ }
+ },
+ "meta": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email"
+ }
+ },
+ "required": ["email"]
+ }
+ },
+ "required": ["code", "message", "meta"]
+ }
+ }
+ },
+ "required": ["code", "message"]
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_invitation"],
+ "description": "Resource type."
+ },
+ "id": {},
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email"
+ }
+ },
+ "required": ["email"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ }
+ },
+ "required": ["data"]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "402": {
+ "description": "This feature requires the Enterprise organization plan."
+ },
+ "403": {
+ "description": "Forbidden action"
+ },
+ "429": {
+ "description": "Rate limit exceeded"
+ }
+ },
+ "security": [
+ {
+ "bearer": []
+ },
+ {
+ "fga_permissions": ["members_write"]
+ }
+ ],
+ "summary": "Creates organization invitations",
+ "tags": ["Organizations Members Invitations"],
+ "x-allowed-plans": ["Enterprise"],
+ "x-badges": [
+ {
+ "name": "OAuth scope: organizations:write",
+ "position": "after"
+ },
+ {
+ "name": "Only available on Enterprise",
+ "position": "before"
+ }
+ ],
+ "x-endpoint-owners": ["management-api"],
+ "x-oauth-scope": "organizations:write"
+ }
}
},
"components": {
@@ -2954,6 +3623,154 @@
},
"required": ["data"]
},
+ "V2ListPrivateLinkAssociationsResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "id": {
+ "type": "string"
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID this PrivateLink share is associated with."
+ },
+ "account_name": {
+ "type": "string",
+ "description": "Human-readable name for the AWS account."
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "CREATING",
+ "READY",
+ "ASSOCIATION_REQUEST_EXPIRED",
+ "ASSOCIATION_ACCEPTED",
+ "CREATION_FAILED",
+ "DELETING"
+ ],
+ "description": "\n - `CREATING`: The PrivateLink resources and the Association are in the process of being created. The PrivateLink Share cannot be accepted yet.\n - `READY`: The PrivateLink resources have been created and the PrivateLink Share can be accepted for the duration of 12h after sharing. See `shared_at`.\n - `ASSOCIATION_REQUEST_EXPIRED`: The PrivateLink Share has not been accepted within the 12h time limit. This association can now be deleted.\n - `ASSOCIATION_ACCEPTED`: The PrivateLink Share was successfully accepted.\n - `CREATION_FAILED`: The PrivateLink resources failed to create. This likely means something went wrong on Supabase's and and support should be contacted.\n - `DELETING`: The PrivateLink resources and the Association are in the process of being deleted. The PrivateLink Share cannot be accepted yet.\n"
+ },
+ "shared_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "The time and date at which the AWS Resource Share Association was requested from Supabase. `null` means that the association was not yet requested while the PrivateLink Association is pending."
+ }
+ },
+ "required": ["aws_account_id", "status", "shared_at"]
+ }
+ },
+ "required": ["type", "id", "attributes"]
+ }
+ }
+ },
+ "required": ["data"]
+ },
+ "V2CreatePrivateLinkAssociationRequest": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID to add to the project PrivateLink share."
+ },
+ "account_name": {
+ "type": "string",
+ "maxLength": 128,
+ "description": "Optional human-readable name for the AWS account."
+ }
+ },
+ "required": ["aws_account_id"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ },
+ "required": ["data"]
+ },
+ "V2PrivateLinkAssociationResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["private_link_association"],
+ "description": "Resource type."
+ },
+ "id": {
+ "type": "string"
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "aws_account_id": {
+ "type": "string",
+ "minLength": 12,
+ "maxLength": 12,
+ "pattern": "^\\d{12}$",
+ "description": "The AWS account ID this PrivateLink share is associated with."
+ },
+ "account_name": {
+ "type": "string",
+ "description": "Human-readable name for the AWS account."
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "CREATING",
+ "READY",
+ "ASSOCIATION_REQUEST_EXPIRED",
+ "ASSOCIATION_ACCEPTED",
+ "CREATION_FAILED",
+ "DELETING"
+ ],
+ "description": "\n - `CREATING`: The PrivateLink resources and the Association are in the process of being created. The PrivateLink Share cannot be accepted yet.\n - `READY`: The PrivateLink resources have been created and the PrivateLink Share can be accepted for the duration of 12h after sharing. See `shared_at`.\n - `ASSOCIATION_REQUEST_EXPIRED`: The PrivateLink Share has not been accepted within the 12h time limit. This association can now be deleted.\n - `ASSOCIATION_ACCEPTED`: The PrivateLink Share was successfully accepted.\n - `CREATION_FAILED`: The PrivateLink resources failed to create. This likely means something went wrong on Supabase's and and support should be contacted.\n - `DELETING`: The PrivateLink resources and the Association are in the process of being deleted. The PrivateLink Share cannot be accepted yet.\n"
+ },
+ "shared_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "The time and date at which the AWS Resource Share Association was requested from Supabase. `null` means that the association was not yet requested while the PrivateLink Association is pending."
+ }
+ },
+ "required": ["aws_account_id", "status", "shared_at"]
+ }
+ },
+ "required": ["type", "id", "attributes"]
+ }
+ },
+ "required": ["data"]
+ },
"V2ListMembersResponse": {
"type": "object",
"properties": {
@@ -2992,19 +3809,20 @@
"type": "boolean",
"description": "Whether this member is a Single Sign-On user"
},
+ "avatar_url": {
+ "type": "string",
+ "nullable": true,
+ "description": "Member's avatar URL"
+ },
"roles": {
"type": "array",
"items": {
"type": "object",
"properties": {
- "id": {
- "type": "integer",
- "description": "Role ID."
- },
"name": {
"type": "string",
"description": "Role name. For project-scoped roles this is the base role name.",
- "example": "Developer"
+ "example": "developer"
},
"scope": {
"type": "string",
@@ -3028,12 +3846,19 @@
"description": "Project refs this role is scoped to. Empty array for org-level roles."
}
},
- "required": ["id", "name", "scope", "projects"]
+ "required": ["name", "scope", "projects"]
},
"description": "Roles assigned to this member. Includes both org-level and project-scoped roles."
}
},
- "required": ["username", "primary_email", "mfa_enabled", "is_sso_user", "roles"]
+ "required": [
+ "username",
+ "primary_email",
+ "mfa_enabled",
+ "is_sso_user",
+ "avatar_url",
+ "roles"
+ ]
}
},
"required": ["type", "id", "attributes"]
@@ -3072,6 +3897,100 @@
},
"required": ["data", "links"]
},
+ "V2AssignOrganizationMemberRoleRequest": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_member_role"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "role": {
+ "type": "string",
+ "enum": ["owner", "administrator", "developer", "read-only"],
+ "description": "Role name to assign. Must be one of: owner, administrator, developer, read-only. Must be on a Team or Enterprise plan to use the read-only role.",
+ "example": "developer"
+ },
+ "projects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "ref": {
+ "type": "string",
+ "description": "Project ref",
+ "example": "abcjuqabhgwjjutfvtpa"
+ }
+ },
+ "required": ["ref"]
+ },
+ "minItems": 1,
+ "description": "The projects to assign a project-scoped role for. If omitted, assigns an org-wide role."
+ }
+ },
+ "required": ["role"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ },
+ "required": ["data"]
+ },
+ "OrganizationMemberRoleResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_member_role"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Role name. For project-scoped assignments this is the base role name.",
+ "example": "developer"
+ },
+ "scope": {
+ "type": "string",
+ "enum": ["organization", "project"],
+ "description": "Whether this role applies org-wide or is scoped to specific projects for the user."
+ },
+ "projects": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "ref": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["ref", "name"]
+ },
+ "description": "Project refs this role is scoped to. Empty array for org-level roles."
+ }
+ },
+ "required": ["name", "scope", "projects"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ },
+ "required": ["data"]
+ },
"V2ListRolesResponse": {
"type": "object",
"properties": {
@@ -3085,26 +4004,50 @@
"enum": ["organization_role"],
"description": "Resource type."
},
- "id": {
- "type": "string"
- },
+ "id": {},
"attributes": {
"type": "object",
"properties": {
"name": {
"type": "string",
- "description": "Role name. For project-scoped roles this is the base role name.",
- "example": "Developer"
- },
- "scope": {
+ "description": "Role name.",
+ "example": "developer"
+ }
+ },
+ "required": ["name"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ }
+ },
+ "required": ["data"]
+ },
+ "V2CreateInvitationsRequest": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_invitation"],
+ "description": "Resource type."
+ },
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "email": {
"type": "string",
- "enum": ["organization", "project"],
- "description": "Whether this role applies org-wide or is scoped to specific projects for the user."
+ "format": "email"
},
- "base_role_id": {
- "type": "integer",
- "nullable": true,
- "description": "null for org-level roles; the base role ID for project-scoped roles."
+ "role": {
+ "type": "string",
+ "enum": ["owner", "administrator", "developer", "read-only"],
+ "description": "Role name to assign. Must be on a Team or Enterprise plan to use the read-only role.",
+ "example": "developer"
},
"projects": {
"type": "array",
@@ -3112,55 +4055,170 @@
"type": "object",
"properties": {
"ref": {
- "type": "string"
- },
- "name": {
- "type": "string"
+ "type": "string",
+ "description": "Project ref",
+ "example": "abcjuqabhgwjjutfvtpa"
}
},
- "required": ["ref", "name"]
+ "required": ["ref"]
},
- "description": "Project refs this role is scoped to. Empty array for org-level roles."
+ "minItems": 1,
+ "description": "The projects to limit a user to. If omitted, user will have org-wide access with the provided role."
+ },
+ "require_sso": {
+ "type": "boolean"
}
},
- "required": ["name", "scope", "base_role_id", "projects"]
+ "required": ["email", "role"]
}
},
- "required": ["type", "id", "attributes"]
- }
- },
- "links": {
- "type": "object",
- "properties": {
- "first": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the first page if available.",
- "example": "/v2/organizations/my-org/roles?page[size]=10"
- },
- "prev": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the previous page.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[before]=019adf7d-4513-74c5-bb9a-f1bc0f7a95d7"
- },
- "next": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the next page.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[after]=019adf7d-4513-7062-b292-78b86cc470a4"
- },
- "last": {
- "type": "string",
- "nullable": true,
- "description": "URL path to the last page if available.",
- "example": "/v2/organizations/my-org/roles?page[size]=10&page[after]=019adf7d-4513-71ba-b264-21900edb4295"
- }
+ "required": ["type", "attributes"]
},
- "required": ["prev", "next"]
+ "minItems": 1,
+ "maxItems": 50
}
},
- "required": ["data", "links"]
+ "required": ["data"]
+ },
+ "V2CreateInvitationsResponse": {
+ "type": "object",
+ "properties": {
+ "error": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "code": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "links": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "href": {
+ "type": "string"
+ },
+ "rel": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ },
+ "describedby": {
+ "type": "string"
+ },
+ "meta": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": ["href"]
+ }
+ },
+ "meta": {
+ "type": "object",
+ "additionalProperties": {}
+ },
+ "issues": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "code": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "links": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "href": {
+ "type": "string"
+ },
+ "rel": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ },
+ "describedby": {
+ "type": "string"
+ },
+ "meta": {
+ "type": "object",
+ "additionalProperties": {}
+ }
+ },
+ "required": ["href"]
+ }
+ },
+ "meta": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email"
+ }
+ },
+ "required": ["email"]
+ }
+ },
+ "required": ["code", "message", "meta"]
+ }
+ }
+ },
+ "required": ["code", "message"]
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["organization_invitation"],
+ "description": "Resource type."
+ },
+ "id": {},
+ "attributes": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "format": "email"
+ }
+ },
+ "required": ["email"]
+ }
+ },
+ "required": ["type", "attributes"]
+ }
+ }
+ },
+ "required": ["data"]
}
}
}
diff --git a/apps/docs/styles/globals.css b/apps/docs/styles/globals.css
index 47b3026db95..4064aaa2199 100644
--- a/apps/docs/styles/globals.css
+++ b/apps/docs/styles/globals.css
@@ -11,6 +11,29 @@
@source './../../../packages/ui/src/**/*.{tsx,ts,js}';
@source './../../../packages/ui-patterns/src/**/*.{tsx,ts,js}';
+@theme inline {
+ --font-sans:
+ var(--font-inter), Inter, Helvetica Neue, Helvetica, ui-sans-serif, system-ui, sans-serif;
+ --font-heading: var(--font-manrope, var(--font-sans));
+ --font-mono: 'Source Code Pro', ui-monospace, Menlo, monospace;
+}
+
+@theme {
+ --text-sm: 0.8125rem;
+ --text-base: 0.9375rem;
+ --text-lg: 1rem;
+ --text-xl: 1.125rem;
+ --text-2xl: 1.375rem;
+ --text-3xl: 1.75rem;
+ --text-4xl: 2.125rem;
+ --text-5xl: 2.875rem;
+ --text-6xl: 3.625rem;
+ --text-7xl: 4.375rem;
+ --text-8xl: 5.875rem;
+ --text-9xl: 7.875rem;
+ --font-weight-normal: 450;
+}
+
@layer utilities {
.prose--remove-p-margin p {
margin: 0;
@@ -23,28 +46,30 @@
::before,
::backdrop,
::file-selector-button {
- border-color: hsl(var(--border-default, currentColor));
+ border-color: var(--border-default, currentColor);
}
-}
-@font-face {
- font-family: 'custom-font';
- src:
- url(../../../packages/common/assets/fonts/CustomFont-Book.woff2) format('woff2'),
- url(../../../packages/common/assets/fonts/CustomFont-Book.woff) format('woff');
- font-weight: 400;
- font-style: normal;
- font-display: swap;
-}
-
-@font-face {
- font-family: 'custom-font';
- src:
- url(../../../packages/common/assets/fonts/CustomFont-Medium.woff2) format('woff2'),
- url(../../../packages/common/assets/fonts/CustomFont-Medium.woff) format('woff');
- font-weight: 500;
- font-style: normal;
- font-display: swap;
+ code,
+ .code-content,
+ pre,
+ kbd,
+ samp,
+ .font-mono {
+ --text-xs: 0.75rem;
+ --text-sm: 0.875rem;
+ --text-base: 1rem;
+ --text-lg: 1.125rem;
+ --text-xl: 1.25rem;
+ --text-2xl: 1.5rem;
+ --text-3xl: 1.875rem;
+ --text-4xl: 2.25rem;
+ --text-5xl: 3rem;
+ --text-6xl: 3.75rem;
+ --text-7xl: 4.5rem;
+ --text-8xl: 6rem;
+ --text-9xl: 8rem;
+ --font-weight-normal: 400;
+ }
}
:root {
@@ -52,9 +77,17 @@
--header-height: 50px;
}
+html {
+ scroll-behavior: smooth;
+ font-weight: 500;
+}
+
+html[data-theme*='dark'] {
+ font-weight: 450;
+}
+
body {
@apply bg-background text-foreground;
- scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -64,14 +97,19 @@ body {
color: #333 !important;
}
-/* a { */
-/* text-decoration: none !important; */
-/* } */
-
-article h1 {
- /* margin-bottom: 2rem !important; */
- /* font-size: 3rem !important; */
- /* font-weight: 400 !important; */
+h1:not(.font-mono),
+h2:not(.font-mono),
+h3:not(.font-mono),
+h4:not(.font-mono),
+h5:not(.font-mono),
+h6:not(.font-mono),
+.h1:not(.font-mono),
+.h2:not(.font-mono),
+.h3:not(.font-mono),
+.h4:not(.font-mono),
+.h5:not(.font-mono),
+.h6:not(.font-mono) {
+ @apply font-heading font-semibold;
}
.thin-scrollbar {
@@ -103,10 +141,42 @@ article h1 {
@apply text-foreground-muted text-xs;
}
-.prose :where(p):not(:where([class~='not-prose'] *)) {
+.prose :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
white-space: pre-line;
}
+.prose :where(p, a, code, pre):not(:where([class~='not-prose'], [class~='not-prose'] *)),
+.prose :where(ol > li):not(:where([class~='not-prose'], [class~='not-prose'] *))::marker {
+ font-weight: inherit;
+}
+
+/* Body text inherits a heavier base weight, so override the shared `strong: 500`
+ * to keep bold text visually distinct. */
+.prose :where(strong, b):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
+ font-weight: 700;
+}
+
+.prose :where(h1, h2, h3, h4, h5, h6):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
+ @apply font-heading font-semibold;
+}
+
+.prose
+ :where(p, li, a, figcaption):not(:where([class~='not-prose'], [class~='not-prose'] *)):not(
+ :where(h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6) *
+ ) {
+ @apply text-base leading-7;
+}
+
+.prose :where(h1, .h1):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
+ @apply text-4xl;
+}
+.prose :where(h2, .h2):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
+ @apply text-2xl;
+}
+.prose :where(h3, .h3):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
+ @apply text-xl leading-7;
+}
+
code[class*='language-'],
pre[class*='language-'] {
text-shadow: none !important;
@@ -136,7 +206,7 @@ pre[class*='language-'] {
}
.method-list-title-isChild {
- font-family: var(--custom-font-size-xs);
+ font-family: var(--font-mono);
& h5 {
font-weight: 200;
}
@@ -335,20 +405,13 @@ th code {
transition-timing-function: cubic-bezier(0.24, 0.25, 0.05, 1) !important;
}
[data-rmiz-modal-overlay='visible'] {
- background-color: hsl(var(--background-default)) !important;
+ background-color: var(--background-default) !important;
opacity: 0.8;
}
[data-rmiz-modal-img] {
image-rendering: high-quality;
}
-.skip-link {
- @apply sr-only;
-}
-.skip-link:focus {
- @apply not-sr-only;
-}
-
/* Code blocks need margin applied when in content container */
.prose :where(.shiki:not(.shiki-wrapper *), .shiki-wrapper) {
margin-block: 2rem;
@@ -361,7 +424,7 @@ th code {
--code-foreground: #ffffff;
--code-token-constant: #3ecf8e;
--code-token-string: #ffcda1;
- --code-token-comment: #7e7e7e;
+ --code-token-comment: #949494;
--code-token-parameter: #ffffff;
--code-token-function: #3ecf8e;
--code-token-string-expression: #ffcda1;
@@ -373,17 +436,17 @@ th code {
}
[data-theme='light'],
.light {
- --code-token-keyword: #6b35dc;
- --code-foreground: hsl(var(--foreground-light) / 1);
+ --code-token-keyword: #5f2fc4;
+ --code-foreground: oklch(from var(--foreground-light) l c h / 1);
--code-token-constant: #15593b;
- --code-token-string: #f1a10d;
- --code-token-comment: #7e7e7e;
- --code-token-parameter: hsl(var(--foreground-light) / 1);
+ --code-token-string: #9a5200;
+ --code-token-comment: #6a6a6a;
+ --code-token-parameter: oklch(from var(--foreground-light) l c h / 1);
--code-token-function: #15593b;
- --code-token-string-expression: #f1a10d;
- --code-token-punctuation: hsl(var(--foreground-light) / 1);
- --code-token-link: hsl(var(--foreground-light) / 1);
- --code-token-number: hsl(var(--foreground-light) / 1);
+ --code-token-string-expression: #9a5200;
+ --code-token-punctuation: oklch(from var(--foreground-light) l c h / 1);
+ --code-token-link: oklch(from var(--foreground-light) l c h / 1);
+ --code-token-number: oklch(from var(--foreground-light) l c h / 1);
--code-token-property: #15593b;
--code-highlight-color: #1c1c1c;
}
diff --git a/apps/docs/turbo.jsonc b/apps/docs/turbo.jsonc
index b025460e4ac..6ffd9e5257a 100644
--- a/apps/docs/turbo.jsonc
+++ b/apps/docs/turbo.jsonc
@@ -10,8 +10,14 @@
"inputs": ["spec/**"],
"outputs": ["features/docs/generated/**"],
},
+ // Generates the guide/reference .md files and manifest.json under public/markdown/.
+ // Declaring outputs lets Turbo restore these artifacts on a cache hit; without it a
+ // cached run would skip the script and leave the generated markdown missing for `next build`.
+ "build:markdown": {
+ "outputs": ["public/markdown/**", "public/markdown/manifest.json"],
+ },
"build": {
- "dependsOn": ["^build", "codegen:examples", "codegen:references"],
+ "dependsOn": ["^build", "codegen:examples", "codegen:references", "build:markdown"],
"env": [
"ANALYZE",
"NEXT_PUBLIC_SUPABASE_URL",
diff --git a/apps/learn/components/command.tsx b/apps/learn/components/command.tsx
index b7142400870..fdc4064c4fa 100644
--- a/apps/learn/components/command.tsx
+++ b/apps/learn/components/command.tsx
@@ -1,7 +1,8 @@
'use client'
import { motion } from 'framer-motion'
-import { Tabs_Shadcn_, TabsContent_Shadcn_, TabsList_Shadcn_, TabsTrigger_Shadcn_ } from 'ui'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
+
import { CommandCopyButton } from './command-copy-button'
import { useLocalStorage } from './use-local-storage'
@@ -57,7 +58,7 @@ export function Command({ name, highlight, framework = 'react' }: CommandCopyPro
}
return (
-
+
{highlight && (
-
+
{(Object.keys(commands) as PackageManager[]).map((manager) => (
-
+
{manager}
-
+
))}
-
+
{(Object.keys(commands) as PackageManager[]).map((manager) => (
-
+
$
@@ -93,10 +94,10 @@ export function Command({ name, highlight, framework = 'react' }: CommandCopyPro
-
+
))}
-
+
)
}
diff --git a/apps/learn/components/side-navigation.tsx b/apps/learn/components/side-navigation.tsx
index 0f4c80dae6a..3668830c1a3 100644
--- a/apps/learn/components/side-navigation.tsx
+++ b/apps/learn/components/side-navigation.tsx
@@ -4,7 +4,7 @@ import { logOut, useIsLoggedIn, useUser } from 'common'
import { LogOut, Settings, UserIcon } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
-import { AuthenticatedDropdownMenu, type menuItem } from 'ui-patterns'
+import { AuthenticatedDropdownMenu, type menuItem } from 'ui-patterns/AuthenticatedDropdownMenu'
import { CommandMenu } from './command-menu'
import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
diff --git a/apps/learn/components/theme-switcher-dropdown.tsx b/apps/learn/components/theme-switcher-dropdown.tsx
index 9a53adadb1d..776909bb4f5 100644
--- a/apps/learn/components/theme-switcher-dropdown.tsx
+++ b/apps/learn/components/theme-switcher-dropdown.tsx
@@ -14,7 +14,6 @@ import {
DropdownMenuTrigger,
RadioGroup,
singleThemes,
- Theme,
} from 'ui'
const ThemeSwitcherDropdown = () => {
@@ -75,7 +74,7 @@ const ThemeSwitcherDropdown = () => {
value={theme}
onValueChange={(themeValue) => setTheme(themeValue)}
>
- {singleThemes.map((theme: Theme) => (
+ {singleThemes.map((theme) => (
{theme.name}
diff --git a/apps/learn/styles/code-block-variables.css b/apps/learn/styles/code-block-variables.css
index f1054071748..a21af6b3751 100644
--- a/apps/learn/styles/code-block-variables.css
+++ b/apps/learn/styles/code-block-variables.css
@@ -18,16 +18,16 @@
[data-theme='light'],
.light {
--code-token-keyword: #6b35dc;
- --code-foreground: hsl(var(--foreground-light) / 1);
+ --code-foreground: oklch(from var(--foreground-light) l c h / 1);
--code-token-constant: #15593b;
- --code-token-string: #f1a10d;
+ --code-token-string: #c46a0a;
--code-token-comment: #7e7e7e;
- --code-token-parameter: hsl(var(--foreground-light) / 1);
+ --code-token-parameter: oklch(from var(--foreground-light) l c h / 1);
--code-token-function: #15593b;
- --code-token-string-expression: #f1a10d;
- --code-token-punctuation: hsl(var(--foreground-light) / 1);
- --code-token-link: hsl(var(--foreground-light) / 1);
- --code-token-number: hsl(var(--foreground-light) / 1);
+ --code-token-string-expression: #c46a0a;
+ --code-token-punctuation: oklch(from var(--foreground-light) l c h / 1);
+ --code-token-link: oklch(from var(--foreground-light) l c h / 1);
+ --code-token-number: oklch(from var(--foreground-light) l c h / 1);
--code-token-property: #15593b;
--code-highlight-color: #1c1c1c;
}
diff --git a/apps/learn/styles/globals.css b/apps/learn/styles/globals.css
index 84065d57ffa..1478d871f62 100644
--- a/apps/learn/styles/globals.css
+++ b/apps/learn/styles/globals.css
@@ -11,25 +11,25 @@
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
- --color-background: hsl(var(--background));
- --color-foreground: hsl(var(--foreground));
- --color-card: hsl(var(--card));
- --color-card-foreground: hsl(var(--card-foreground));
- --color-popover: hsl(var(--popover));
- --color-popover-foreground: hsl(var(--popover-foreground));
- --color-primary: hsl(var(--primary));
- --color-primary-foreground: hsl(var(--primary-foreground));
- --color-secondary: hsl(var(--secondary));
- --color-secondary-foreground: hsl(var(--secondary-foreground));
- --color-muted: hsl(var(--muted));
- --color-muted-foreground: hsl(var(--muted-foreground));
- --color-accent: hsl(var(--accent));
- --color-accent-foreground: hsl(var(--accent-foreground));
- --color-destructive: hsl(var(--destructive));
- --color-destructive-foreground: hsl(var(--destructive-foreground));
- --color-border: hsl(var(--border));
- --color-input: hsl(var(--input));
- --color-ring: hsl(var(--ring));
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
--color-chart-1: hsl(var(--chart-1));
--color-chart-2: hsl(var(--chart-2));
--color-chart-3: hsl(var(--chart-3));
@@ -39,74 +39,74 @@
@layer base {
:root {
- --background: 0 0% 100%;
- --foreground: 224 71.4% 4.1%;
- --card: 0 0% 100%;
- --card-foreground: 224 71.4% 4.1%;
- --popover: 0 0% 100%;
- --popover-foreground: 224 71.4% 4.1%;
- --primary: 220.9 39.3% 11%;
- --primary-foreground: 210 20% 98%;
- --secondary: 220 14.3% 95.9%;
- --secondary-foreground: 220.9 39.3% 11%;
- --muted: 220 14.3% 95.9%;
- --muted-foreground: 220 8.9% 46.1%;
- --accent: 220 14.3% 95.9%;
- --accent-foreground: 220.9 39.3% 11%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 84.2% 60.2%;
- --border: 220 13% 91%;
- --input: 220 13% 91%;
- --ring: 220 13% 70.8%;
+ --background: hsl(0 0% 100%);
+ --foreground: hsl(224 71.4% 4.1%);
+ --card: hsl(0 0% 100%);
+ --card-foreground: hsl(224 71.4% 4.1%);
+ --popover: hsl(0 0% 100%);
+ --popover-foreground: hsl(224 71.4% 4.1%);
+ --primary: hsl(220.9 39.3% 11%);
+ --primary-foreground: hsl(210 20% 98%);
+ --secondary: hsl(220 14.3% 95.9%);
+ --secondary-foreground: hsl(220.9 39.3% 11%);
+ --muted: hsl(220 14.3% 95.9%);
+ --muted-foreground: hsl(220 8.9% 46.1%);
+ --accent: hsl(220 14.3% 95.9%);
+ --accent-foreground: hsl(220.9 39.3% 11%);
+ --destructive: hsl(0 84.2% 60.2%);
+ --destructive-foreground: hsl(0 84.2% 60.2%);
+ --border: hsl(220 13% 91%);
+ --input: hsl(220 13% 91%);
+ --ring: hsl(220 13% 70.8%);
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.625rem;
- --sidebar: 210 20% 98.5%;
- --sidebar-foreground: 224 71.4% 4.1%;
- --sidebar-primary: 220.9 39.3% 11%;
- --sidebar-primary-foreground: 210 20% 98%;
- --sidebar-accent: 220 14.3% 95.9%;
- --sidebar-accent-foreground: 220.9 39.3% 11%;
- --sidebar-border: 220 13% 91%;
- --sidebar-ring: 220 13% 70.8%;
+ --sidebar: hsl(210 20% 98.5%);
+ --sidebar-foreground: hsl(224 71.4% 4.1%);
+ --sidebar-primary: hsl(220.9 39.3% 11%);
+ --sidebar-primary-foreground: hsl(210 20% 98%);
+ --sidebar-accent: hsl(220 14.3% 95.9%);
+ --sidebar-accent-foreground: hsl(220.9 39.3% 11%);
+ --sidebar-border: hsl(220 13% 91%);
+ --sidebar-ring: hsl(220 13% 70.8%);
}
.dark {
- --background: 224 71.4% 4.1%;
- --foreground: 210 20% 98%;
- --card: 224 71.4% 4.1%;
- --card-foreground: 210 20% 98%;
- --popover: 224 71.4% 4.1%;
- --popover-foreground: 210 20% 98%;
- --primary: 210 20% 98%;
- --primary-foreground: 220.9 39.3% 11%;
- --secondary: 215 27.9% 16.9%;
- --secondary-foreground: 210 20% 98%;
- --muted: 215 27.9% 16.9%;
- --muted-foreground: 217.9 10.6% 64.9%;
- --accent: 215 27.9% 16.9%;
- --accent-foreground: 210 20% 98%;
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 84.2% 60.2%;
- --border: 215 27.9% 16.9%;
- --input: 215 27.9% 16.9%;
- --ring: 217.9 10.6% 55.6%;
+ --background: hsl(224 71.4% 4.1%);
+ --foreground: hsl(210 20% 98%);
+ --card: hsl(224 71.4% 4.1%);
+ --card-foreground: hsl(210 20% 98%);
+ --popover: hsl(224 71.4% 4.1%);
+ --popover-foreground: hsl(210 20% 98%);
+ --primary: hsl(210 20% 98%);
+ --primary-foreground: hsl(220.9 39.3% 11%);
+ --secondary: hsl(215 27.9% 16.9%);
+ --secondary-foreground: hsl(210 20% 98%);
+ --muted: hsl(215 27.9% 16.9%);
+ --muted-foreground: hsl(217.9 10.6% 64.9%);
+ --accent: hsl(215 27.9% 16.9%);
+ --accent-foreground: hsl(210 20% 98%);
+ --destructive: hsl(0 62.8% 30.6%);
+ --destructive-foreground: hsl(0 84.2% 60.2%);
+ --border: hsl(215 27.9% 16.9%);
+ --input: hsl(215 27.9% 16.9%);
+ --ring: hsl(217.9 10.6% 55.6%);
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
- --sidebar: 220.9 39.3% 11%;
- --sidebar-foreground: 210 20% 98%;
- --sidebar-primary: 220 70% 50%;
- --sidebar-primary-foreground: 210 20% 98%;
- --sidebar-accent: 215 27.9% 16.9%;
- --sidebar-accent-foreground: 210 20% 98%;
- --sidebar-border: 215 27.9% 16.9%;
- --sidebar-ring: 220 13% 43.9%;
+ --sidebar: hsl(220.9 39.3% 11%);
+ --sidebar-foreground: hsl(210 20% 98%);
+ --sidebar-primary: hsl(220 70% 50%);
+ --sidebar-primary-foreground: hsl(210 20% 98%);
+ --sidebar-accent: hsl(215 27.9% 16.9%);
+ --sidebar-accent-foreground: hsl(210 20% 98%);
+ --sidebar-border: hsl(215 27.9% 16.9%);
+ --sidebar-ring: hsl(220 13% 43.9%);
}
}
@@ -153,75 +153,75 @@
}
.preview {
- --background: 0 0% 100%;
- --foreground: 0 0% 14.5%;
- --card: 0 0% 100%;
- --card-foreground: 0 0% 14.5%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 14.5%;
- --primary: 0 0% 20.5%;
- --primary-foreground: 0 0% 98.5%;
- --secondary: 0 0% 97%;
- --secondary-foreground: 0 0% 20.5%;
- --muted: 0 0% 97%;
- --muted-foreground: 0 0% 55.6%;
- --accent: 0 0% 97%;
- --accent-foreground: 0 0% 20.5%;
- --destructive: 27.3 24.5% 57.7%;
- --destructive-foreground: 27.3 24.5% 57.7%;
- --border: 0 0% 92.2%;
- --input: 0 0% 92.2%;
- --ring: 0 0% 70.8%;
+ --background: hsl(0 0% 100%);
+ --foreground: hsl(0 0% 14.5%);
+ --card: hsl(0 0% 100%);
+ --card-foreground: hsl(0 0% 14.5%);
+ --popover: hsl(0 0% 100%);
+ --popover-foreground: hsl(0 0% 14.5%);
+ --primary: hsl(0 0% 20.5%);
+ --primary-foreground: hsl(0 0% 98.5%);
+ --secondary: hsl(0 0% 97%);
+ --secondary-foreground: hsl(0 0% 20.5%);
+ --muted: hsl(0 0% 97%);
+ --muted-foreground: hsl(0 0% 55.6%);
+ --accent: hsl(0 0% 97%);
+ --accent-foreground: hsl(0 0% 20.5%);
+ --destructive: hsl(27.3 24.5% 57.7%);
+ --destructive-foreground: hsl(27.3 24.5% 57.7%);
+ --border: hsl(0 0% 92.2%);
+ --input: hsl(0 0% 92.2%);
+ --ring: hsl(0 0% 70.8%);
--chart-1: 41.1 22.2% 64.6%;
--chart-2: 184.7 11.8% 60%;
--chart-3: 227.4 7% 39.8%;
--chart-4: 84.4 18.9% 82.8%;
--chart-5: 70.1 18.8% 76.9%;
--radius: 0.625rem;
- --sidebar: 0 0% 98.5%;
- --sidebar-foreground: 0 0% 14.5%;
- --sidebar-primary: 0 0% 20.5%;
- --sidebar-primary-foreground: 0 0% 98.5%;
- --sidebar-accent: 0 0% 97%;
- --sidebar-accent-foreground: 0 0% 20.5%;
- --sidebar-border: 0 0% 92.2%;
- --sidebar-ring: 0 0% 70.8%;
+ --sidebar: hsl(0 0% 98.5%);
+ --sidebar-foreground: hsl(0 0% 14.5%);
+ --sidebar-primary: hsl(0 0% 20.5%);
+ --sidebar-primary-foreground: hsl(0 0% 98.5%);
+ --sidebar-accent: hsl(0 0% 97%);
+ --sidebar-accent-foreground: hsl(0 0% 20.5%);
+ --sidebar-border: hsl(0 0% 92.2%);
+ --sidebar-ring: hsl(0 0% 70.8%);
}
[data-theme='dark'] .preview,
[data-theme='classic-dark'] .preview {
- --background: 0 0% 14.5%;
- --foreground: 0 0% 98.5%;
- --card: 0 0% 0%;
- --card-foreground: 0 0% 98.5%;
- --popover: 0 0% 14.5%;
- --popover-foreground: 0 0% 98.5%;
- --primary: 0 0% 98.5%;
- --primary-foreground: 0 0% 20.5%;
- --secondary: 0 0% 26.9%;
- --secondary-foreground: 0 0% 98.5%;
- --muted: 0 0% 26.9%;
- --muted-foreground: 0 0% 70.8%;
- --accent: 0 0% 26.9%;
- --accent-foreground: 0 0% 98.5%;
- --destructive: 25.7 14.1% 39.6%;
- --destructive-foreground: 25.3 23.7% 63.7%;
- --border: 0 0% 26.9%;
- --input: 0 0% 26.9%;
- --ring: 0 0% 55.6%;
+ --background: hsl(0 0% 14.5%);
+ --foreground: hsl(0 0% 98.5%);
+ --card: hsl(0 0% 0%);
+ --card-foreground: hsl(0 0% 98.5%);
+ --popover: hsl(0 0% 14.5%);
+ --popover-foreground: hsl(0 0% 98.5%);
+ --primary: hsl(0 0% 98.5%);
+ --primary-foreground: hsl(0 0% 20.5%);
+ --secondary: hsl(0 0% 26.9%);
+ --secondary-foreground: hsl(0 0% 98.5%);
+ --muted: hsl(0 0% 26.9%);
+ --muted-foreground: hsl(0 0% 70.8%);
+ --accent: hsl(0 0% 26.9%);
+ --accent-foreground: hsl(0 0% 98.5%);
+ --destructive: hsl(25.7 14.1% 39.6%);
+ --destructive-foreground: hsl(25.3 23.7% 63.7%);
+ --border: hsl(0 0% 26.9%);
+ --input: hsl(0 0% 26.9%);
+ --ring: hsl(0 0% 55.6%);
--chart-1: 264.4 24.3% 48.8%;
--chart-2: 162.5 17% 69.6%;
--chart-3: 70.1 18.8% 76.9%;
--chart-4: 303.9 26.5% 62.7%;
--chart-5: 16.4 24.6% 64.5%;
- --sidebar: 0 0% 20.5%;
- --sidebar-foreground: 0 0% 98.5%;
- --sidebar-primary: 264.4 24.3% 48.8%;
- --sidebar-primary-foreground: 0 0% 98.5%;
- --sidebar-accent: 0 0% 26.9%;
- --sidebar-accent-foreground: 0 0% 98.5%;
- --sidebar-border: 0 0% 26.9%;
- --sidebar-ring: 0 0% 43.9%;
+ --sidebar: hsl(0 0% 20.5%);
+ --sidebar-foreground: hsl(0 0% 98.5%);
+ --sidebar-primary: hsl(264.4 24.3% 48.8%);
+ --sidebar-primary-foreground: hsl(0 0% 98.5%);
+ --sidebar-accent: hsl(0 0% 26.9%);
+ --sidebar-accent-foreground: hsl(0 0% 98.5%);
+ --sidebar-border: hsl(0 0% 26.9%);
+ --sidebar-ring: hsl(0 0% 43.9%);
}
.preview * {
diff --git a/apps/lite-studio/app/routes/projects.$ref.tsx b/apps/lite-studio/app/routes/projects.$ref.tsx
index 2fc27badfef..3605a8f0224 100644
--- a/apps/lite-studio/app/routes/projects.$ref.tsx
+++ b/apps/lite-studio/app/routes/projects.$ref.tsx
@@ -9,10 +9,10 @@ import {
Label,
Separator,
Switch,
- Tabs_Shadcn_ as Tabs,
- TabsContent_Shadcn_ as TabsContent,
- TabsList_Shadcn_ as TabsList,
- TabsTrigger_Shadcn_ as TabsTrigger,
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import {
diff --git a/apps/lite-studio/styles/globals.css b/apps/lite-studio/styles/globals.css
index 7450b023ad8..d468bbe9fef 100644
--- a/apps/lite-studio/styles/globals.css
+++ b/apps/lite-studio/styles/globals.css
@@ -38,6 +38,6 @@ body {
::before,
::backdrop,
::file-selector-button {
- border-color: hsl(var(--border-default, currentColor));
+ border-color: var(--border-default, currentColor);
}
}
diff --git a/apps/studio/.github/eslint-rule-baselines.json b/apps/studio/.github/eslint-rule-baselines.json
index 7074c1000f7..5926095fa21 100644
--- a/apps/studio/.github/eslint-rule-baselines.json
+++ b/apps/studio/.github/eslint-rule-baselines.json
@@ -1,12 +1,25 @@
{
"rules": {
- "react-hooks/exhaustive-deps": 172,
+ "react-hooks/exhaustive-deps": 161,
"import/no-anonymous-default-export": 57,
"@tanstack/query/exhaustive-deps": 9,
- "@typescript-eslint/no-explicit-any": 952,
+ "@typescript-eslint/no-explicit-any": 898,
"no-restricted-imports": 0,
- "no-restricted-exports": 235,
- "react/no-unstable-nested-components": 41
+ "no-restricted-exports": 198,
+ "react/no-unstable-nested-components": 38,
+ "jsx-a11y/aria-props": 0,
+ "jsx-a11y/aria-proptypes": 0,
+ "jsx-a11y/role-supports-aria-props": 0,
+ "jsx-a11y/anchor-has-content": 0,
+ "jsx-a11y/control-has-associated-label": 274,
+ "jsx-a11y/label-has-associated-control": 37,
+ "jsx-a11y/aria-role": 0,
+ "jsx-a11y/no-redundant-roles": 4,
+ "jsx-a11y/no-aria-hidden-on-focusable": 0,
+ "jsx-a11y/tabindex-no-positive": 0,
+ "jsx-a11y/anchor-is-valid": 7,
+ "jsx-a11y/heading-has-content": 3,
+ "jsx-a11y/no-distracting-elements": 0
},
"ruleFiles": {
"react-hooks/exhaustive-deps": {
@@ -20,9 +33,6 @@
"components/interfaces/Auth/BasicAuthSettingsForm.tsx": 1,
"components/interfaces/Auth/OAuthApps/OAuthServerSettingsForm.tsx": 1,
"components/interfaces/Auth/PerformanceSettingsForm.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditor/PolicyDefinition.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditorPanel/RLSCodeEditor.tsx": 2,
"components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx": 1,
"components/interfaces/Auth/RedirectUrls/AddNewURLModal.tsx": 1,
"components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx": 1,
@@ -38,6 +48,8 @@
"components/interfaces/ConnectSheet/content/steps/mcp/cursor/content.tsx": 1,
"components/interfaces/Database/Backups/PITR/TimeInput.tsx": 4,
"components/interfaces/Database/Indexes/Indexes.tsx": 2,
+ "components/interfaces/Database/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx": 1,
+ "components/interfaces/Database/Policies/PolicyEditorPanel/RLSCodeEditor.tsx": 1,
"components/interfaces/Database/Replication/ReplicationDiagram/index.tsx": 1,
"components/interfaces/DiskManagement/DiskManagementForm.tsx": 1,
"components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx": 1,
@@ -57,14 +69,11 @@
"components/interfaces/Reports/ReportBlock/ChartBlock.tsx": 1,
"components/interfaces/Reports/ReportFilterBar.tsx": 1,
"components/interfaces/Reports/Reports.tsx": 1,
- "components/interfaces/Reports/useReportFilters.ts": 4,
"components/interfaces/SQLEditor/AskAIWidget.tsx": 1,
"components/interfaces/SQLEditor/InlineWidget.tsx": 2,
- "components/interfaces/SQLEditor/MonacoEditor.tsx": 1,
"components/interfaces/SQLEditor/MoveQueryModal.tsx": 1,
"components/interfaces/SQLEditor/OngoingQueriesPanel.tsx": 1,
"components/interfaces/SQLEditor/SQLEditor.tsx": 2,
- "components/interfaces/SQLEditor/UtilityPanel/SavingIndicator.tsx": 1,
"components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx": 1,
"components/interfaces/Settings/Addons/IPv4SidePanel.tsx": 1,
"components/interfaces/Settings/Addons/PITRSidePanel.tsx": 1,
@@ -79,6 +88,7 @@
"components/interfaces/SignIn/SignInPartner.tsx": 1,
"components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CopyEnvButton.tsx": 1,
"components/interfaces/Storage/StorageExplorer/FileExplorerRowEditing.tsx": 1,
+ "components/interfaces/Storage/StoragePolicies/PolicyEditor/PolicyDefinition.tsx": 1,
"components/interfaces/Storage/VectorBuckets/CreateVectorTableSheet.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/index.tsx": 2,
"components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx": 1,
@@ -97,17 +107,13 @@
"components/ui/CodeEditor/CodeEditor.tsx": 1,
"components/ui/DataTable/DataTableFilters/DataTableFilterInput.tsx": 2,
"components/ui/DataTable/DataTableFilters/DataTableFilterSlider.tsx": 2,
- "components/ui/DataTable/DataTableHeaderLayout.tsx": 1,
"components/ui/DataTable/DataTableSideBarLayout.tsx": 4,
- "components/ui/DataTable/DataTableToolbar.tsx": 1,
"components/ui/DataTable/DataTableViewOptions.tsx": 1,
"components/ui/DataTable/LiveButton.tsx": 1,
"components/ui/DataTable/TimelineChart.tsx": 1,
"components/ui/DatePicker/TimeSplitInput.tsx": 1,
"components/ui/DateRangePicker.tsx": 1,
- "components/ui/Error.tsx": 1,
"components/ui/FilterPopover.tsx": 1,
- "components/ui/ScrollGradient.tsx": 1,
"data/__templates/resource-query.ts": 1,
"data/__templates/resources-query.ts": 1,
"data/reports/api-report-query.ts": 2,
@@ -128,7 +134,7 @@
"pages/logout.tsx": 1,
"pages/new/index.tsx": 1,
"pages/organizations.tsx": 1,
- "pages/project/[ref]/auth/policies.tsx": 2,
+ "pages/project/[ref]/database/policies.tsx": 2,
"pages/project/[ref]/editor/[id].tsx": 1,
"pages/project/[ref]/integrations/[id]/index.tsx": 1,
"pages/project/[ref]/settings/jwt/legacy.tsx": 1,
@@ -209,7 +215,6 @@
"@typescript-eslint/no-explicit-any": {
"components/grid/SupabaseGrid.tsx": 1,
"components/grid/SupabaseGrid.utils.ts": 2,
- "components/grid/components/common/MonacoEditor.tsx": 3,
"components/grid/components/editor/JsonEditor.tsx": 2,
"components/grid/components/editor/SelectEditor.tsx": 1,
"components/grid/components/editor/TextEditor.tsx": 1,
@@ -234,13 +239,6 @@
"components/interfaces/Auth/Overview/OverviewTable.tsx": 1,
"components/interfaces/Auth/Overview/OverviewUsage.constants.ts": 1,
"components/interfaces/Auth/PerformanceSettingsForm.tsx": 2,
- "components/interfaces/Auth/Policies/Policies.tsx": 1,
- "components/interfaces/Auth/Policies/Policies.utils.ts": 1,
- "components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditor/index.tsx": 2,
- "components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyTemplates.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditorPanel/RLSCodeEditor.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx": 2,
"components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx": 1,
"components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx": 1,
"components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx": 2,
@@ -262,10 +260,13 @@
"components/interfaces/Database/Functions/CreateFunction/FunctionEditor.tsx": 1,
"components/interfaces/Database/Indexes/Indexes.tsx": 1,
"components/interfaces/Database/Migrations/Migrations.tsx": 1,
+ "components/interfaces/Database/Policies/Policies.tsx": 1,
+ "components/interfaces/Database/Policies/PolicyEditorPanel/PolicyTemplates.tsx": 1,
+ "components/interfaces/Database/Policies/PolicyEditorPanel/index.tsx": 2,
"components/interfaces/Database/Privileges/PrivilegesTable.tsx": 1,
"components/interfaces/Database/Publications/PublicationsList.tsx": 5,
"components/interfaces/Database/Publications/PublicationsTableItem.tsx": 5,
- "components/interfaces/Database/Replication/DisableExternalReplicationDialog.tsx": 1,
+ "components/interfaces/Database/Replication/DisablePipelinesDialog.tsx": 1,
"components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.utils.tsx": 1,
"components/interfaces/Database/RestoreToNewProject/RestoreToNewProject.tsx": 1,
"components/interfaces/Docs/Description.tsx": 2,
@@ -278,9 +279,7 @@
"components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx": 1,
"components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.utils.tsx": 3,
"components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx": 1,
- "components/interfaces/Functions/FunctionsNav.tsx": 1,
"components/interfaces/Home/ProjectList/ProjectList.tsx": 1,
- "components/interfaces/Home/ServiceStatus.tsx": 1,
"components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CreateCronJobSheet.tsx": 1,
"components/interfaces/Integrations/CronJobs/CronJobTableCell.tsx": 2,
"components/interfaces/Integrations/CronJobs/PreviousRunsTab.tsx": 2,
@@ -301,7 +300,6 @@
"components/interfaces/Linter/LinterDataGrid.tsx": 4,
"components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx": 3,
"components/interfaces/Observability/ObservabilityOverview.tsx": 1,
- "components/interfaces/Observability/ServiceHealthCard.tsx": 1,
"components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx": 3,
"components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.utils.ts": 2,
"components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx": 2,
@@ -329,7 +327,6 @@
"components/interfaces/QueryPerformance/IndexAdvisor/EnableIndexAdvisorButton.tsx": 1,
"components/interfaces/QueryPerformance/IndexAdvisor/IndexAdvisorDisabledState.tsx": 1,
"components/interfaces/QueryPerformance/IndexAdvisor/index-advisor.utils.ts": 2,
- "components/interfaces/QueryPerformance/QueryPerformanceChart.tsx": 3,
"components/interfaces/QueryPerformance/QueryPerformanceGrid.tsx": 3,
"components/interfaces/QueryPerformance/WithStatements/WithStatements.tsx": 1,
"components/interfaces/Realtime/Inspector/Messages.types.ts": 1,
@@ -347,7 +344,6 @@
"components/interfaces/Reports/v2/ReportChartUpsell.tsx": 1,
"components/interfaces/Reports/v2/ReportChartV2.tsx": 1,
"components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx": 1,
- "components/interfaces/SQLEditor/MonacoEditor.tsx": 2,
"components/interfaces/SQLEditor/MoveQueryModal.tsx": 2,
"components/interfaces/SQLEditor/RenameQueryModal.tsx": 2,
"components/interfaces/SQLEditor/SQLEditor.tsx": 4,
@@ -368,9 +364,9 @@
"components/interfaces/Settings/Logs/LogTable.tsx": 1,
"components/interfaces/Settings/Logs/Logs.constants.ts": 1,
"components/interfaces/Settings/Logs/Logs.types.ts": 1,
+ "components/interfaces/Settings/Logs/Logs.utils.otel.ts": 1,
"components/interfaces/Settings/Logs/Logs.utils.ts": 12,
"components/interfaces/Settings/Logs/PreviewFilterPanel.tsx": 1,
- "components/interfaces/Settings/Logs/PreviewFilterPanelWithUniversal.tsx": 2,
"components/interfaces/SignIn/SignInForm.tsx": 1,
"components/interfaces/SignIn/SignInWithCustom.tsx": 1,
"components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CreateTable/CreateTableSheet.tsx": 2,
@@ -391,7 +387,10 @@
"components/interfaces/Storage/StorageExplorer/MoveItemsModal.tsx": 1,
"components/interfaces/Storage/StorageExplorer/StorageExplorer.tsx": 1,
"components/interfaces/Storage/StorageExplorer/StorageExplorer.utils.tsx": 2,
+ "components/interfaces/Storage/StoragePolicies/PolicyEditor/PolicyRoles.tsx": 1,
+ "components/interfaces/Storage/StoragePolicies/PolicyEditor/index.tsx": 2,
"components/interfaces/Storage/StoragePolicies/StoragePolicies.tsx": 5,
+ "components/interfaces/Storage/StoragePolicies/StoragePolicies.utils.ts": 1,
"components/interfaces/Storage/StoragePolicies/StoragePoliciesEditPolicyModal.tsx": 10,
"components/interfaces/Storage/StoragePolicies/StoragePoliciesEditor.tsx": 2,
"components/interfaces/Storage/StoragePolicies/StoragePoliciesReview.tsx": 3,
@@ -416,24 +415,19 @@
"components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/TextEditor.tsx": 2,
"components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx": 13,
"components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.types.ts": 1,
- "components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx": 2,
+ "components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetTextInput.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.utils.tsx": 2,
"components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx": 2,
"components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetPreviewGrid.tsx": 2,
"components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ForeignKeysManagement/ForeignKeyRow.tsx": 1,
- "components/interfaces/TableGridEditor/TableDefinition.tsx": 3,
"components/interfaces/TableGridEditor/TableEntity.utils.ts": 1,
"components/interfaces/UnifiedLogs/ServiceFlow/components/PostgresFlowDetail.tsx": 2,
- "components/interfaces/UnifiedLogs/ServiceFlow/components/shared/FieldWithSeeMore.tsx": 4,
- "components/interfaces/UnifiedLogs/ServiceFlow/config/fieldHelpers.ts": 3,
"components/interfaces/UnifiedLogs/ServiceFlow/types.ts": 10,
"components/interfaces/UnifiedLogs/ServiceFlow/utils/storageUtils.ts": 3,
- "components/layouts/DocsLayout/DocsLayout.tsx": 2,
"components/layouts/IntegrationsLayout/Integrations.utils.ts": 2,
"components/layouts/Navigation/LayoutHeader/BreadcrumbsView.tsx": 2,
"components/layouts/Navigation/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx": 3,
- "components/layouts/Navigation/NavigationBar/NavigationIconLink.tsx": 1,
"components/layouts/ProjectLayout/BuildingState.tsx": 1,
"components/layouts/ProjectLayout/LayoutHeader/MobileMenuContent/mobileOrgMenuRegistry.tsx": 2,
"components/layouts/SQLEditorLayout/SQLEditorMenu.tsx": 2,
@@ -443,7 +437,6 @@
"components/layouts/SQLEditorLayout/SQLEditorNavV2/SearchList.tsx": 1,
"components/layouts/TableEditorLayout/EntityListItem.tsx": 1,
"components/layouts/TableEditorLayout/TableEditorMenu.tsx": 1,
- "components/to-be-cleaned/KeyMap.tsx": 3,
"components/ui/AIAssistantPanel/AIAssistant.utils.ts": 1,
"components/ui/AIAssistantPanel/DisplayBlockRenderer.tsx": 1,
"components/ui/AIAssistantPanel/MessageMarkdown.tsx": 1,
@@ -451,15 +444,13 @@
"components/ui/CardButton.tsx": 2,
"components/ui/Charts/AreaChart.tsx": 1,
"components/ui/Charts/BarChart.tsx": 1,
- "components/ui/Charts/ChartHandler.tsx": 4,
"components/ui/Charts/ChartHeader.tsx": 5,
"components/ui/Charts/Charts.utils.tsx": 3,
"components/ui/Charts/ComposedChart.tsx": 8,
"components/ui/Charts/ComposedChart.utils.tsx": 14,
"components/ui/Charts/ComposedChartHandler.tsx": 9,
"components/ui/Charts/StackedBarChart.tsx": 3,
- "components/ui/CodeEditor/CodeEditor.tsx": 7,
- "components/ui/CodeEditor/CodeEditor.utils.ts": 2,
+ "components/ui/CodeEditor/CodeEditor.tsx": 2,
"components/ui/CodeEditor/Providers/BackwardIterator.ts": 1,
"components/ui/CodeEditor/Providers/PgSQLCompletionProvider.ts": 21,
"components/ui/CodeEditor/Providers/PgSQLSignatureHelpProvider.ts": 4,
@@ -471,20 +462,15 @@
"components/ui/DatePicker/DatePicker.types.ts": 2,
"components/ui/DatePicker/index.tsx": 2,
"components/ui/DateRangePicker.tsx": 1,
- "components/ui/DebouncedComponent.tsx": 1,
"components/ui/DownloadResultsButton.tsx": 1,
- "components/ui/Error.tsx": 1,
"components/ui/ErrorBoundary/ErrorBoundary.tsx": 2,
"components/ui/ErrorBoundary/GlobalErrorBoundaryState.tsx": 1,
"components/ui/FilterPopover.tsx": 1,
- "components/ui/Forms/Form.constants.ts": 1,
"components/ui/ProductMenu/ProductMenu.types.ts": 1,
"components/ui/ProjectSettings/DisplayApiSettings.tsx": 1,
"components/ui/QueryBlock/QueryBlock.tsx": 4,
"components/ui/QueryBlock/QueryBlock.utils.ts": 1,
- "components/ui/SchemaComboBox.tsx": 1,
"components/ui/SchemaSelector.tsx": 1,
- "components/ui/SqlEditor.tsx": 6,
"components/ui/ui.types.ts": 1,
"data/ai/check-api-key-query.ts": 1,
"data/ai/rate-message-mutation.ts": 1,
@@ -492,8 +478,6 @@
"data/ai/sql-policy-mutation.ts": 1,
"data/ai/sql-title-mutation.ts": 1,
"data/analytics/functions-combined-stats-query.ts": 1,
- "data/analytics/functions-req-stats-query.ts": 1,
- "data/analytics/functions-resource-usage-query.ts": 1,
"data/analytics/infra-monitoring-query.ts": 1,
"data/analytics/project-daily-stats-queries.ts": 1,
"data/auth/auth-overview-query.ts": 1,
@@ -605,7 +589,7 @@
"pages/project/[ref]/settings/log-drains.tsx": 1,
"state/ai-assistant-state.tsx": 4,
"state/role-impersonation-state.tsx": 1,
- "state/sql-editor-v2.ts": 5,
+ "state/sql-editor/sql-editor-session-state.ts": 4,
"state/storage-explorer.tsx": 15,
"state/table-editor-table.tsx": 15,
"state/table-editor.tsx": 5,
@@ -624,14 +608,6 @@
"components/interfaces/Account/TOTPFactors/DeleteFactorModal.tsx": 1,
"components/interfaces/App/CommandMenu/CommandMenu.tsx": 1,
"components/interfaces/Auth/AuthProvidersForm/FormField.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditor/PolicyAllowedOperation.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditor/PolicyDefinition.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditor/PolicyEditorFooter.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyEditorModal/PolicyEditorModalTitle.tsx": 1,
- "components/interfaces/Auth/Policies/PolicySelection.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyTemplates/TemplatePreview.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyTemplates/TemplatesList.tsx": 1,
- "components/interfaces/Auth/Policies/PolicyTemplates/index.tsx": 1,
"components/interfaces/Auth/SiteUrl/SiteUrl.tsx": 1,
"components/interfaces/Auth/Users/CreateUserModal.tsx": 1,
"components/interfaces/Auth/Users/InviteUserModal.tsx": 1,
@@ -640,7 +616,6 @@
"components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx": 1,
"components/interfaces/Billing/Payment/PaymentMethods/ChangePaymentMethodModal.tsx": 1,
"components/interfaces/Billing/Payment/PaymentMethods/CreditCard.tsx": 1,
- "components/interfaces/Billing/Payment/PaymentMethods/CurrentPaymentMethod.tsx": 1,
"components/interfaces/Billing/Payment/PaymentMethods/DeletePaymentMethodModal.tsx": 1,
"components/interfaces/Billing/Payment/PaymentMethods/PaymentMethods.tsx": 1,
"components/interfaces/Billing/SpendCapModal.tsx": 1,
@@ -652,7 +627,6 @@
"components/interfaces/Database/EnumeratedTypes/CreateEnumeratedTypeSidePanel.tsx": 1,
"components/interfaces/Database/EnumeratedTypes/EditEnumeratedTypeSidePanel.tsx": 1,
"components/interfaces/Database/EnumeratedTypes/EnumeratedTypeValueRow.tsx": 1,
- "components/interfaces/Database/Extensions/ExtensionCardSkeleton.tsx": 1,
"components/interfaces/Database/Migrations/Migrations.tsx": 1,
"components/interfaces/Database/Privileges/PrivilegesHead.tsx": 1,
"components/interfaces/Database/Privileges/PrivilegesTable.tsx": 1,
@@ -668,9 +642,7 @@
"components/interfaces/Docs/PublicSchemaNotEnabledAlert.tsx": 1,
"components/interfaces/Docs/Snippets.ts": 1,
"components/interfaces/Functions/CommandRender.tsx": 1,
- "components/interfaces/Functions/EdgeFunctionOverview/EdgeFunctionOverview.tsx": 1,
"components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx": 1,
- "components/interfaces/Functions/FunctionsNav.tsx": 1,
"components/interfaces/Integrations/CronJobs/CronJobsEmptyState.tsx": 1,
"components/interfaces/Integrations/CronJobs/CronSyntaxChart.tsx": 1,
"components/interfaces/Integrations/Integration/LegacyIntegrationPage.tsx": 1,
@@ -690,7 +662,6 @@
"components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx": 1,
"components/interfaces/Organization/BillingSettings/Subscription/UpgradeModal.tsx": 1,
"components/interfaces/Organization/CloudMarketplace/AwsMarketplaceAutoRenewalWarning.tsx": 1,
- "components/interfaces/Organization/IntegrationSettings/SidePanelVercelProjectLinker.tsx": 1,
"components/interfaces/Organization/InvoicesSettings/InvoicePayButton.tsx": 1,
"components/interfaces/Organization/TeamSettings/MembersView.tsx": 1,
"components/interfaces/Organization/Usage/ActiveCompute.tsx": 1,
@@ -718,24 +689,19 @@
"components/interfaces/Reports/ReportWidget.tsx": 1,
"components/interfaces/Reports/Reports.tsx": 1,
"components/interfaces/SQLEditor/InlineWidget.tsx": 1,
- "components/interfaces/SQLEditor/MonacoEditor.tsx": 1,
"components/interfaces/SQLEditor/SQLTemplates/SQLExamples.tsx": 1,
"components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx": 1,
"components/interfaces/SQLEditor/UtilityPanel/ReadOnlyBadge.tsx": 1,
- "components/interfaces/SQLEditor/UtilityPanel/Results.tsx": 1,
"components/interfaces/SQLEditor/UtilityPanel/SavingIndicator.tsx": 1,
- "components/interfaces/SQLEditor/hooks.ts": 1,
"components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx": 1,
"components/interfaces/Settings/Addons/IPv4SidePanel.tsx": 1,
"components/interfaces/Settings/Addons/PITRSidePanel.tsx": 1,
"components/interfaces/Settings/Database/DatabaseSettings/ConfirmDisableReadOnlyModal.tsx": 1,
- "components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx": 1,
"components/interfaces/Settings/Database/DiskSizeConfigurationModal.tsx": 1,
"components/interfaces/Settings/Database/NetworkRestrictions/AddRestrictionModal.tsx": 1,
"components/interfaces/Settings/Database/NetworkRestrictions/AllowAllModal.tsx": 1,
"components/interfaces/Settings/Database/NetworkRestrictions/DisallowAllModal.tsx": 1,
"components/interfaces/Settings/Database/NetworkRestrictions/RemoveRestrictionModal.tsx": 1,
- "components/interfaces/Settings/General/CustomDomainConfig/DNSRecord.tsx": 1,
"components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx": 1,
"components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx": 1,
"components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropAllReplicasConfirmationModal.tsx": 1,
@@ -750,20 +716,20 @@
"components/interfaces/Settings/Logs/LogColumnRenderers/MultigresColumnRender.tsx": 1,
"components/interfaces/Settings/Logs/LogSelection.tsx": 1,
"components/interfaces/Settings/Logs/LogSelectionRenderers/DefaultPreviewSelectionRenderer.tsx": 1,
- "components/interfaces/Settings/Logs/Logs.Divider.tsx": 1,
- "components/interfaces/Settings/Logs/Logs.SavedQueriesItem.tsx": 1,
"components/interfaces/Settings/Logs/Logs.SqlSnippetCode.tsx": 1,
"components/interfaces/Settings/Logs/LogsErrorRenderers/ResourcesExceededErrorRenderer.tsx": 1,
"components/interfaces/Settings/Logs/LogsFilterPopover.tsx": 1,
- "components/interfaces/Settings/Logs/LogsQueryPanel.tsx": 1,
"components/interfaces/Settings/Logs/PreviewFilterPanel.tsx": 1,
"components/interfaces/Settings/Logs/RecentQueriesItem.tsx": 1,
"components/interfaces/Settings/Logs/UpgradePrompt.tsx": 1,
"components/interfaces/SignIn/PasswordConditionsHelper.tsx": 1,
"components/interfaces/Storage/StorageBucketsError.tsx": 1,
- "components/interfaces/Storage/StoragePolicies/StoragePoliciesEditor.tsx": 1,
+ "components/interfaces/Storage/StoragePolicies/PolicyEditor/PolicyAllowedOperation.tsx": 1,
+ "components/interfaces/Storage/StoragePolicies/PolicyEditor/PolicyDefinition.tsx": 1,
+ "components/interfaces/Storage/StoragePolicies/PolicyEditor/PolicyEditorFooter.tsx": 1,
+ "components/interfaces/Storage/StoragePolicies/PolicyTemplates/TemplatePreview.tsx": 1,
+ "components/interfaces/Storage/StoragePolicies/PolicyTemplates/TemplatesList.tsx": 1,
"components/interfaces/Storage/StoragePolicies/StoragePoliciesPlaceholder.tsx": 1,
- "components/interfaces/Storage/StoragePolicies/StoragePoliciesReview.tsx": 1,
"components/interfaces/TableGridEditor/DeleteConfirmationDialogs.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnDefaultValue.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx": 1,
@@ -782,8 +748,6 @@
"components/layouts/AuthLayout/AuthLayout.tsx": 1,
"components/layouts/BranchLayout/BranchLayout.tsx": 1,
"components/layouts/DatabaseLayout/DatabaseLayout.tsx": 1,
- "components/layouts/DefaultLayout.tsx": 1,
- "components/layouts/DocsLayout/DocsLayout.tsx": 1,
"components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx": 1,
"components/layouts/EdgeFunctionsLayout/EdgeFunctionsLayout.tsx": 1,
"components/layouts/IntegrationsLayout/IntegrationWindowLayout.tsx": 1,
@@ -791,8 +755,6 @@
"components/layouts/JWTKeys/JWTKeysLayout.tsx": 1,
"components/layouts/LogsLayout/LogsLayout.tsx": 1,
"components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx": 1,
- "components/layouts/Navigation/NavigationBar/MobileViewNav.tsx": 1,
- "components/layouts/Navigation/NavigationBar/NavigationIconLink.tsx": 1,
"components/layouts/Navigation/ProductMenuBar.tsx": 1,
"components/layouts/ObservabilityLayout/ObservabilityLayout.tsx": 1,
"components/layouts/ObservabilityLayout/ObservabilityMenu.tsx": 1,
@@ -805,50 +767,35 @@
"components/layouts/SQLEditorLayout/SQLEditorLayout.tsx": 1,
"components/layouts/StorageLayout/StorageLayout.tsx": 1,
"components/layouts/WizardLayout.tsx": 1,
- "components/to-be-cleaned/KeyMap.tsx": 1,
"components/to-be-cleaned/ProductEmptyState.tsx": 1,
"components/to-be-cleaned/Table.tsx": 1,
"components/ui/AIEditor/ResizableAIWidget.tsx": 1,
- "components/ui/AlertError.tsx": 1,
"components/ui/CardButton.tsx": 1,
"components/ui/Charts/AreaChart.tsx": 1,
"components/ui/Charts/BarChart.tsx": 1,
- "components/ui/Charts/ChartHandler.tsx": 1,
"components/ui/Charts/NoDataPlaceholder.tsx": 1,
"components/ui/Charts/StackedBarChart.tsx": 1,
- "components/ui/CodeEditor/CodeEditor.tsx": 1,
"components/ui/CodeEditor/Providers/BackwardIterator.ts": 1,
"components/ui/CodeEditor/Providers/PgSQLCompletionProvider.ts": 1,
"components/ui/CodeEditor/Providers/PgSQLSignatureHelpProvider.ts": 1,
"components/ui/CopyButton.tsx": 1,
"components/ui/CountdownTimer/CountdownTimerRadial.tsx": 1,
"components/ui/CountdownTimer/CountdownTimerSpan.tsx": 1,
- "components/ui/DateRangePicker.tsx": 1,
- "components/ui/EditorPanel/EditorPanel.tsx": 1,
- "components/ui/Error.tsx": 1,
"components/ui/FormBoxEmpty.tsx": 1,
"components/ui/FunctionSelector.tsx": 1,
- "components/ui/GrafanaPromoBanner.tsx": 1,
"components/ui/InformationBox.tsx": 1,
"components/ui/LoadingOpacity.tsx": 1,
"components/ui/Logs/LogsExplorerHeader.tsx": 1,
- "components/ui/NoPermission.tsx": 1,
"components/ui/Panel.tsx": 1,
"components/ui/PartnerIcon.tsx": 1,
"components/ui/PartnerManagedResource.tsx": 1,
- "components/ui/SchemaSelector.tsx": 1,
"components/ui/ShimmerLine.tsx": 1,
- "components/ui/SparkBar.tsx": 1,
- "components/ui/SqlEditor.tsx": 1,
"data/prefetchers/PrefetchableLink.tsx": 1,
"hooks/analytics/useDbQuery.tsx": 1,
"hooks/analytics/useLogsPreview.tsx": 1,
- "hooks/analytics/useLogsQuery.tsx": 1,
"hooks/analytics/useProjectUsageStats.tsx": 1,
"hooks/analytics/useSingleLog.tsx": 1,
"hooks/analytics/useTimeseriesUnixToIso.ts": 1,
- "hooks/branches/useEdgeFunctionsDiff.ts": 1,
- "hooks/misc/useLatest.ts": 1,
"lib/api/apiWrapper.ts": 1,
"lib/pingPostgrest.ts": 1,
"scripts/codegen.ts": 1
@@ -869,9 +816,7 @@
"components/interfaces/ProjectAPIDocs/FirstLevelNav.tsx": 1,
"components/interfaces/Settings/Logs/LogSelection.tsx": 1,
"components/interfaces/Settings/Logs/LogsFormatters.tsx": 2,
- "components/interfaces/Settings/Logs/PreviewFilterPanelWithUniversal.tsx": 1,
"components/layouts/Navigation/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx": 1,
- "components/layouts/Navigation/NavigationBar/NavigationIconLink.tsx": 1,
"components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorNav.tsx": 3,
"components/layouts/SQLEditorLayout/SQLEditorNavV2/SearchList.tsx": 1,
"components/ui/AIAssistantPanel/MessageMarkdown.tsx": 1,
@@ -881,8 +826,271 @@
"components/ui/Charts/ComposedChart.tsx": 2,
"components/ui/Charts/ComposedChart.utils.tsx": 3,
"components/ui/ErrorBoundary/ErrorBoundary.tsx": 1,
- "components/ui/NoPermission.tsx": 1,
"pages/project/[ref]/observability/database.tsx": 1
- }
+ },
+ "jsx-a11y/aria-props": {},
+ "jsx-a11y/aria-proptypes": {},
+ "jsx-a11y/role-supports-aria-props": {},
+ "jsx-a11y/anchor-has-content": {},
+ "jsx-a11y/control-has-associated-label": {
+ "components/grid/components/editor/DateTimeEditor.tsx": 1,
+ "components/grid/components/editor/NumberEditor.tsx": 1,
+ "components/grid/components/editor/TimeEditor.tsx": 1,
+ "components/grid/components/footer/pagination/Pagination.tsx": 3,
+ "components/grid/components/grid/AddColumn.tsx": 1,
+ "components/grid/components/header/filter/FilterRow.tsx": 1,
+ "components/grid/components/header/sort/SortRow.tsx": 2,
+ "components/interfaces/APIKeys/APIKeyRow.tsx": 1,
+ "components/interfaces/Account/AccessTokens/AccessTokenList.tsx": 1,
+ "components/interfaces/Account/AccessTokens/AccessTokenNewBanner/AccessTokenNewBanner.tsx": 1,
+ "components/interfaces/Account/AccessTokens/Classic/NewTokenButton.tsx": 1,
+ "components/interfaces/Account/AccessTokens/Scoped/Form/Permissions/Permissions.tsx": 1,
+ "components/interfaces/Account/AccessTokens/Scoped/Form/ResourceAccess/ResourceOption.tsx": 1,
+ "components/interfaces/Account/AccessTokens/Scoped/ScopedTokenList.tsx": 1,
+ "components/interfaces/Account/Preferences/AnalyticsSettings.tsx": 1,
+ "components/interfaces/Account/Preferences/DashboardToggle.tsx": 1,
+ "components/interfaces/Account/Preferences/HotkeyToggle.tsx": 1,
+ "components/interfaces/Advisors/CreateRuleSheet.tsx": 1,
+ "components/interfaces/Auth/AuditLogsForm.tsx": 1,
+ "components/interfaces/Auth/AuthProvidersForm/FormField.tsx": 1,
+ "components/interfaces/Auth/BasicAuthSettingsForm.tsx": 4,
+ "components/interfaces/Auth/CustomAuthProviders/CreateOrUpdateCustomProviderSheet.tsx": 1,
+ "components/interfaces/Auth/CustomAuthProviders/CustomAuthProvidersList.tsx": 2,
+ "components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx": 1,
+ "components/interfaces/Auth/Hooks/CreateHookSheet.tsx": 1,
+ "components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx": 1,
+ "components/interfaces/Auth/OAuthApps/CreateOrUpdateOAuthAppSheet.tsx": 2,
+ "components/interfaces/Auth/OAuthApps/NewOAuthAppBanner.tsx": 1,
+ "components/interfaces/Auth/OAuthApps/OAuthAppsList.tsx": 2,
+ "components/interfaces/Auth/OAuthApps/OAuthServerSettingsForm.tsx": 2,
+ "components/interfaces/Auth/Passkeys/PasskeysSettingsForm.tsx": 1,
+ "components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx": 1,
+ "components/interfaces/Auth/RateLimits/RateLimits.tsx": 1,
+ "components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx": 2,
+ "components/interfaces/Auth/SmtpForm/SmtpForm.tsx": 1,
+ "components/interfaces/Auth/Users/UserPanel.tsx": 1,
+ "components/interfaces/Auth/Users/UsersFooter.tsx": 1,
+ "components/interfaces/Auth/Users/UsersGridComponents.tsx": 1,
+ "components/interfaces/Auth/Users/UsersSearch.tsx": 1,
+ "components/interfaces/BranchManagement/CreateBranchModal.tsx": 1,
+ "components/interfaces/BranchManagement/Overview.tsx": 2,
+ "components/interfaces/BranchManagement/WorkflowLogsCard.tsx": 1,
+ "components/interfaces/Branching/MergeRequest.tsx": 1,
+ "components/interfaces/ConnectSheet/ConnectConfigSection.tsx": 1,
+ "components/interfaces/Database/EnumeratedTypes/EnumeratedTypeValueRow.tsx": 1,
+ "components/interfaces/Database/Extensions/ExtensionRow.tsx": 1,
+ "components/interfaces/Database/Functions/CreateFunction/FunctionEditor.tsx": 1,
+ "components/interfaces/Database/Functions/CreateFunction/index.tsx": 2,
+ "components/interfaces/Database/Hooks/HooksList/HookList.tsx": 1,
+ "components/interfaces/Database/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx": 1,
+ "components/interfaces/Database/Policies/PolicyTableRow/PolicyRow.tsx": 1,
+ "components/interfaces/Database/Privileges/PrivilegesTable.tsx": 2,
+ "components/interfaces/Database/Publications/PublicationSkeleton.tsx": 1,
+ "components/interfaces/Database/Publications/PublicationsList.tsx": 1,
+ "components/interfaces/Database/Publications/PublicationsTableItem.tsx": 1,
+ "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AnalyticsBucket/Fields.tsx": 2,
+ "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DuckLake/Fields.tsx": 2,
+ "components/interfaces/Database/Replication/DestinationPanel/DestinationForm/Snowflake/Fields.tsx": 1,
+ "components/interfaces/Database/Replication/Destinations.tsx": 2,
+ "components/interfaces/Database/Replication/ReadReplicas/ReadReplicaRow.tsx": 1,
+ "components/interfaces/Database/Replication/ReplicationPipelineStatus/ReplicationPipelineStatus.tsx": 1,
+ "components/interfaces/Database/Replication/RowMenu.tsx": 1,
+ "components/interfaces/Database/Roles/CreateRolePanel.tsx": 2,
+ "components/interfaces/Database/Roles/RoleRow.tsx": 1,
+ "components/interfaces/Database/Tables/ColumnList.tsx": 1,
+ "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx": 1,
+ "components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet.tsx": 1,
+ "components/interfaces/Functions/EdgeFunctionSecrets/EditSecretSheet.tsx": 1,
+ "components/interfaces/Home/ProjectList/ProjectCard.tsx": 1,
+ "components/interfaces/HomePageActions.tsx": 1,
+ "components/interfaces/Integrations/CronJobs/CreateCronJobSheet/CronJobScheduleSection.tsx": 1,
+ "components/interfaces/Integrations/CronJobs/CronJobTableCell.tsx": 2,
+ "components/interfaces/Integrations/CronJobs/CronJobsTab.Header.tsx": 1,
+ "components/interfaces/Integrations/Queues/CreateQueueSheet/RlsSection.tsx": 1,
+ "components/interfaces/Integrations/Queues/QueuesSettings.tsx": 1,
+ "components/interfaces/Integrations/Queues/QueuesTab.tsx": 1,
+ "components/interfaces/Integrations/Queues/SingleQueue/MessageDetailsPanel.tsx": 1,
+ "components/interfaces/Integrations/Queues/SingleQueue/QueueSettings.tsx": 1,
+ "components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx": 2,
+ "components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx": 1,
+ "components/interfaces/Integrations/Wrappers/CreateWrapperSheet.tsx": 2,
+ "components/interfaces/Integrations/Wrappers/EditWrapperSheet.tsx": 2,
+ "components/interfaces/JwtSecrets/jwt-secret-keys-table/signing-key-row.tsx": 1,
+ "components/interfaces/Linter/LintDetail.tsx": 1,
+ "components/interfaces/Linter/LinterDataGrid.tsx": 1,
+ "components/interfaces/LogDrains/LogDrainDestinationSheetForm.tsx": 3,
+ "components/interfaces/LogDrains/LogDrainsCard.tsx": 1,
+ "components/interfaces/LogDrains/LogDrainsList.tsx": 1,
+ "components/interfaces/LogDrains/OrgAuditLogDrains.tsx": 1,
+ "components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingMetric.tsx": 1,
+ "components/interfaces/Organization/BillingSettings/BillingBreakdown/ComputeMetric.tsx": 1,
+ "components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerDataForm.tsx": 1,
+ "components/interfaces/Organization/BillingSettings/Subscription/ExitSurveyModal.tsx": 1,
+ "components/interfaces/Organization/BillingSettings/Subscription/UpgradeModal.tsx": 1,
+ "components/interfaces/Organization/NewOrg/NewOrgForm.tsx": 1,
+ "components/interfaces/Organization/OAuthApps/AuthorizedAppRow.tsx": 1,
+ "components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx": 1,
+ "components/interfaces/Organization/OAuthApps/OAuthApps.tsx": 1,
+ "components/interfaces/Organization/OAuthApps/PublishAppSidePanel/index.tsx": 1,
+ "components/interfaces/Organization/PrivateApps/Apps/AppsList/AppsListTable.tsx": 1,
+ "components/interfaces/Organization/PrivateApps/Apps/CreateAppSheet/CreateAppSheet.tsx": 3,
+ "components/interfaces/Organization/PrivateApps/Apps/ViewAppSheet/ViewAppSheet.tsx": 1,
+ "components/interfaces/Organization/SSO/JoinOrganizationOnSignup.tsx": 1,
+ "components/interfaces/Organization/SSO/SSOConfig.tsx": 2,
+ "components/interfaces/Organization/SSO/SSODomains.tsx": 1,
+ "components/interfaces/Organization/SSO/SSOMetadata.tsx": 1,
+ "components/interfaces/Organization/SecuritySettings.tsx": 1,
+ "components/interfaces/Organization/TeamSettings/InviteMemberButton.tsx": 1,
+ "components/interfaces/Organization/TeamSettings/MemberActions.tsx": 1,
+ "components/interfaces/Organization/TeamSettings/UpdateRolesPanel/UpdateRolesPanel.tsx": 1,
+ "components/interfaces/Platform/Webhooks/PlatformWebhooksEndpointList.tsx": 1,
+ "components/interfaces/Platform/Webhooks/PlatformWebhooksEndpointSheet.tsx": 1,
+ "components/interfaces/Platform/Webhooks/PlatformWebhooksPage.tsx": 1,
+ "components/interfaces/ProjectAPIDocs/SecondLevelNav.Layout.tsx": 1,
+ "components/interfaces/ProjectCreation/HighAvailabilityInput.tsx": 1,
+ "components/interfaces/QueryInsights/QueryInsightsTable/QueryInsightsTable.tsx": 1,
+ "components/interfaces/QueryInsights/hooks/useQueryInsightsTableColumns.tsx": 2,
+ "components/interfaces/QueryPerformance/QueryPerformanceGrid.tsx": 1,
+ "components/interfaces/QueryPerformance/WithStatements/WithStatements.tsx": 2,
+ "components/interfaces/QueryPerformance/components/FilterInput.tsx": 1,
+ "components/interfaces/Realtime/Inspector/ChooseChannelPopover.tsx": 1,
+ "components/interfaces/Realtime/Inspector/RealtimeFilterPopover/index.tsx": 3,
+ "components/interfaces/Realtime/RealtimeSettings.tsx": 2,
+ "components/interfaces/Reports/ReportFilterBar.tsx": 1,
+ "components/interfaces/Reports/ReportWidget.tsx": 1,
+ "components/interfaces/RoleImpersonationSelector/RoleImpersonationRadio.tsx": 1,
+ "components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx": 1,
+ "components/interfaces/SQLEditor/OngoingQueriesPanel.tsx": 2,
+ "components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx": 1,
+ "components/interfaces/Settings/API/DataApiEnableSwitchForm.tsx": 1,
+ "components/interfaces/Settings/API/ExposedFunctionSelector.tsx": 1,
+ "components/interfaces/Settings/API/ExposedSchemaSelector.tsx": 1,
+ "components/interfaces/Settings/API/ExposedTableSelector.tsx": 1,
+ "components/interfaces/Settings/API/PostgrestConfig.tsx": 1,
+ "components/interfaces/Settings/Database/ConnectionLogging.tsx": 2,
+ "components/interfaces/Settings/Database/JitDatabaseAccess/JitDbAccessConfiguration.tsx": 1,
+ "components/interfaces/Settings/Database/SSLConfiguration.tsx": 1,
+ "components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode.tsx": 1,
+ "components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal.tsx": 1,
+ "components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx": 1,
+ "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx": 3,
+ "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx": 2,
+ "components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx": 1,
+ "components/interfaces/Settings/Integrations/AWSPrivateLink/AWSPrivateLinkAccountItem.tsx": 1,
+ "components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx": 3,
+ "components/interfaces/Settings/Integrations/VercelIntegration/VercelIntegrationConnectionForm.tsx": 3,
+ "components/interfaces/Settings/Logs/MultiSelectActionBar.tsx": 1,
+ "components/interfaces/Settings/Logs/PreviewFilterPanel.tsx": 1,
+ "components/interfaces/SignIn/ResetPasswordForm.tsx": 2,
+ "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CreateTable/CreateTableInstructionsDialog.tsx": 1,
+ "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/CreateTable/CreateTableSheet.tsx": 1,
+ "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/DecryptedReadOnlyInput.tsx": 2,
+ "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/TableRowComponent.tsx": 3,
+ "components/interfaces/Storage/AnalyticsBuckets/AnalyticsBucketDetails/NamespaceWithTables/index.tsx": 1,
+ "components/interfaces/Storage/BucketFilePickerDialog/BucketFilePickerHeader.tsx": 4,
+ "components/interfaces/Storage/CreateBucketModal.tsx": 3,
+ "components/interfaces/Storage/EditBucketModal.tsx": 3,
+ "components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx": 2,
+ "components/interfaces/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx": 1,
+ "components/interfaces/Storage/StorageExplorer/FileExplorerRowEditing.tsx": 2,
+ "components/interfaces/Storage/StorageExplorer/MoveItemsModal.tsx": 1,
+ "components/interfaces/Storage/StoragePolicies/StoragePoliciesBucketsSection.tsx": 1,
+ "components/interfaces/Storage/StorageSettings/S3Connection.tsx": 1,
+ "components/interfaces/Storage/StorageSettings/StorageCredItem.tsx": 1,
+ "components/interfaces/Storage/StorageSettings/StorageSettings.tsx": 1,
+ "components/interfaces/Storage/VectorBuckets/CreateVectorTableSheet.tsx": 1,
+ "components/interfaces/Storage/VectorBuckets/VectorBucketDetails/index.tsx": 1,
+ "components/interfaces/Support/AttachmentUpload.tsx": 1,
+ "components/interfaces/Support/DashboardLogsToggle.tsx": 1,
+ "components/interfaces/Support/SupportAccessToggle.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx": 4,
+ "components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/ForeignRowSelector.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/ForeignRowSelector/Pagination.tsx": 2,
+ "components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImportPreview.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ApiAccessToggle.tsx": 1,
+ "components/interfaces/UnifiedLogs/ServiceFlow/components/ServiceFlowPanelControls.tsx": 3,
+ "components/interfaces/UnifiedLogs/ServiceFlow/components/shared/DetailRow.tsx": 1,
+ "components/layouts/Navigation/FloatingMobileToolbar/FloatingMobileToolbar.tsx": 2,
+ "components/layouts/Navigation/LayoutHeader/FeedbackDropdown/FeedbackWidget.tsx": 2,
+ "components/layouts/Navigation/NavigationBar/MobileNavigationBar.tsx": 1,
+ "components/layouts/ObservabilityLayout/ObservabilityMenuItem.tsx": 1,
+ "components/layouts/ProjectLayout/PauseFailedState.tsx": 1,
+ "components/layouts/ProjectLayout/RestoreFailedState.tsx": 1,
+ "components/layouts/TableEditorLayout/EntityListItem.tsx": 1,
+ "components/ui/AIAssistantPanel/AIAssistantChatSelector.tsx": 4,
+ "components/ui/AIAssistantPanel/CollapsibleCodeBlock.tsx": 1,
+ "components/ui/AdvisorPanel/AdvisorPanelBody.tsx": 1,
+ "components/ui/AdvisorPanel/AdvisorSignalDetail.tsx": 1,
+ "components/ui/AiAssistantDropdown.tsx": 1,
+ "components/ui/Charts/ReportSettings.tsx": 2,
+ "components/ui/EditorPanel/EditorPanel.tsx": 1,
+ "components/ui/Logs/LogsExplorerHeader.tsx": 1,
+ "components/ui/PasswordStrengthBar.tsx": 1,
+ "components/ui/Resource/ResourceItem.tsx": 1,
+ "pages/project/[ref]/auth/templates/[templateId].tsx": 1,
+ "pages/project/[ref]/branches/merge-requests.tsx": 1,
+ "pages/project/[ref]/database/policies.tsx": 1,
+ "pages/project/[ref]/functions/index.tsx": 1,
+ "pages/project/[ref]/observability/auth.tsx": 1,
+ "pages/project/[ref]/observability/database.tsx": 1,
+ "pages/project/[ref]/observability/edge-functions.tsx": 1,
+ "pages/project/[ref]/observability/postgrest.tsx": 1,
+ "pages/project/[ref]/observability/realtime.tsx": 1,
+ "pages/project/[ref]/observability/storage.tsx": 1,
+ "pages/project/[ref]/settings/log-drains.tsx": 1
+ },
+ "jsx-a11y/label-has-associated-control": {
+ "components/grid/components/header/sort/SortRow.tsx": 1,
+ "components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx": 1,
+ "components/interfaces/Auth/RedirectUrls/AddNewURLModal.tsx": 1,
+ "components/interfaces/BranchManagement/CreateBranchModal.tsx": 2,
+ "components/interfaces/BranchManagement/EditBranchModal.tsx": 1,
+ "components/interfaces/Docs/Param.tsx": 3,
+ "components/interfaces/Docs/ResourceContent.tsx": 1,
+ "components/interfaces/Docs/RpcContent.tsx": 1,
+ "components/interfaces/Integrations/Wrappers/WrapperTableEditor.tsx": 1,
+ "components/interfaces/Organization/PrivateApps/Apps/CreateAppSheet/CreateAppSheet.tsx": 1,
+ "components/interfaces/Platform/Webhooks/PlatformWebhooksEndpointSheet.tsx": 1,
+ "components/interfaces/Platform/Webhooks/PlatformWebhooksPage.tsx": 1,
+ "components/interfaces/Realtime/Inspector/ChooseChannelPopover.tsx": 1,
+ "components/interfaces/Reports/v2/ReportsNumericFilter.tsx": 2,
+ "components/interfaces/SQLEditor/MoveQueryModal.tsx": 1,
+ "components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx": 2,
+ "components/interfaces/Settings/General/CustomDomainConfig/DNSTableHeaders.tsx": 2,
+ "components/interfaces/Settings/Logs/LogsFormatters.tsx": 2,
+ "components/interfaces/Storage/BucketFilePickerDialog/BucketFilePickerPreviewPane.tsx": 2,
+ "components/interfaces/Storage/StorageExplorer/PreviewPane.tsx": 2,
+ "components/interfaces/Storage/StoragePolicies/PolicyTemplates/TemplatePreview.tsx": 1,
+ "components/interfaces/Storage/VectorBuckets/CreateVectorTableSheet.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnDefaultValue.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx": 1,
+ "components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx": 1,
+ "components/ui/Charts/ComposedChart.tsx": 1,
+ "components/ui/Charts/ComposedChart.utils.tsx": 2
+ },
+ "jsx-a11y/aria-role": {},
+ "jsx-a11y/no-redundant-roles": {
+ "components/interfaces/Organization/BillingSettings/Subscription/EnterpriseCard.tsx": 1,
+ "components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx": 1,
+ "components/interfaces/RoleImpersonationSelector/UserImpersonationSelector.tsx": 2
+ },
+ "jsx-a11y/no-aria-hidden-on-focusable": {},
+ "jsx-a11y/tabindex-no-positive": {},
+ "jsx-a11y/anchor-is-valid": {
+ "components/interfaces/BranchManagement/Overview.tsx": 1,
+ "components/interfaces/Organization/ProjectClaim/confirm.tsx": 1,
+ "components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx": 2,
+ "components/interfaces/SignIn/SignInMfaForm.tsx": 1,
+ "components/interfaces/TableGridEditor/GridHeaderActions.tsx": 1,
+ "components/layouts/Navigation/LayoutHeader/BreadcrumbsView.tsx": 1
+ },
+ "jsx-a11y/heading-has-content": {
+ "components/interfaces/Database/Backups/PITR/PITRForm.tsx": 1,
+ "components/layouts/Scaffold.tsx": 2
+ },
+ "jsx-a11y/no-distracting-elements": {}
}
}
diff --git a/apps/studio/TANSTACK_MIGRATION.md b/apps/studio/TANSTACK_MIGRATION.md
index 2d49fd90c13..728290838b2 100644
--- a/apps/studio/TANSTACK_MIGRATION.md
+++ b/apps/studio/TANSTACK_MIGRATION.md
@@ -23,6 +23,19 @@ Throughout this migration both runtimes coexist in the same workspace:
they stay until the cleanup pass, regardless of how many routes have
moved.
+**PR guardrail — mirror page edits into their route**
+
+Because both runtimes ship at once, any change to a `pages/...` file can
+leave its `routes/...` mirror stale. A CodeRabbit `path_instructions`
+rule (`.coderabbit.yaml`, scoped to `apps/studio/pages/**`) posts a
+review reminder on every PR that touches a page, prompting the author to
+check whether the corresponding route needs the same change. It's a
+verify-not-block reminder: pure body edits on re-export (Path A) pages
+propagate automatically, but layout/`getLayout`, `staticData` props,
+`withAuth`, redirect-path, or brand-new-page changes must be mirrored by
+hand. This guardrail is temporary scaffolding — remove it in the cleanup
+pass when `pages/**` is deleted (tracked by FE-3106).
+
**Strategy — minimum-diff re-export**
The goal is to flip URL ownership to TanStack without rewriting page internals yet. For each page we pick one of two paths:
@@ -481,6 +494,80 @@ Keep this plugin even after migration — it's not a Next-related shim,
it's general protection against this entire class of bug. Just clear
the allowlist when the underlying cycle is gone.
+### `@sentry/nextjs` → `@sentry/react` alias
+
+`resolve.alias` in `vite.config.ts` rewrites the bare `@sentry/nextjs`
+import to `compat/sentry-nextjs.ts`, which re-exports `@sentry/react`
+(the same-version package `@sentry/nextjs` wraps on the client) plus
+explicit stand-ins for the Next-only APIs (`captureRouterTransitionStart`,
+`captureRequestError`, `withSentryConfig`).
+
+Why: `@sentry/nextjs`'s client entry imports
+`next/dist/shared/lib/constants`, whose module scope evaluates
+`...(process?.features?.typescript ? ['next.config.mts'] : [])`.
+Optional chaining does **not** guard an undeclared `process` identifier,
+so every built client chunk containing it (table editor was the canary)
+threw `ReferenceError: process is not defined` at module load. Dev was
+unaffected (dev pipeline shims `process`), so it only surfaced in the
+production/test build.
+
+The alias also made the previous `@sentry/nextjs` SSR workarounds
+(`ssr.noExternal` entry + `ssr.optimizeDeps.include`) obsolete — the id
+is rewritten before SSR resolution, and `@sentry/react` ships real ESM.
+App source keeps importing `@sentry/nextjs` so the Next build
+(`build:next`) is untouched; drop the alias + shim together with the
+Next build when the migration is done (switch imports to
+`@sentry/react` directly).
+
+### GraphiQL Monaco workers: `setup-workers/webpack` → `setup-workers/vite`
+
+App source (`GraphiQLTab.tsx`) imports `graphiql/setup-workers/webpack`,
+which registers `MonacoEnvironment.getWorker` using
+`new Worker(new URL('monaco-editor/...', import.meta.url))` — the URL form
+webpack/turbopack rewrites at build time. Vite doesn't rewrite bare module
+specifiers inside `new URL(..., import.meta.url)`, so under the TanStack
+build the worker URLs 404'd and Monaco fell back to running the `json`,
+`editorWorkerService` and `graphql` workers on the main thread ("Could not
+create web worker(s). Falling back to loading web worker code in main
+thread" in the console).
+
+The `graphiqlViteWorkers` plugin in `vite.config.ts` resolves that import
+to graphiql's own `setup-workers/vite` variant (same three workers via
+Vite `?worker` imports) in client builds; SSR resolution is untouched. The
+import specifier stays `.../webpack` in app source so the Next build keeps
+working. The whole setup-workers chain is also in `optimizeDeps.exclude` —
+the Rolldown dep optimizer can't load `?worker` ids
+(`UNLOADABLE_DEPENDENCY`), so the modules go through the normal transform
+pipeline where Vite's built-in worker plugin handles them. Drop the plugin
+and the exclude, and switch the import to `graphiql/setup-workers/vite`,
+when the Next build goes away.
+
+### Raw-text imports: `*.md` + `public/deno/*.d.ts` (`rawTextLoader`)
+
+Next's raw-loader rules (next.config.ts `turbopack.rules`) serve `*.md`
+files and the Deno typings `public/deno/edge-runtime.d.ts` /
+`public/deno/lib.deno.d.ts` as JS modules whose default export is the
+file's text. The `rawTextLoader` plugin in `vite.config.ts` mirrors that
+for the Vite pipeline:
+
+- `*.md` — plain `transform` (used by
+ `static-data/integrations/*/overview.md` via the literal-import registry
+ in `static-data/integrations/overviews.ts`).
+- The two Deno `.d.ts` files (used by `components/ui/AIEditor` as Monaco
+ extra libs for edge-function editors) — an exact-specifier allowlist
+ resolved to `\0`-virtual ids and served from a `load` hook. They can't go
+ through `transform`: Rolldown's native dep scanner skips JS plugin hooks
+ and hard-fails parsing TS _declaration_ syntax (`get stdin(): ...;`) as
+ runtime TS, which killed dependency pre-bundling wholesale. The previous
+ `/* @vite-ignore */` hack kept the scanner away but also meant the
+ imports failed at runtime, silently dropping Deno type hints in the
+ TanStack build. Do NOT widen the allowlist to `*.d.ts` — hijacking
+ declaration-file resolution globally would corrupt packages that ship
+ `.d.ts` next to their JS. The `as string` casts on the import specifiers
+ in `AIEditor/index.tsx` keep tsc from resolving the `.d.ts` files as
+ declaration files (TS2846) while erasing to plain literals both bundlers
+ statically analyze.
+
### Other build-side migration changes
- `pnpm-workspace.yaml` catalog now includes `@tanstack/react-router`,
@@ -511,4 +598,5 @@ the allowlist when the underlying cycle is gone.
- Lift `manualChunks` pins (`class-variance-authority`, `lucide-react`, `react-vendor`) once the structural fix in `packages/ui` lands — see CIRCULAR_IMPORTS.md. Keep `assertNoChunkCycles`; just clear `KNOWN_CHUNK_CYCLES`.
- Delete `pages/_app.tsx`, `pages/_document.tsx`, `pages/_error.jsx`, `pages/500.tsx`, `pages/404.tsx` (Next-only catch-alls; TanStack equivalents on `__root.tsx`).
- Drop the `dev:next` / `build:next` / `start:next` scripts from `apps/studio/package.json` once we're committed to TanStack.
+- Remove the `apps/studio/pages/**` `path_instructions` guardrail entry from `.coderabbit.yaml` (added in FE-3423; remove it as part of this FE-3106 cleanup) — it's only useful while both runtimes coexist.
- Delete this file.
diff --git a/apps/studio/api/server.js b/apps/studio/api/server.js
new file mode 100644
index 00000000000..3d12a8e55b3
--- /dev/null
+++ b/apps/studio/api/server.js
@@ -0,0 +1,25 @@
+// STUDIO_FRAMEWORK gates whether this function actually serves the TanStack
+// SSR handler. Vercel auto-detects every file under /api as a Function
+// regardless of the framework preset (vercel.com/docs/functions), so we
+// can't keep this file from being deployed in the Next.js prod build — we
+// just make it inert when the env var is unset.
+const isTanstack = process.env.STUDIO_FRAMEWORK === 'tanstack'
+
+// Computed path keeps `dist/server/server.js` out of Vercel's function
+// bundler's static analysis. In the Next.js prod deploy the `dist/` tree
+// doesn't exist, but Vercel still bundles this file because it lives under
+// `api/`. With the .join() the bundler treats the import as runtime-only
+// and the missing dist/ isn't a build error. In TanStack mode the SSR
+// bundle is shipped into the function via the `functions['api/server.js']
+// .includeFiles` config in vercel.ts.
+const tanstackEntry = ['..', 'dist', 'server', 'server.js'].join('/')
+
+const handler = isTanstack
+ ? (await import(tanstackEntry)).default
+ : { fetch: () => new Response('Not Found', { status: 404 }) }
+
+// Vercel's Web API handler convention: export an object with `fetch(request)`.
+// TanStack's server build is already shaped that way — default-export it
+// verbatim and Vercel hands us a real Web Request.
+// eslint-disable-next-line no-restricted-exports
+export default handler
diff --git a/apps/studio/compat/next/link.tsx b/apps/studio/compat/next/link.tsx
index c662e623466..cbb68e892ce 100644
--- a/apps/studio/compat/next/link.tsx
+++ b/apps/studio/compat/next/link.tsx
@@ -11,6 +11,8 @@ import {
type Ref,
} from 'react'
+import { splitInternalUrl } from '@/lib/internal-url'
+
// Next's Link accepts either a string `href` or a `UrlObject`
// ({pathname, query, hash}). Workspace source does both — flatten
// `UrlObject` into `pathname?search#hash` first so the TanStack `to`
@@ -62,92 +64,14 @@ function resolveHref(href: string | UrlObject): string {
return `${pathname}${search}${hash}`
}
-// Inlined at build time via Vite's `define`. Must agree with Vite `base`
-// and `tanstackStart({ router: { basepath } })`. Empty string when no
-// basePath is configured. Used to strip a duplicate prefix in
-// `splitInternalUrl` below — see the comment there.
-const NEXT_PUBLIC_BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? ''
-
// TanStack Link's `to` prop is a route-pattern path; query params and hash
-// must be passed separately via `search` / `hash`. Studio code (and Next's
-// own contract) routinely passes one of three href shapes:
-// 1. a relative path like `/project/abc/editor/123?schema=public`
-// 2. a same-origin absolute URL produced by `new URL(...).toString()`,
-// e.g. `http://localhost:8082/project/abc/editor/123?schema=public`
-// (this is what `buildTableEditorUrl` does)
-// 3. a genuinely external URL like `https://supabase.com/docs`.
-//
-// If we forward any of these straight through to TanStack as `to`, TanStack
-// either fails to match a known route pattern (#1 with query) or treats
-// the whole thing as external (#2) and falls back to native browser
-// navigation — which the user sees as a full page reload.
-//
-// Split into three parts: pathname, search, hash. Same-origin absolute
-// URLs are normalised to a relative path. Cross-origin URLs are left
-// alone so TanStack's external-link path handles them.
-//
-// basePath quirk: TanStack's `to` is **basepath-relative** — given
-// `basepath: '/dashboard'` and `to: '/foo'`, TanStack builds the href
-// `/dashboard/foo`. Next's contract treats `href` as the **full path
-// from app root including basePath**, and studio code routinely
-// pre-prefixes BASE_PATH (e.g. `buildTableEditorUrl` calls
-// `new URL(`${BASE_PATH}/project/.../editor/...`, location.origin)`).
-// Forwarding the BASE_PATH-prefixed pathname as `to` makes TanStack
-// double-prefix it (`/dashboard/dashboard/project/...`). Strip the
-// basePath when we see it, so what we hand TanStack is always
-// basepath-relative.
-function splitInternalUrl(url: string): {
- to: string
- search?: Record
- hash?: string
-} {
- // Try to detect cross-origin absolute URLs cheaply before paying for a
- // full parse. Protocol-relative URLs (`//host/...`) are always external.
- if (url.startsWith('//')) {
- return { to: url }
- }
-
- // Use the document origin as the parse base so relative inputs resolve.
- // SSR has no `location`; fall back to a placeholder host that won't ever
- // collide with a real one.
- const base =
- typeof window !== 'undefined' && window.location ? window.location.origin : 'http://_/'
-
- let parsed: URL
- try {
- parsed = new URL(url, base)
- } catch {
- return { to: url }
- }
-
- // Cross-origin → leave for TanStack to handle as external.
- if (
- typeof window !== 'undefined' &&
- window.location &&
- parsed.origin !== window.location.origin
- ) {
- return { to: url }
- }
-
- let pathname = parsed.pathname
- // Strip a leading basePath segment so we hand TanStack a basepath-
- // relative path. Match `/dashboard` exactly OR `/dashboard/...`; don't
- // strip a coincidental prefix like `/dashboard-other`.
- if (
- NEXT_PUBLIC_BASE_PATH &&
- (pathname === NEXT_PUBLIC_BASE_PATH || pathname.startsWith(`${NEXT_PUBLIC_BASE_PATH}/`))
- ) {
- pathname = pathname.slice(NEXT_PUBLIC_BASE_PATH.length) || '/'
- }
-
- const search = Object.fromEntries(parsed.searchParams)
- const hash = parsed.hash || undefined
- return {
- to: pathname,
- search: Object.keys(search).length > 0 ? search : undefined,
- hash,
- }
-}
+// must be passed separately via `search` / `hash`. If we forward an href
+// straight through to TanStack as `to`, TanStack either mangles the query
+// (path interpolation percent-decodes it and strips control characters) or
+// treats a same-origin absolute URL as external and falls back to native
+// browser navigation — which the user sees as a full page reload. Splitting
+// (plus origin/basePath normalisation) lives in `splitInternalUrl`
+// (@/lib/internal-url), shared with the next/router shim.
// Next: prefetch=true|"auto" → eagerly preload; false → never; default
// in production = true. TanStack's preload values are "intent" (on hover/
diff --git a/apps/studio/compat/next/navigation.ts b/apps/studio/compat/next/navigation.ts
index 576693a1d9b..6b5da9328aa 100644
--- a/apps/studio/compat/next/navigation.ts
+++ b/apps/studio/compat/next/navigation.ts
@@ -3,9 +3,12 @@ import {
useMatches,
useParams as useTanStackParams,
useRouter as useTanStackRouter,
+ type AnyRouter,
} from '@tanstack/react-router'
import { useMemo } from 'react'
+import { splitInternalUrl } from '@/lib/internal-url'
+
// `next/navigation` is the App Router hook surface (Next 13+). Studio is
// still pages-based, so most of these are entry-points that arrive via
// stray imports or shared packages — we keep the surface comprehensive
@@ -29,13 +32,27 @@ export function useRouter() {
return useMemo(
() => ({
+ // Split query/hash out of the href — embedding them in TanStack's
+ // `to` runs the whole string through path interpolation, which
+ // percent-decodes it and strips control characters from query values
+ // (see @/lib/internal-url). `search: {}` / `hash: ''` clear stale
+ // state, matching Next's full-href navigation semantics.
+ //
+ // The `` type arguments opt out of the registered
+ // route tree's strict typing: Next-style hrefs are free-form strings
+ // that can't satisfy the route-path union at compile time.
push: (href: string, _options?: NavigateOptions) => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- router.navigate({ to: href as any })
+ const { to, search, hash } = splitInternalUrl(href)
+ router.navigate({ to, search: search ?? {}, hash: hash ?? '' })
},
replace: (href: string, _options?: NavigateOptions) => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- router.navigate({ to: href as any, replace: true })
+ const { to, search, hash } = splitInternalUrl(href)
+ router.navigate({
+ to,
+ search: search ?? {},
+ hash: hash ?? '',
+ replace: true,
+ })
},
// App Router's refresh() refetches the current route's data without
// re-mounting the React tree. Closest TanStack equivalent is
@@ -50,10 +67,14 @@ export function useRouter() {
if (typeof window !== 'undefined') window.history.forward()
},
prefetch: (href: string, _options?: PrefetchOptions) => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- router.preloadRoute({ to: href as any }).catch(() => {
- // Match Next's fire-and-forget contract.
- })
+ const { to, search, hash } = splitInternalUrl(href)
+ // `` (TFrom, TTo) loosens `to` to a plain string for
+ // the same free-form-href reason as `navigate` above.
+ router
+ .preloadRoute({ to, search: search ?? {}, hash: hash ?? '' })
+ .catch(() => {
+ // Match Next's fire-and-forget contract.
+ })
},
}),
[router]
diff --git a/apps/studio/compat/next/router.test.ts b/apps/studio/compat/next/router.test.ts
index f5b3aa67323..16c0ad8a56f 100644
--- a/apps/studio/compat/next/router.test.ts
+++ b/apps/studio/compat/next/router.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
-import { resolveUrl } from './router'
+import { resolveSearchOrHashOnlyTarget, resolveUrl, withDefaultPathname } from './router'
describe('resolveUrl (next/router compat shim)', () => {
it('returns string URLs untouched', () => {
@@ -58,3 +58,79 @@ describe('resolveUrl (next/router compat shim)', () => {
).toBe('/project/x?a=1')
})
})
+
+describe('resolveSearchOrHashOnlyTarget (next/router compat shim)', () => {
+ it('prefixes the current pathname on a ?-only target', () => {
+ // The regression we fixed: `push({ query })` with no pathname produced a
+ // relative `?...` that TanStack resolved by appending — landing on
+ // `/advisors/security/?preset=...` (trailing slash injected).
+ expect(resolveSearchOrHashOnlyTarget('?preset=x', '/advisors/security')).toBe(
+ '/advisors/security?preset=x'
+ )
+ })
+
+ it('prefixes the current pathname on a #-only target', () => {
+ expect(resolveSearchOrHashOnlyTarget('#invoices', '/org/slug/billing')).toBe(
+ '/org/slug/billing#invoices'
+ )
+ })
+
+ it('strips a trailing slash from the current pathname, but keeps the root "/"', () => {
+ expect(resolveSearchOrHashOnlyTarget('?a=1', '/auth/providers/')).toBe('/auth/providers?a=1')
+ expect(resolveSearchOrHashOnlyTarget('?a=1', '/')).toBe('/?a=1')
+ expect(resolveSearchOrHashOnlyTarget('?a=1', '')).toBe('/?a=1')
+ })
+
+ it('leaves targets with a pathname untouched', () => {
+ expect(resolveSearchOrHashOnlyTarget('/project/abc?x=1', '/elsewhere')).toBe('/project/abc?x=1')
+ expect(resolveSearchOrHashOnlyTarget('/project/abc', '/elsewhere')).toBe('/project/abc')
+ })
+})
+
+describe('withDefaultPathname (next/router compat shim)', () => {
+ it('fills a missing pathname from the current route pattern so path params are consumed', () => {
+ // The regression we fixed: `push({ query: { ...router.query, filter } })`
+ // with no pathname leaked `ref`/`id` into the query string
+ // (`/editor/17597/?ref=...&id=17597&filter=...`) because nothing
+ // interpolated them back into the path.
+ const target = withDefaultPathname(
+ { query: { ref: 'abc', id: '17597', filter: 'note:eq:hi' } },
+ '/project/[ref]/editor/[id]',
+ { ref: 'abc', id: '17597' }
+ )
+ expect(resolveUrl(target)).toBe('/project/abc/editor/17597?filter=note%3Aeq%3Ahi')
+ })
+
+ it('backfills params the caller omitted from the current route params', () => {
+ const target = withDefaultPathname(
+ { query: { preset: 'WARN' } },
+ '/project/[ref]/advisors/security',
+ { ref: 'abc' }
+ )
+ expect(resolveUrl(target)).toBe('/project/abc/advisors/security?preset=WARN')
+ })
+
+ it('caller-provided query values win over backfilled params', () => {
+ const target = withDefaultPathname({ query: { ref: 'other', page: '2' } }, '/project/[ref]', {
+ ref: 'abc',
+ })
+ expect(resolveUrl(target)).toBe('/project/other?page=2')
+ })
+
+ it('never backfills the TanStack _splat param', () => {
+ const target = withDefaultPathname({ query: { a: '1' } }, '/org/_/[[...routeSlug]]', {
+ _splat: 'x/y',
+ })
+ expect(resolveUrl(target)).toBe('/org/_/?a=1')
+ })
+
+ it('leaves string URLs, explicit pathnames, and raw-string queries untouched', () => {
+ expect(withDefaultPathname('/x?a=1', '/p/[ref]', { ref: 'r' })).toBe('/x?a=1')
+ expect(
+ withDefaultPathname({ pathname: '/y', query: { a: '1' } }, '/p/[ref]', { ref: 'r' })
+ ).toEqual({ pathname: '/y', query: { a: '1' } })
+ expect(withDefaultPathname({ query: 'a=1' }, '/p/[ref]', { ref: 'r' })).toEqual({
+ query: 'a=1',
+ })
+ })
+})
diff --git a/apps/studio/compat/next/router.ts b/apps/studio/compat/next/router.ts
index 479b4234e84..d2ae293151c 100644
--- a/apps/studio/compat/next/router.ts
+++ b/apps/studio/compat/next/router.ts
@@ -4,10 +4,12 @@ import {
useParams,
useSearch,
useRouter as useTanStackRouter,
+ type AnyRouter,
} from '@tanstack/react-router'
import { useMemo } from 'react'
import { getRouterEventsProxy } from './_router-events'
+import { splitInternalUrl } from '@/lib/internal-url'
// Next's pages-router exposes `router.pathname` as the route *pattern*
// (e.g. `/project/[ref]/sql/[id]`), not the resolved URL. TanStack's
@@ -107,6 +109,45 @@ function interpolatePathname(
return { pathname: interpolated, query: rest }
}
+// TanStack resolves a `?`- or `#`-only relative `to` by *appending* it to the
+// current path, injecting a trailing slash: navigating to `?preset=x` from
+// `/advisors/security` lands on `/advisors/security/?preset=x`. Next resolved
+// these against the current pathname. Prefix it explicitly (trailing slash
+// stripped; root stays `/`) so `push({ query })` with no pathname stays on
+// the exact current path.
+// Exported for unit tests (see router.test.ts) — not part of the Next surface.
+export function resolveSearchOrHashOnlyTarget(to: string, currentPathname: string): string {
+ if (!to.startsWith('?') && !to.startsWith('#')) return to
+ let base = currentPathname || '/'
+ if (base.length > 1 && base.endsWith('/')) base = base.slice(0, -1)
+ return `${base}${to}`
+}
+
+// Next resolves a pathname-less UrlObject against the *current route
+// pattern*, re-consuming dynamic params from `query` into the path — e.g.
+// `push({ query: { ...router.query, preset } })` on `/project/[ref]/advisors`
+// stays on `/project/[/advisors?preset=…`. Because the shim's
+// `router.query` merges path params in (Next shape), skipping this would leak
+// `ref`/`id` into the query string. Params the caller didn't include are
+// backfilled from the current route's params (minus TanStack's `_splat`,
+// which no bracket segment can consume) so partial `{ query }` pushes stay
+// on-page instead of producing empty path segments.
+// Exported for unit tests (see router.test.ts) — not part of the Next surface.
+export function withDefaultPathname(
+ url: string | UrlObject,
+ currentPathPattern: string,
+ currentParams: Record]
+): string | UrlObject {
+ if (typeof url === 'string' || url.pathname != null) return url
+ if (!url.query || typeof url.query !== 'object') return url
+ const { _splat, ...paramsWithoutSplat } = currentParams
+ return {
+ ...url,
+ pathname: currentPathPattern,
+ query: { ...paramsWithoutSplat, ...url.query },
+ }
+}
+
// Exported for unit tests (see router.test.ts) — not part of the Next surface.
export function resolveUrl(url: string | UrlObject): string {
if (typeof url === 'string') return url
@@ -135,8 +176,8 @@ export function resolveUrl(url: string | UrlObject): string {
// `basepath: '/dashboard'` and `to: '/foo'`, it produces `/dashboard/foo`.
// So we strip the origin AND the basePath when present; otherwise
// `router.push('/dashboard/...')` would double-prefix to
-// `/dashboard/dashboard/...`. Mirrors the equivalent logic in the
-// next/link shim's `splitInternalUrl`. Cross-origin URLs pass through
+// `/dashboard/dashboard/...`. Mirrors the equivalent logic in
+// `splitInternalUrl` (@/lib/internal-url). Cross-origin URLs pass through
// untouched so TanStack hands them to the browser as external.
const NEXT_PUBLIC_BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? ''
@@ -231,9 +272,33 @@ export function useRouter() {
// It's intentionally not part of Next's public TransitionOptions.
options?: TransitionOptions & { _replace?: boolean }
): Promise => {
- const to = toRelativeSameOrigin(resolveUrl(url))
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- await router.navigate({ to: to as any, replace: options?._replace })
+ // `location.pathname` is already basepath-stripped by TanStack, so the
+ // prefixed target stays basepath-relative like every other `to`.
+ const target = resolveSearchOrHashOnlyTarget(
+ // `useParams({ strict: false })` types as possibly-undefined; treat
+ // "no params" as an empty record for backfilling.
+ toRelativeSameOrigin(resolveUrl(withDefaultPathname(url, pathPattern, params ?? {}))),
+ location.pathname
+ )
+ // Never embed `?query`/`#hash` inside TanStack's `to` — router-core
+ // runs the whole string through path interpolation, which percent-
+ // decodes it and strips control characters (dropping `%0A` newlines
+ // from values like the Logs Explorer's `s` SQL param). Split into
+ // { to, search, hash } instead; the target is already relative and
+ // basepath-stripped, so splitInternalUrl's own origin/basePath
+ // normalisation is a no-op here. `search: {}` clears the query,
+ // matching Next's push-without-query semantics; same for `hash: ''`.
+ //
+ // The `` type arguments opt out of the registered
+ // route tree's strict typing: Next-style hrefs are free-form strings
+ // that can't satisfy the route-path union at compile time.
+ const { to, search, hash } = splitInternalUrl(target)
+ await router.navigate({
+ to,
+ search: search ?? {},
+ hash: hash ?? '',
+ replace: options?._replace,
+ })
return true
}
@@ -308,8 +373,16 @@ export function useRouter() {
_options?: PrefetchOptions
): Promise => {
try {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- await router.preloadRoute({ to: url as any })
+ // Split like navigate() above so a query string in the href
+ // preloads the real target instead of a path-mangled one.
+ // `` (TFrom, TTo) loosens `to` to a plain string
+ // for the same free-form-href reason as `navigate` above.
+ const { to, search, hash } = splitInternalUrl(toRelativeSameOrigin(url))
+ await router.preloadRoute({
+ to,
+ search: search ?? {},
+ hash: hash ?? '',
+ })
} catch {
// Next's prefetch is fire-and-forget; swallow resolution errors
// (e.g. unknown route) so callers don't have to guard.
diff --git a/apps/studio/compat/sentry-nextjs.ts b/apps/studio/compat/sentry-nextjs.ts
new file mode 100644
index 00000000000..8666c90b2ac
--- /dev/null
+++ b/apps/studio/compat/sentry-nextjs.ts
@@ -0,0 +1,62 @@
+/**
+ * TanStack-build replacement for `@sentry/nextjs`.
+ *
+ * `@sentry/nextjs`'s client entry imports `next/dist/shared/lib/constants`,
+ * which evaluates `...(process?.features?.typescript ? ['next.config.mts'] : [])`
+ * at module scope. Optional chaining does NOT guard an undeclared `process`
+ * identifier in the browser, so any built chunk containing it throws
+ * `ReferenceError: process is not defined` at load time (observed on the
+ * table editor route). The Vite dev server shims `process`, so the crash
+ * only surfaces in the production/test build.
+ *
+ * vite.config.ts aliases `@sentry/nextjs` to this module for the TanStack
+ * build only — app source keeps importing `@sentry/nextjs`, and the Next
+ * build (`build:next`) is untouched.
+ *
+ * `@sentry/nextjs` re-exports `@sentry/react` (same version, 10.x) and adds
+ * Next-specific helpers on top. Everything Studio uses at runtime —
+ * `captureException`, `captureMessage`, `startSpan`, `withScope`, `setTag`,
+ * `setUser`, `init`, and the `Event` / `Breadcrumb` / `StackFrame` types —
+ * exists in `@sentry/react`, so re-export the lot.
+ */
+import { captureException } from '@sentry/react'
+
+// eslint-disable-next-line barrel-files/avoid-re-export-all -- compat shim: its whole job is to mirror @sentry/react's surface
+export * from '@sentry/react'
+
+// ---------------------------------------------------------------------------
+// Next-specific APIs with no `@sentry/react` equivalent. None of them are in
+// the TanStack/Vite module graph today (they're only referenced from
+// Next-convention files: instrumentation-client.ts, instrumentation.ts and
+// next.config.ts), but provide explicit stand-ins so the alias can never
+// produce an `undefined is not a function` crash if one leaks in later.
+// ---------------------------------------------------------------------------
+
+/**
+ * `@sentry/nextjs`'s hook for Next's `onRouterTransitionStart` instrumentation
+ * event. TanStack Router navigation spans would instead come from
+ * `Sentry.tanstackRouterBrowserTracingIntegration` — no-op here.
+ */
+export function captureRouterTransitionStart(_href: string, _navigationType: string): void {}
+
+/**
+ * `@sentry/nextjs`'s hook for Next's `onRequestError` server instrumentation.
+ * Report the error through the regular capture pipeline instead.
+ */
+export function captureRequestError(
+ error: unknown,
+ _request: unknown,
+ _errorContext: unknown
+): void {
+ // `captureException` from `@sentry/react` is isomorphic (no-op without an
+ // initialized client), so this is safe on both server and client.
+ captureException(error)
+}
+
+/**
+ * Build-time-only wrapper for next.config — meaningless under Vite. Return
+ * the config unchanged so any accidental usage is inert.
+ */
+export function withSentryConfig(config: T, ..._options: unknown[]): T {
+ return config
+}
diff --git a/apps/studio/components/grid/MsSqlValidation.tsx b/apps/studio/components/grid/MsSqlValidation.tsx
index 8a57748fd64..8a5a92fff57 100644
--- a/apps/studio/components/grid/MsSqlValidation.tsx
+++ b/apps/studio/components/grid/MsSqlValidation.tsx
@@ -1,5 +1,5 @@
import type { ComponentType, ReactNode } from 'react'
-import { Admonition } from 'ui-patterns'
+import { Admonition } from 'ui-patterns/admonition'
import type { Filter, Sort } from './types'
import { isMsSqlForeignTable, type Entity } from '@/data/table-editor/table-editor-types'
diff --git a/apps/studio/components/grid/SupabaseGrid.tsx b/apps/studio/components/grid/SupabaseGrid.tsx
index 4c7871006de..e1f3e5205f8 100644
--- a/apps/studio/components/grid/SupabaseGrid.tsx
+++ b/apps/studio/components/grid/SupabaseGrid.tsx
@@ -91,6 +91,7 @@ export const SupabaseGrid = ({
customHeader={customHeader}
isRefetching={isRefetching}
tableQueriesEnabled={tableQueriesEnabled}
+ rows={rows}
/>
{msSqlWarning.warning !== null && }
diff --git a/apps/studio/components/grid/components/common/DefaultValue.tsx b/apps/studio/components/grid/components/common/DefaultValue.tsx
index 6a94b026097..bf46676a977 100644
--- a/apps/studio/components/grid/components/common/DefaultValue.tsx
+++ b/apps/studio/components/grid/components/common/DefaultValue.tsx
@@ -1,3 +1,3 @@
export const DefaultValue = () => {
- return DEFAULT
+ return DEFAULT
}
diff --git a/apps/studio/components/grid/components/common/DropdownControl.tsx b/apps/studio/components/grid/components/common/DropdownControl.tsx
index fe49c3ccc45..ab498bf368c 100644
--- a/apps/studio/components/grid/components/common/DropdownControl.tsx
+++ b/apps/studio/components/grid/components/common/DropdownControl.tsx
@@ -30,8 +30,8 @@ export const DropdownControl = ({
{children}
-
- {options.length === 0 &&
No more items
}
+
+ {options.length === 0 &&
No more items
}
{options.map((x) => {
return (
{
- return EMPTY
+ return EMPTY
}
diff --git a/apps/studio/components/grid/components/common/NullValue.tsx b/apps/studio/components/grid/components/common/NullValue.tsx
index a2f45c9398c..c9cafa35019 100644
--- a/apps/studio/components/grid/components/common/NullValue.tsx
+++ b/apps/studio/components/grid/components/common/NullValue.tsx
@@ -1,3 +1,3 @@
export const NullValue = () => {
- return NULL
+ return NULL
}
diff --git a/apps/studio/components/grid/components/editor/DateTimeEditor.tsx b/apps/studio/components/grid/components/editor/DateTimeEditor.tsx
index d0133a7f547..741bd38c464 100644
--- a/apps/studio/components/grid/components/editor/DateTimeEditor.tsx
+++ b/apps/studio/components/grid/components/editor/DateTimeEditor.tsx
@@ -13,8 +13,8 @@ import {
PopoverContent,
PopoverTrigger,
} from 'ui'
-import { TimestampInfo, timestampLocalFormatter } from 'ui-patterns'
import { Input } from 'ui-patterns/DataInputs/Input'
+import { TimestampInfo, timestampLocalFormatter } from 'ui-patterns/TimestampInfo'
import { BlockKeys } from '../common/BlockKeys'
import { useIsQueueOperationsEnabled } from '@/components/interfaces/Account/Preferences/useDashboardSettings'
diff --git a/apps/studio/components/grid/components/editor/JsonEditor.tsx b/apps/studio/components/grid/components/editor/JsonEditor.tsx
index 21c6224df7a..da56dfba3c3 100644
--- a/apps/studio/components/grid/components/editor/JsonEditor.tsx
+++ b/apps/studio/components/grid/components/editor/JsonEditor.tsx
@@ -165,7 +165,7 @@ export const JsonEditor = ({
setIsPopoverOpen(!isPopoverOpen)}
>
{value === null || value === '' ?
: jsonString}
diff --git a/apps/studio/components/grid/components/editor/NumberEditor.tsx b/apps/studio/components/grid/components/editor/NumberEditor.tsx
index f61f087a032..3d5ed3866f1 100644
--- a/apps/studio/components/grid/components/editor/NumberEditor.tsx
+++ b/apps/studio/components/grid/components/editor/NumberEditor.tsx
@@ -25,7 +25,7 @@ export function NumberEditor
({
return (
({
setIsPopoverOpen(!isPopoverOpen)}
>
diff --git a/apps/studio/components/grid/components/editor/TimeEditor.tsx b/apps/studio/components/grid/components/editor/TimeEditor.tsx
index b092ccd2b81..59cfcfdbd2a 100644
--- a/apps/studio/components/grid/components/editor/TimeEditor.tsx
+++ b/apps/studio/components/grid/components/editor/TimeEditor.tsx
@@ -42,7 +42,7 @@ function BaseEditor
({
return (
{
+ if (!hasMountedRef.current) {
+ hasMountedRef.current = true
+ return
+ }
+ snap.setPage(1)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [filters])
+
// [Joshen] One to revisit if we can consolidate this and the main return statement
if (isForeignTableSelected) {
return (
diff --git a/apps/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx b/apps/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx
index 50e8bb81595..afced4818d5 100644
--- a/apps/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx
+++ b/apps/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx
@@ -56,8 +56,8 @@ export const ForeignKeyFormatter = (props: Props) => {
foreignKeyColumn?.format === 'bytea' && !!value ? convertByteaToHex(value) : value
return (
-
-
+
+
{formattedValue === null ? : formattedValue}
{isLoading && formattedValue !== null && (
@@ -86,6 +86,7 @@ export const ForeignKeyFormatter = (props: Props) => {
{
e.preventDefault()
diff --git a/apps/studio/components/grid/components/formatter/ReferenceRecordPeek.tsx b/apps/studio/components/grid/components/formatter/ReferenceRecordPeek.tsx
index f45858e7ca5..f72d3ce51b5 100644
--- a/apps/studio/components/grid/components/formatter/ReferenceRecordPeek.tsx
+++ b/apps/studio/components/grid/components/formatter/ReferenceRecordPeek.tsx
@@ -122,39 +122,41 @@ export const ReferenceRecordPeek = ({ table, column, value }: ReferenceRecordPee
:
-
- rowIdx: number
- row: SupaRow
- }) => {
- selectedCellRef.current = { idx: args.column.idx, rowIdx: args.rowIdx }
- }}
- onCellDoubleClick={(_, e) => {
- e.preventDefault()
- e.stopPropagation()
- }}
- renderers={{
- noRowsFallback: (
-
- {isLoading && (
-