diff --git a/apps/docs/app/api/search_v2/route.ts b/apps/docs/app/api/search_v2/route.ts
index 94030052ef7..9d08c899782 100644
--- a/apps/docs/app/api/search_v2/route.ts
+++ b/apps/docs/app/api/search_v2/route.ts
@@ -1,3 +1,4 @@
+import * as Sentry from '@sentry/nextjs'
import { supabaseSearchV2 } from '~/scripts/search_v2/client'
import { type NextRequest } from 'next/server'
@@ -10,23 +11,36 @@ export async function OPTIONS() {
}
export async function GET(request: NextRequest) {
- const { searchParams } = new URL(request.url)
- const query = searchParams.get('q')?.trim()
- const limit = Number(searchParams.get('limit')) || 10
+ try {
+ const { searchParams } = new URL(request.url)
+ const query = searchParams.get('q')?.trim()
+ const limit = Number(searchParams.get('limit')) || 10
- if (!query) {
- return Response.json({ error: 'Missing q parameter' }, { status: 400, headers: corsHeaders })
+ if (!query) {
+ return Response.json({ error: 'Missing q parameter' }, { status: 400, headers: corsHeaders })
+ }
+
+ const { data, error } = await supabaseSearchV2().rpc('search_docs', {
+ query_text: query,
+ match_limit: limit,
+ })
+
+ if (error) {
+ console.error('Error running docs search v2:', error)
+ Sentry.captureException(new Error(error.message), {
+ tags: { route: 'search_v2' },
+ extra: { query, limit, error },
+ })
+ return Response.json({ error: error.message }, { status: 500, headers: corsHeaders })
+ }
+
+ return Response.json(data, { headers: corsHeaders })
+ } catch (error) {
+ console.error('Error handling docs search v2 request:', error)
+ Sentry.captureException(error, { tags: { route: 'search_v2' } })
+ return Response.json(
+ { error: 'There was an error processing your request' },
+ { status: 500, headers: corsHeaders }
+ )
}
-
- const { data, error } = await supabaseSearchV2().rpc('search_docs', {
- query_text: query,
- match_limit: limit,
- })
-
- if (error) {
- console.error('Error running docs search v2:', error)
- return Response.json({ error: error.message }, { status: 500, headers: corsHeaders })
- }
-
- return Response.json(data, { headers: corsHeaders })
}
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 17022d54b9b..0f7c3bb734a 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -1099,6 +1099,10 @@ export const database: NavMenuConstant = {
name: 'Managing database functions',
url: '/guides/database/functions' as `/${string}`,
},
+ {
+ name: 'Debugging database functions',
+ url: '/guides/database/debugging-functions' as `/${string}`,
+ },
{
name: 'Managing database triggers',
url: '/guides/database/postgres/triggers' as `/${string}`,
diff --git a/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx b/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx
index 1c06cc8b765..451bb8b77b0 100644
--- a/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx
+++ b/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx
@@ -51,7 +51,6 @@ const TopNavBar: FC = () => {
{searchVariant === 'search-v2-active' ? (
Search
diff --git a/apps/docs/content/guides/database/debugging-functions.mdx b/apps/docs/content/guides/database/debugging-functions.mdx
new file mode 100644
index 00000000000..f386ae108f5
--- /dev/null
+++ b/apps/docs/content/guides/database/debugging-functions.mdx
@@ -0,0 +1,174 @@
+---
+id: 'debugging-functions'
+title: 'Debugging database functions'
+description: 'Logging and error handling inside Postgres functions.'
+---
+
+Add logs and error handling to a database function so you can see what it does at runtime. Logs matter most in a complex function.
+
+For how to write and call a function, see [Database functions](/docs/guides/database/functions).
+
+Good targets to log include:
+
+- Values of (non-sensitive) variables
+- Returned results from queries
+
+## General logging
+
+Use the `raise` keyword to write custom logs to the [Postgres logs](/dashboard/project/_/logs/postgres-logs) in the Dashboard. Three severity levels appear by default:
+
+- `log`
+- `warning`
+- `exception` (error level)
+
+```sql
+create function logging_example(
+ log_message text,
+ warning_message text,
+ error_message text
+)
+returns void
+language plpgsql
+as $$
+begin
+ raise log 'logging message: %', log_message;
+ raise warning 'logging warning: %', warning_message;
+
+ -- immediately ends function and reverts transaction
+ raise exception 'logging error: %', error_message;
+end;
+$$;
+
+select logging_example('LOGGED MESSAGE', 'WARNING MESSAGE', 'ERROR MESSAGE');
+```
+
+## Error handling
+
+You can create custom errors with the `raise exception` keywords.
+
+A common pattern is to throw an error when a variable doesn't meet a condition:
+
+```sql
+create or replace function error_if_null(some_val text)
+returns text
+language plpgsql
+as $$
+begin
+ -- error if some_val is null
+ if some_val is null then
+ raise exception 'some_val should not be NULL';
+ end if;
+ -- return some_val if it is not null
+ return some_val;
+end;
+$$;
+
+select error_if_null(null);
+```
+
+Value checking is common, so Postgres provides the `assert` keyword as a shorthand. It takes the following format:
+
+```text
+assert , 'message';
+```
+
+For example:
+
+```sql
+-- assumes an attendance_table with an id uuid column and a student text column
+create function assert_example(name text)
+returns uuid
+language plpgsql
+as $$
+declare
+ student_id uuid;
+begin
+ -- save a user's id into the user_id variable
+ select
+ id into student_id
+ from attendance_table
+ where student = name;
+
+ -- throw an error if the student_id is null
+ assert student_id is not null, 'assert_example() ERROR: student not found';
+
+ -- otherwise, return the user's id
+ return student_id;
+end;
+$$;
+
+select assert_example('Harry Potter');
+```
+
+You can also capture and modify an error message with the `exception` keyword:
+
+```sql
+create function error_example()
+returns void
+language plpgsql
+as $$
+begin
+ -- fails: cannot read from nonexistent table
+ select * from table_that_does_not_exist;
+
+ exception
+ when others then
+ raise exception 'An error occurred in function : %', sqlerrm;
+end;
+$$;
+```
+
+## Advanced logging
+
+For a more complex function, or for harder debugging, log the following:
+
+- Formatted variables
+- Individual rows
+- Start and end of function calls
+
+```sql
+-- assumes a some_table with a col_1 int column and a col_2 text column
+create or replace function advanced_example(num int default 10)
+returns text
+language plpgsql
+as $$
+declare
+ var1 int := 20;
+ var2 text;
+begin
+ -- Logging start of function
+ raise log 'logging start of function call: (%)', (select now());
+
+ -- Logging a variable from a SELECT query
+ select
+ col_1 into var1
+ from some_table
+ limit 1;
+ raise log 'logging a variable (%)', var1;
+
+ -- It is also possible to avoid using variables, by returning the values of your query to the log
+ raise log 'logging a query with a single return value(%)', (select col_1 from some_table limit 1);
+
+ -- If necessary, you can even log an entire row as JSON
+ raise log 'logging an entire row as JSON (%)', (select to_jsonb(some_table.*) from some_table limit 1);
+
+ -- When using INSERT or UPDATE, the new value(s) can be returned
+ -- into a variable.
+ -- When using DELETE, the deleted value(s) can be returned.
+ -- All three operations use "RETURNING value(s) INTO variable(s)" syntax
+ insert into some_table (col_2)
+ values ('new val')
+ returning col_2 into var2;
+
+ raise log 'logging a value from an INSERT (%)', var2;
+
+ return var1 || ',' || var2;
+exception
+ -- Handle exceptions here if needed
+ when others then
+ raise exception 'An error occurred in function : %', sqlerrm;
+end;
+$$;
+
+select advanced_example();
+```
diff --git a/apps/docs/content/guides/database/extensions/pgaudit.mdx b/apps/docs/content/guides/database/extensions/pgaudit.mdx
index 7514e339dea..e67f1f4bc91 100644
--- a/apps/docs/content/guides/database/extensions/pgaudit.mdx
+++ b/apps/docs/content/guides/database/extensions/pgaudit.mdx
@@ -344,7 +344,7 @@ To prevent PGAudit from monitoring the problematic roles, you'll want to change
### Using PGAudit to debug database functions
-Technically yes, but it is not the best approach. It is better to check out our [function debugging guide](/docs/guides/database/functions#general-logging) instead.
+Technically yes, but it is not the best approach. It is better to check out our [function debugging guide](/docs/guides/database/debugging-functions#general-logging) instead.
### Downloading database logs
@@ -387,6 +387,6 @@ PGAudit's [official documentation](https://www.pgaudit.org) focuses on system an
## Resources
- [Official `PGAudit` documentation](https://www.pgaudit.org)
-- [Database Function Logging](/docs/guides/database/functions#general-logging)
+- [Database Function Logging](/docs/guides/database/debugging-functions#general-logging)
- [Supabase Logging](/docs/guides/observability/logs)
- [Self-Hosting Logs](/docs/reference/self-hosting-analytics/introduction)
diff --git a/apps/docs/content/guides/database/functions.mdx b/apps/docs/content/guides/database/functions.mdx
index b077f4c036a..bd1a3b59356 100644
--- a/apps/docs/content/guides/database/functions.mdx
+++ b/apps/docs/content/guides/database/functions.mdx
@@ -1,6 +1,6 @@
---
id: 'functions'
-title: 'Database Functions'
+title: 'Database functions'
description: 'Creating and using Postgres functions.'
video: 'https://www.youtube.com/v/MJZCCpCYEqk'
---
@@ -8,11 +8,24 @@ video: 'https://www.youtube.com/v/MJZCCpCYEqk'
Postgres has built-in support for [SQL functions](https://www.postgresql.org/docs/current/sql-createfunction.html).
These functions live inside your database, and you can call them from your app with [`rpc()`](../../reference/javascript/rpc).
+- [Database functions vs Edge Functions](#database-functions-vs-edge-functions) compares the two. Start here if you aren't sure which one fits.
+- [Create a database function](#create-a-database-function) has the steps, from a one-line function to one that takes parameters.
+- [Secure a database function](#secure-a-database-function) covers which user the function runs as and who can call it.
+- [Debugging database functions](/docs/guides/database/debugging-functions) covers logging and error handling.
+
## Quick demo
-## Getting started
+## Database functions vs Edge Functions
+
+For data-intensive operations, use database functions. They run inside your database, and you can call them remotely with the [REST and GraphQL API](../api).
+
+For use cases that need low latency, use [Edge Functions](../../guides/functions). They're globally distributed and you write them in TypeScript.
+
+## Create a database function
+
+### Getting started
Create a database function from the Dashboard, or write the SQL yourself against a
[direct connection](../../guides/database/connecting-to-postgres).
@@ -24,7 +37,7 @@ To use the Dashboard:
3. Enter the SQL that creates or replaces your database function.
4. Click **Run**. You can also press `cmd+enter` or `ctrl+enter`.
-## Basic functions [#simple-functions]
+### Basic functions [#simple-functions]
Create a basic database function that returns the string "hello world".
@@ -47,7 +60,7 @@ At its most basic, a function has the following parts:
2. `returns text`: The type of data the function returns. For a function that returns nothing, write `returns void`.
3. `language sql`: The language used inside the function body. This can also be a procedural language: `plpgsql`, `plpython`, etc.
4. `as $$`: The function wrapper. Anything inside the `$$` symbols is part of the function body.
-5. `select 'hello world';`: A basic function body. The function returns the result of the last `select` statement in its body.
+5. `select 'hello world';`: A basic function body. The function returns the result of its final query, which can be an `insert`, `update`, or `delete` with a `returning` clause.
6. `$$;`: The closing symbols of the function wrapper.
@@ -141,22 +154,22 @@ Reference: [`Rpc()`](../../reference/csharp/rpc)
$Show>
-## Returning data sets
+### Returning data sets
A database function can also return a data set from a [table](../../guides/database/tables) or a view.
-For example, take a database holding some Star Wars data:
+For example, create two tables holding some Star Wars data. The **Data** tab shows the rows they end up with.
-### Planets
+#### Planets
```
| id | name |
@@ -166,7 +179,7 @@ For example, take a database holding some Star Wars data:
| 3 | Kashyyyk |
```
-### People
+#### People
```
| id | name | planet_id |
@@ -187,11 +200,11 @@ create table planets (
);
insert into planets
- (id, name)
+ (name)
values
- (1, 'Tattoine'),
- (2, 'Alderaan'),
- (3, 'Kashyyyk');
+ ('Tatooine'),
+ ('Alderaan'),
+ ('Kashyyyk');
create table people (
id serial primary key,
@@ -200,12 +213,12 @@ create table people (
);
insert into people
- (id, name, planet_id)
+ (name, planet_id)
values
- (1, 'Anakin Skywalker', 1),
- (2, 'Luke Skywalker', 1),
- (3, 'Princess Leia', 2),
- (4, 'Chewbacca', 3);
+ ('Anakin Skywalker', 1),
+ ('Luke Skywalker', 1),
+ ('Princess Leia', 2),
+ ('Chewbacca', 3);
```
@@ -291,7 +304,7 @@ data = supabase.rpc('get_planets').eq('id', 1).execute()
$Show>
-## Passing parameters
+### Passing parameters
Create a function that inserts a new planet into the `planets` table and returns the new ID. This function uses the `plpgsql` language.
@@ -408,20 +421,14 @@ await supabase.Rpc("add_planet", new Dictionary { { "name", "Jak
$Show>
-## Suggestions
-
-### Database Functions vs Edge Functions
-
-For data-intensive operations, use database functions. They run inside your database, and you can call them remotely with the [REST and GraphQL API](../api).
-
-For use cases that need low latency, use [Edge Functions](../../guides/functions). They're globally distributed and you write them in TypeScript.
+## Secure a database function
### Security `definer` vs `invoker`
Postgres runs a function either as the user _calling_ it (`invoker`) or as its _creator_ (`definer`). For example:
```sql
-create function hello_world()
+create or replace function hello_world()
returns text
language plpgsql
security definer set search_path = ''
@@ -436,6 +443,17 @@ Prefer `security invoker`, which is also the default. When you use `security def
With an empty search path, `search_path = ''`, name the schema for every relation in the function body, such as `from public.table`. An empty search path limits the damage when the function can reach a schema you don't want the calling user to reach.
+
+
+A `security definer` function can return rows the caller isn't allowed to read. It runs with its creator's privileges. A function created in the Dashboard or by a migration is owned by `postgres`, which bypasses Row Level Security. Any role can call it by default, including `anon`, the role that serves requests carrying no user session.
+
+Pinning the `search_path` doesn't change who can call the function. When a `security definer` function reads per-user data:
+
+- **Check ownership in the function body.** An execute grant controls which roles can call the function. It doesn't limit the rows a permitted role gets back, so granting to `authenticated` still returns any user's row to every signed-in caller.
+- **Narrow the execute privilege as well.** Revoke it from `public` and from each role holding a direct grant, such as `anon`. Then grant it to the roles that need it. See the [Function privileges](#function-privileges) section of this page.
+
+
+
### Function privileges
By default, any role can run a database function. You can restrict execution in two ways:
@@ -463,178 +481,10 @@ By default, any role can run a database function. You can restrict execution in
alter default privileges in schema public revoke execute on functions from anon, authenticated;
```
- You can then regrant permissions for a specific function to a specific role:
-
- ```sql
- grant execute on function public.hello_world to authenticated;
- ```
-
-### Debugging functions
-
-Add logs to help you debug a function. Logs matter most in a complex function.
-
-Good targets to log include:
-
-- Values of (non-sensitive) variables
-- Returned results from queries
-
-#### General logging
-
-Use the `raise` keyword to write custom logs to the [Postgres logs](/dashboard/project/_/logs/postgres-logs) in the Dashboard. Three severity levels appear by default:
-
-- `log`
-- `warning`
-- `exception` (error level)
+You can then regrant permissions for a specific function to a specific role:
```sql
-create function logging_example(
- log_message text,
- warning_message text,
- error_message text
-)
-returns void
-language plpgsql
-as $$
-begin
- raise log 'logging message: %', log_message;
- raise warning 'logging warning: %', warning_message;
-
- -- immediately ends function and reverts transaction
- raise exception 'logging error: %', error_message;
-end;
-$$;
-
-select logging_example('LOGGED MESSAGE', 'WARNING MESSAGE', 'ERROR MESSAGE');
-```
-
-#### Error handling
-
-You can create custom errors with the `raise exception` keywords.
-
-A common pattern is to throw an error when a variable doesn't meet a condition:
-
-```sql
-create or replace function error_if_null(some_val text)
-returns text
-language plpgsql
-as $$
-begin
- -- error if some_val is null
- if some_val is null then
- raise exception 'some_val should not be NULL';
- end if;
- -- return some_val if it is not null
- return some_val;
-end;
-$$;
-
-select error_if_null(null);
-```
-
-Value checking is common, so Postgres provides the `assert` keyword as a shorthand. It takes the following format:
-
-```sql
--- throw error when condition is false
-assert , 'message';
-```
-
-For example:
-
-```sql
-create function assert_example(name text)
-returns uuid
-language plpgsql
-as $$
-declare
- student_id uuid;
-begin
- -- save a user's id into the user_id variable
- select
- id into student_id
- from attendance_table
- where student = name;
-
- -- throw an error if the student_id is null
- assert student_id is not null, 'assert_example() ERROR: student not found';
-
- -- otherwise, return the user's id
- return student_id;
-end;
-$$;
-
-select assert_example('Harry Potter');
-```
-
-You can also capture and modify an error message with the `exception` keyword:
-
-```sql
-create function error_example()
-returns void
-language plpgsql
-as $$
-begin
- -- fails: cannot read from nonexistent table
- select * from table_that_does_not_exist;
-
- exception
- when others then
- raise exception 'An error occurred in function : %', sqlerrm;
-end;
-$$;
-```
-
-#### Advanced logging
-
-For a more complex function, or for harder debugging, log the following:
-
-- Formatted variables
-- Individual rows
-- Start and end of function calls
-
-```sql
-create or replace function advanced_example(num int default 10)
-returns text
-language plpgsql
-as $$
-declare
- var1 int := 20;
- var2 text;
-begin
- -- Logging start of function
- raise log 'logging start of function call: (%)', (select now());
-
- -- Logging a variable from a SELECT query
- select
- col_1 into var1
- from some_table
- limit 1;
- raise log 'logging a variable (%)', var1;
-
- -- It is also possible to avoid using variables, by returning the values of your query to the log
- raise log 'logging a query with a single return value(%)', (select col_1 from some_table limit 1);
-
- -- If necessary, you can even log an entire row as JSON
- raise log 'logging an entire row as JSON (%)', (select to_jsonb(some_table.*) from some_table limit 1);
-
- -- When using INSERT or UPDATE, the new value(s) can be returned
- -- into a variable.
- -- When using DELETE, the deleted value(s) can be returned.
- -- All three operations use "RETURNING value(s) INTO variable(s)" syntax
- insert into some_table (col_2)
- values ('new val')
- returning col_2 into var2;
-
- raise log 'logging a value from an INSERT (%)', var2;
-
- return var1 || ',' || var2;
-exception
- -- Handle exceptions here if needed
- when others then
- raise exception 'An error occurred in function : %', sqlerrm;
-end;
-$$;
-
-select advanced_example();
+grant execute on function public.hello_world to authenticated;
```
## Resources
@@ -642,18 +492,16 @@ select advanced_example();
- Official Client libraries: [JavaScript](../../reference/javascript/rpc) and [Flutter](../../reference/dart/rpc)
- Community client libraries: [github.com/supabase-community](https://github.com/supabase-community)
- Postgres Official Docs: [Chapter 9. Functions and Operators](https://www.postgresql.org/docs/current/functions.html)
-- Postgres Reference: [CREATE FUNCTION](https://www.postgresql.org/docs/9.1/sql-createfunction.html)
+- Postgres Reference: [CREATE FUNCTION](https://www.postgresql.org/docs/current/sql-createfunction.html)
-## Deep dive
-
-### Create Database Functions
+### Create a database function in the Dashboard
-### Call Database Functions using JavaScript
+### Call a database function from JavaScript
-### Using Database Functions to call an external API
+### Call an external API from a database function
diff --git a/apps/docs/content/guides/database/multigres.mdx b/apps/docs/content/guides/database/multigres.mdx
index 3100f911fbf..8e0cb752c65 100644
--- a/apps/docs/content/guides/database/multigres.mdx
+++ b/apps/docs/content/guides/database/multigres.mdx
@@ -15,13 +15,14 @@ Multigres gives your project high availability by running Postgres across multip
-## Eligibility
+## Requesting access
+
+To request access to the Multigres Private Alpha, [fill out the early access form](https://supabase.com/go/multigres-early-access#form).
During the Private Alpha:
- Multigres is available to organizations on a paid plan. It isn't available on the Free plan.
-- Availability is being rolled out gradually, so the option to enable it may not yet appear for every eligible organization.
-- Up to two projects per eligible organization can use Multigres for free during the Alpha.
+- Up to two projects per organization can use Multigres for free during the Alpha.
## Enabling Multigres
@@ -32,7 +33,7 @@ In the Dashboard, Multigres appears as **High availability** during project crea
3. Finish the remaining fields such as database password, region, and compute size, if shown.
4. Click **Create new project**.
-If your organization isn't eligible or hasn't been rolled out yet, **High availability** won't appear.
+If you don't see **High availability**, your organization doesn't have access yet. See [Requesting access](#requesting-access).
## What's not included in the alpha
diff --git a/apps/docs/content/guides/database/postgres/postgres-log-config.mdx b/apps/docs/content/guides/database/postgres/postgres-log-config.mdx
index f820fd05687..f31dc184de4 100644
--- a/apps/docs/content/guides/database/postgres/postgres-log-config.mdx
+++ b/apps/docs/content/guides/database/postgres/postgres-log-config.mdx
@@ -823,16 +823,16 @@ Determines what _query generated_ logs (not background or networking logs) are r
As an example of how `log_min_messages` works, if the setting were changed to `error`, Postgres would stop recording logs with the severity levels `warning`, `notice`, `info`, and `debug1 ... debug5` events. However, it would continue recording all `error`, `log`, `fatal`, and `panic` occurrences.
-| Severity | Description | Example log |
-| :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **debug1 ... debug5** | Successively detailed debugging and server info, predominantly used by Postgres developers and extension maintainers. | `DEBUG1: rehashing catalog cache id 6...` |
-| **info** | Information explicitly requested by the user during an operation. | `INFO: analyzing "public.example..."`
Example returned by the [`analyze verbose`](https://www.postgresql.org/docs/current/sql-analyze.html) command |
-| **notice** | Helpful, non-essential information about automatic background actions. | `NOTICE: table "old_logs" does not exist, skipping`
Example returned by the [`drop table if exists`](https://www.postgresql.org/docs/current/sql-droptable.html) commands |
-| **warning** | A query completed, but skipped requested actions. | `WARNING: no privileges were granted for "some_user"`
Example returned by the [`grant`](https://www.postgresql.org/docs/current/sql-grant.html) command |
-| **error** | A specific query failed, but the overall database connection remains alive. | `ERROR: duplicate key value violates unique constraint "example_pkey"` |
-| **log** | Operational events. Usually generated by [background activity log settings](/docs/guides/database/postgres/postgres-log-config#background-activity) or by [database functions](/docs/guides/database/functions?queryGroups=language&language=js#debugging-functions) | `LOG: connection received...` |
-| **fatal** | An error that causes a database connection to abruptly terminate. | `FATAL: terminating connection due to administrator command` |
-| **panic** | A critical, system-wide failure that forces the database to shut down and crash-recover. | `PANIC: could not locate a valid checkpoint record at 0/61013608` |
+| Severity | Description | Example log |
+| :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **debug1 ... debug5** | Successively detailed debugging and server info, predominantly used by Postgres developers and extension maintainers. | `DEBUG1: rehashing catalog cache id 6...` |
+| **info** | Information explicitly requested by the user during an operation. | `INFO: analyzing "public.example..."`
Example returned by the [`analyze verbose`](https://www.postgresql.org/docs/current/sql-analyze.html) command |
+| **notice** | Helpful, non-essential information about automatic background actions. | `NOTICE: table "old_logs" does not exist, skipping`
Example returned by the [`drop table if exists`](https://www.postgresql.org/docs/current/sql-droptable.html) commands |
+| **warning** | A query completed, but skipped requested actions. | `WARNING: no privileges were granted for "some_user"`
Example returned by the [`grant`](https://www.postgresql.org/docs/current/sql-grant.html) command |
+| **error** | A specific query failed, but the overall database connection remains alive. | `ERROR: duplicate key value violates unique constraint "example_pkey"` |
+| **log** | Operational events. Usually generated by [background activity log settings](/docs/guides/database/postgres/postgres-log-config#background-activity) or by [database functions](/docs/guides/database/debugging-functions) | `LOG: connection received...` |
+| **fatal** | An error that causes a database connection to abruptly terminate. | `FATAL: terminating connection due to administrator command` |
+| **panic** | A critical, system-wide failure that forces the database to shut down and crash-recover. | `PANIC: could not locate a valid checkpoint record at 0/61013608` |
@@ -931,5 +931,5 @@ Review the [pgAudit docs](/docs/guides/database/extensions/pgaudit) for more con
## Resources
- [Advanced Log Filtering](/docs/guides/observability/advanced-log-filtering)
-- [Database Function Logging](/docs/guides/database/functions#general-logging)
+- [Database Function Logging](/docs/guides/database/debugging-functions#general-logging)
- [Supabase Logging](/docs/guides/observability/logs)
diff --git a/apps/docs/content/guides/functions/auth.mdx b/apps/docs/content/guides/functions/auth.mdx
index 511dfe9ce9a..f502c125ad4 100644
--- a/apps/docs/content/guides/functions/auth.mdx
+++ b/apps/docs/content/guides/functions/auth.mdx
@@ -7,20 +7,32 @@ subtitle: 'Authentication patterns for Edge Functions'
Secure an Edge Function by declaring which credentials it accepts. The `withSupabase` wrapper from the [`@supabase/server` package](https://github.com/supabase/server) on GitHub checks each caller against the `auth` mode you set. Your handler receives a preconfigured Supabase client on `ctx`.
-For how authorization headers and the `verify_jwt` platform check work, see [Authorization headers](/docs/guides/functions/auth-headers).
+- [Choose an auth mode](#choose-an-auth-mode) compares the four modes. Start here if you aren't sure which one your function needs.
+- [Secure your function](#secure-your-function) has an example for each mode, plus how to combine modes and shape your own error response.
+- [Environment variables](#environment-variables) lists what `@supabase/server` reads from the environment.
-The wrapper accepts four `auth` modes:
+## Choose an auth mode
+
+Each mode names the credential a caller must present. `withSupabase` accepts four:
| Mode | Accepts |
| --------------- | ------------------------------------------ |
| `'user'` | A valid user JWT on `Authorization` |
-| `'secret'` | A secret key on `apikey` |
-| `'publishable'` | A publishable key on `apikey` |
+| `'secret'` | The `default` secret key on `apikey` |
+| `'publishable'` | The `default` publishable key on `apikey` |
| `'none'` | Any caller, no check (for signed webhooks) |
-## Authenticated user calls
+For how authorization headers and the `verify_jwt` platform check work, see [Authorization headers](/docs/guides/functions/auth-headers).
-When a signed-in user calls a function, the request carries the user's session JWT on the `Authorization` header. Your app usually makes that call through `supabase.functions.invoke`. The default is `verify_jwt = true`. The platform validates the JWT before your handler runs. Use `auth: 'user'` to get a `ctx.supabase` scoped to the caller's Row Level Security (RLS) policies.
+## Secure your function
+
+Each of the following sections covers one mode, then how to combine modes and shape your own error response. Every example wraps the handler in `withSupabase` and reads its Supabase client from `ctx`.
+
+### Authenticated user calls
+
+When a signed-in user calls a function, the request carries the user's session JWT on the `Authorization` header. Your app usually makes that call through `supabase.functions.invoke`. Use `auth: 'user'` to get a `ctx.supabase` scoped to the caller's Row Level Security (RLS) policies.
+
+The default is `verify_jwt = true`, so the platform validates the JWT before your handler runs. Leave it on for user-facing functions. Turning it off to clear a 401 during development removes the platform's check, leaving `withSupabase` as the only thing that verifies the caller.
```ts
import { withSupabase } from 'npm:@supabase/server@1'
@@ -40,9 +52,17 @@ export default {
}
```
-## Service-to-service calls
+Reach for `ctx.supabase` when the handler serves the signed-in caller. It applies their RLS policies to every query. Use `ctx.supabaseAdmin` only for work that has to cross those policies.
-Cron jobs, workers, `pg_net`, and other Edge Functions make calls with a secret key on the `apikey` header rather than a user JWT. Disable `verify_jwt` and use `auth: 'secret'`. The wrapper validates the key against any secret key in your [project's API keys](/dashboard/project/_/settings/api-keys), and gives your handler `ctx.supabaseAdmin` for privileged work.
+
+
+A handler that queries a shared table through `ctx.supabaseAdmin` without filtering by the caller's ID returns every user's rows. `ctx.supabaseAdmin` bypasses Row Level Security, so filter by the caller's ID from `ctx.userClaims`, or use `ctx.supabase` instead.
+
+
+
+### Service-to-service calls
+
+Cron jobs, workers, `pg_net`, and other Edge Functions make calls with a secret key on the `apikey` header rather than a user JWT. Disable `verify_jwt` and use `auth: 'secret'`. The wrapper validates the key against the secret key named `default` in your [project's API keys](/dashboard/project/_/settings/api-keys), and gives your handler `ctx.supabaseAdmin` for privileged work.
```ts
import { withSupabase } from 'npm:@supabase/server@1'
@@ -57,13 +77,13 @@ export default {
-To accept only one specific key, use `auth: 'secret:'`. For example, `auth: 'secret:automations'` accepts only the secret key named `automations`. To name a key, open [**Settings > API keys**](/dashboard/project/_/settings/api-keys) in the Supabase Dashboard. The same syntax works for publishable keys: `auth: 'publishable:'`.
+`auth: 'secret'` accepts only the key named `default`. To accept a different key, use `auth: 'secret:'`. For example, `auth: 'secret:automations'` accepts only the secret key named `automations`. To accept any secret key on the project, use `auth: 'secret:*'`. Every project starts with a secret key named `default`, and you can add more. To name a new key, open [**Settings > API keys**](/dashboard/project/_/settings/api-keys) in the Supabase Dashboard. The same syntax works for publishable keys: `auth: 'publishable:'` and `auth: 'publishable:*'`.

-## Public functions
+### Public functions
For a genuinely public function, such as a health check, use `auth: 'none'` with `verify_jwt = false` so anonymous callers can reach the handler.
@@ -85,7 +105,7 @@ export default {
`auth: 'none'` accepts every caller. For a function that authenticates callers itself, see the [External webhooks](#external-webhooks) section of this page.
-## External webhooks
+### External webhooks
External providers such as Stripe or GitHub don't send Supabase credentials. They sign the request body with their own shared secret. Use `auth: 'none'` to skip the wrapper's credential check, then verify the provider's signature inside the handler. Keep `verify_jwt = false`.
@@ -131,7 +151,7 @@ export default {
-## Combining modes
+### Combining modes
Functions that answer both users and internal callers take an array on `auth`. The wrapper tries each mode in order and uses the first one that matches. `ctx.authMode` tells you which mode matched.
@@ -151,7 +171,7 @@ export default {
}
```
-## Custom error responses
+### Custom error responses
To shape the 401 response yourself, use `createSupabaseContext` instead of `withSupabase`. It returns a `{ data, error }` tuple instead of rejecting the request for you.
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 81f7b228c27..70b24e7c33d 100644
--- a/apps/docs/content/guides/local-development/cli/getting-started.mdx
+++ b/apps/docs/content/guides/local-development/cli/getting-started.mdx
@@ -4,25 +4,34 @@ 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 two commands, you can set up and start a new local project:
+The Supabase CLI runs the entire Supabase stack on your own machine or in a CI environment, so you can build and test locally, then connect your project to a hosted one when you're ready to deploy.
-1. `supabase init` to create a new local project
-2. `supabase start` to launch the Supabase services
+- [Quickstart](#quickstart) is the two-command version, if you already have the CLI and a container runtime.
+- [Set up a local project](#set-up-a-local-project) installs the CLI and brings the stack up on your machine. Start here if you haven't run it before.
+- [Connect to a hosted project](#connect-to-a-hosted-project) links your local directory to a project on the Supabase Platform.
+- [Change your CLI version](#change-your-cli-version) updates the CLI and switches between stable and pre-release builds.
+- [Telemetry](#telemetry) covers what the CLI collects and how to opt out.
-
+## Quickstart
-There are two ways to install the CLI, and they change the command you type:
+With two commands, you can set up and start a new local project:
-- **Project dependency** with `npm`, `pnpm`, or `yarn` installs the CLI into a single project (there is no global `supabase` command with this method). Run it through your package runner instead, for example `npx supabase `.
-- **Global install** with Homebrew, Scoop, or Linux packages. Run commands as `supabase `.
+1. Run `supabase init` to create a new local project.
+2. Run `supabase start` to launch the Supabase services.
-Either way, the CLI is **project-scoped**: most commands (including `start`) expect to run inside a directory that has been initialized with `supabase init`, which creates the `supabase/` folder and `config.toml`. Run `init` first, then the other commands from the same directory.
+
-The rest of this page writes examples as `supabase `; translate them to `npx supabase ` if you installed the CLI as a project dependency.
+How you run a CLI command depends on how you install it. Homebrew, Scoop, and the Linux packages give you a global `supabase` command. Installing with npm, pnpm, yarn, or bun adds the CLI to one project instead, so you run it through your package runner as `npx supabase `. This page writes every example as `supabase `.
+
+The CLI is project-scoped either way. Most commands, including `start`, expect a directory that `supabase init` has already set up with a `supabase` directory and a `config.toml`. Run `init` first, then the rest from the same directory.
-## Installing the Supabase CLI
+## Set up a local project
+
+Install the CLI, bring the Supabase stack up on your machine, and stop it when you're done.
+
+### Install the Supabase CLI [#installing-the-supabase-cli]
`; translate them to
>
-Install the CLI as a project dev dependency. This adds it to a single project rather than installing a global command:
+1. Install the CLI as a project dev dependency. This command adds the CLI to a single project rather than installing a global command:
-```sh
-npm install supabase --save-dev
-# or: pnpm add -D supabase / yarn add -D supabase / bun add -D supabase
-```
+ ```sh
+ npm install supabase --save-dev
+ # or: pnpm add -D supabase / yarn add -D supabase / bun add -D supabase
+ ```
-Pin the version in `package.json` so your whole team uses the same CLI version. Then run every command through your package runner:
+2. Pin the version in `package.json` so your whole team uses the same CLI version.
-```sh
-npx supabase --help
-# or: pnpm supabase / yarn supabase / bunx supabase
-```
+3. Run the CLI through your package runner:
+
+ ```sh
+ npx supabase --help
+ # or: pnpm supabase / yarn supabase / bunx supabase
+ ```
@@ -86,19 +97,193 @@ brew install supabase/tap/supabase
#### Linux packages
Linux packages are provided in [Releases](https://github.com/supabase/cli/releases).
-To install, download the `.apk`/`.deb`/`.rpm` file depending on your package manager
-and run one of the following:
-- `sudo apk add --allow-untrusted <...>.apk`
-- `sudo dpkg -i <...>.deb`
-- `sudo rpm -i <...>.rpm`
+1. Download the `.apk`, `.deb`, or `.rpm` file for your package manager.
+2. Install the package:
+ - `sudo apk add --allow-untrusted <...>.apk`
+ - `sudo dpkg -i <...>.deb`
+ - `sudo rpm -i <...>.rpm`
-## Beta channel
+### Run a local Supabase project [#running-a-local-supabase-project]
-Pre-release CLI builds ship from the development branch (`X.Y.Z-beta.N` versions). Use the npm `beta` dist-tag, or install `supabase-beta` via Homebrew / Scoop (separate packages from stable).
+The most common thing you'll do with the CLI is run the full Supabase stack (Postgres, Auth, Storage, and the rest) on your own machine. That stack runs in Docker containers, so you need a container runtime installed first. On Windows and Linux, follow the official guide to install and configure [Docker Desktop](https://docs.docker.com/desktop).
+
+On macOS, we recommend [OrbStack](https://orbstack.dev/) instead of Docker Desktop. It's a drop-in replacement that handles extended file attributes (xattrs) on mounted volumes and container networking more reliably than Docker Desktop. It also starts faster and uses less CPU, memory, and disk, which makes a noticeable difference when running the full Supabase stack.
+
+Alternatively, you can use a different container tool that offers Docker-compatible APIs:
+
+| Tool | Platforms |
+| --------------------------------------------- | --------------------- |
+| [Rancher Desktop](https://rancherdesktop.io/) | macOS, Windows, Linux |
+| [Podman](https://podman.io/) | macOS, Windows, Linux |
+| [colima](https://github.com/abiosoft/colima) | macOS |
+
+To bring the stack up:
+
+1. Start your container runtime.
+
+2. Open a terminal in the directory where you want to create your project.
+
+3. Initialize the project:
+
+ ```bash
+ supabase init
+ ```
+
+ The command creates a `supabase` directory, which is safe to commit to version control. `init` creates local files only: it doesn't sign you in or connect the directory to a project on the Supabase Platform. To connect one, see [Connect to a hosted project](#connect-to-a-hosted-project).
+
+4. Start the Supabase services from the same directory:
+
+ ```bash
+ supabase start
+ ```
+
+
+
+If you installed the CLI as a project dependency with npm, pnpm, yarn, or bun, run `npx supabase init` and `npx supabase start` instead. See [Install the Supabase CLI](#installing-the-supabase-cli).
+
+
+
+The first run takes time while the CLI downloads the Docker images. It pulls the entire Supabase stack, plus a few extra images useful for local development, such as a local SMTP server and a database diff tool.
+
+### Access your project's services
+
+After all the Supabase services are running, the CLI prints your local credentials. The output looks like this, with the URLs and keys you use in your local project:
+
+```
+Started supabase local development setup.
+
+╭──────────────────────────────────────╮
+│ 🔧 Development Tools │
+├─────────┬────────────────────────────┤
+│ Studio │ http://127.0.0.1:54323 │
+│ Mailpit │ http://127.0.0.1:54324 │
+│ MCP │ http://127.0.0.1:54321/mcp │
+╰─────────┴────────────────────────────╯
+
+╭──────────────────────────────────────────────────────╮
+│ 🌐 APIs │
+├────────────────┬─────────────────────────────────────┤
+│ Project URL │ http://127.0.0.1:54321 │
+│ REST │ http://127.0.0.1:54321/rest/v1 │
+│ GraphQL │ http://127.0.0.1:54321/graphql/v1 │
+│ Edge Functions │ http://127.0.0.1:54321/functions/v1 │
+╰────────────────┴─────────────────────────────────────╯
+
+╭───────────────────────────────────────────────────────────────╮
+│ ⛁ Database │
+├─────┬─────────────────────────────────────────────────────────┤
+│ URL │ postgresql://postgres:postgres@127.0.0.1:54322/postgres │
+╰─────┴─────────────────────────────────────────────────────────╯
+
+╭──────────────────────────────────────────────────────────────╮
+│ 🔑 Authentication Keys │
+├─────────────┬────────────────────────────────────────────────┤
+│ Publishable │ sb_publishable_... │
+│ Secret │ sb_secret_... │
+╰─────────────┴────────────────────────────────────────────────╯
+```
+
+
+
+
+```sh
+# Default URL:
+http://localhost:54323
+```
+
+The local development environment includes Supabase Studio, a graphical interface for querying and editing your database.
+
+
+
+
+
+
+```sh
+# Default URL:
+postgresql://postgres:postgres@localhost:54322/postgres
+```
+
+Access the local Postgres instance with [`psql`](https://www.postgresql.org/docs/current/app-psql.html) or any other Postgres client, such as [pgAdmin](https://www.pgadmin.org/). For example:
+
+```bash
+psql 'postgresql://postgres:postgres@localhost:54322/postgres'
+```
+
+
+
+To access the database from an edge function in your local Supabase setup, replace `localhost` with `host.docker.internal`.
+
+
+
+
+
+
+```sh
+# Default URL:
+http://localhost:54321
+```
+
+If you are accessing these services without the client libraries, you might need to pass the client keys as an `Authorization` header. Learn more about [JWT headers](/docs/learn/auth-deep-dive/auth-deep-dive-jwts).
+
+```sh
+curl 'http://localhost:54321/rest/v1/' \
+ -H "apikey: sb_publishable_..."
+
+http://localhost:54321/rest/v1/ # REST (PostgREST)
+http://localhost:54321/realtime/v1/ # Realtime
+http://localhost:54321/storage/v1/ # Storage
+http://localhost:54321/auth/v1/ # Auth (GoTrue)
+```
+
+
+
+
+Local logs rely on the Supabase Analytics Server, which reads the Docker logging driver. On Linux and macOS, mount the `/var/run/docker.sock` domain socket. On Windows, expose the `tcp://localhost:2375` daemon socket. Configure the socket manually after [installing the CLI](#installing-the-supabase-cli).
+
+
+
+For advanced logs analysis using the Logs Explorer, use the BigQuery backend instead of the default Postgres backend. See [Using the BigQuery backend](/docs/reference/self-hosting-analytics/introduction#using-the-bigquery-backend).
+
+
+
+All logs are stored in the local database under the `_analytics` schema.
+
+
+
+
+### Stop local services [#stopping-local-services]
+
+When you finish working, stop the stack. Stopping doesn't reset your local database:
+
+```bash
+supabase stop
+```
+
+With the default ports, `supabase start` runs one local project per machine, because every project's `config.toml` uses the same ports. To run several local projects or git worktrees at the same time, see [Running multiple local projects](/docs/guides/local-development/running-multiple-local-projects).
+
+## Connect to a hosted project
+
+The stack you started in [Run a local Supabase project](#running-a-local-supabase-project) runs only on your machine. Nothing reaches a hosted project until you sign in to the CLI and link your local directory to one.
+
+For the steps, see [Pushing to a remote project](/docs/guides/local-development/cli-workflows#pushing-to-a-remote-project). To run deployments from CI, see [Configure GitHub Actions](/docs/guides/deployment/managing-environments#configure-github-actions).
+
+## Change your CLI version
+
+These sections cover the CLI itself rather than your local project.
+
+### Use the beta channel [#beta-channel]
+
+Pre-release CLI builds ship from the development branch and are versioned `X.Y.Z-beta.N`. Use the npm `beta` dist-tag, or install `supabase-beta` with Homebrew or Scoop. `supabase-beta` is a separate package from `supabase`.
-## Updating the Supabase CLI
+### Update the Supabase CLI [#updating-the-supabase-cli]
-When a new [version](https://github.com/supabase/cli/releases) is released, you can update the CLI using the same channels.
+Update the CLI with the same package manager you installed it with. See the [CLI releases page](https://github.com/supabase/cli/releases) for available versions.
.apk`
- `sudo dpkg -i <...>.deb`
@@ -231,181 +416,31 @@ brew upgrade supabase-beta
-If you have any Supabase containers running locally, stop them and delete their data volumes before proceeding with the upgrade. This ensures that Supabase managed services can apply new migrations on a clean state of the local database.
+If you have any Supabase containers running locally, stop them and delete their data volumes before upgrading. Deleting the volumes lets Supabase managed services apply new migrations on a clean local database.
-
+
-Remember to save any local schema and data changes before stopping because the `--no-backup` flag will delete them.
-
-```sh
-supabase db diff -f my_schema
-supabase db dump --local --data-only > supabase/seed.sql
-supabase stop --no-backup
-```
+`supabase stop --no-backup` deletes your local schema and data changes. Save them first.
-## Running a local Supabase project
+1. Save local schema changes as a migration:
-The most common thing you'll do with the CLI is run the full Supabase stack (Postgres, Auth, Storage, and the rest) on your own machine. That stack runs in Docker containers, so you need a container runtime installed first. On Windows and Linux, follow the official guide to install and configure [Docker Desktop](https://docs.docker.com/desktop).
+ ```sh
+ supabase db diff -f my_schema
+ ```
-On macOS, we recommend [OrbStack](https://orbstack.dev/) instead of Docker Desktop. It's a drop-in replacement that handles extended file attributes (xattrs) on mounted volumes and container networking more reliably than Docker Desktop. It also starts faster and uses less CPU, memory, and disk, which makes a noticeable difference when running the full Supabase stack.
+2. Dump local data to your seed file:
-Alternately, you can use a different container tool that offers Docker compatible APIs.
+ ```sh
+ supabase db dump --local --data-only > supabase/seed.sql
+ ```
-- [Rancher Desktop](https://rancherdesktop.io/) (macOS, Windows, Linux)
-- [Podman](https://podman.io/) (macOS, Windows, Linux)
-- [colima](https://github.com/abiosoft/colima) (macOS)
+3. Stop the containers and delete their data volumes:
-With a container runtime running, go to the folder where you want to create your project and initialize it:
-
-```bash
-supabase init
-```
-
-This creates a new `supabase` folder. It's safe to commit this folder to version control.
-
-Now, from the same folder, start the Supabase stack:
-
-```bash
-supabase start
-```
-
-
-
-If you installed the CLI as a project dependency (npm, pnpm, yarn, or bun), run these as `npx supabase init` and `npx supabase start` instead. See the [note above](#installing-the-supabase-cli).
-
-
-
-This takes time on your first run because the CLI needs to download the Docker images to your local machine. The CLI includes the entire Supabase stack, and a few additional images useful for local development (like a local SMTP server and a database diff tool).
-
-## Access your project's services
-
-Once all the Supabase services are running, you'll see output containing your local Supabase credentials. It should look like the below, with urls and keys that you use in your local project:
-
-```
-Started supabase local development setup.
-
-╭──────────────────────────────────────╮
-│ 🔧 Development Tools │
-├─────────┬────────────────────────────┤
-│ Studio │ http://127.0.0.1:54323 │
-│ Mailpit │ http://127.0.0.1:54324 │
-│ MCP │ http://127.0.0.1:54321/mcp │
-╰─────────┴────────────────────────────╯
-
-╭──────────────────────────────────────────────────────╮
-│ 🌐 APIs │
-├────────────────┬─────────────────────────────────────┤
-│ Project URL │ http://127.0.0.1:54321 │
-│ REST │ http://127.0.0.1:54321/rest/v1 │
-│ GraphQL │ http://127.0.0.1:54321/graphql/v1 │
-│ Edge Functions │ http://127.0.0.1:54321/functions/v1 │
-╰────────────────┴─────────────────────────────────────╯
-
-╭───────────────────────────────────────────────────────────────╮
-│ ⛁ Database │
-├─────┬─────────────────────────────────────────────────────────┤
-│ URL │ postgresql://postgres:postgres@127.0.0.1:54322/postgres │
-╰─────┴─────────────────────────────────────────────────────────╯
-
-╭──────────────────────────────────────────────────────────────╮
-│ 🔑 Authentication Keys │
-├─────────────┬────────────────────────────────────────────────┤
-│ Publishable │ sb_publishable_... │
-│ Secret │ sb_secret_... │
-╰─────────────┴────────────────────────────────────────────────╯
-```
-
-
-
-
-```sh
-# Default URL:
-http://localhost:54323
-```
-
-The local development environment includes Supabase Studio, a graphical interface for working with your database.
-
-
-
-
-
-
-```sh
-# Default URL:
-postgresql://postgres:postgres@localhost:54322/postgres
-```
-
-The local Postgres instance can be accessed through [`psql`](https://www.postgresql.org/docs/current/app-psql.html) or any other Postgres client, such as [pgAdmin](https://www.pgadmin.org/). For example:
-
-```bash
-psql 'postgresql://postgres:postgres@localhost:54322/postgres'
-```
-
-
-
-To access the database from an edge function in your local Supabase setup, replace `localhost` with `host.docker.internal`.
-
-
-
-
-
-
-```sh
-# Default URL:
-http://localhost:54321
-```
-
-If you are accessing these services without the client libraries, you may need to pass the client keys as an `Authorization` header. Learn more about [JWT headers](/docs/learn/auth-deep-dive/auth-deep-dive-jwts).
-
-```sh
-curl 'http://localhost:54321/rest/v1/' \
- -H "apikey: sb_publishable_..."
-
-http://localhost:54321/rest/v1/ # REST (PostgREST)
-http://localhost:54321/realtime/v1/ # Realtime
-http://localhost:54321/storage/v1/ # Storage
-http://localhost:54321/auth/v1/ # Auth (GoTrue)
-```
-
-
-
-`sb_publishable_...` is the publishable key output when you run the command `supabase start`.
-
-
-
-
-
-
-Local logs rely on the Supabase Analytics Server which accesses the docker logging driver by either volume mounting `/var/run/docker.sock` domain socket on Linux and macOS, or exposing `tcp://localhost:2375` daemon socket on Windows. These settings must be configured manually after [installing](/docs/guides/local-development/cli/getting-started#installing-the-supabase-cli) the Supabase CLI.
-
-
-
-For advanced logs analysis using the Logs Explorer, it is advised to use the BigQuery backend instead of the default Postgres backend. Read about the steps [here](/docs/reference/self-hosting-analytics/introduction#using-the-bigquery-backend).
-
-
-
-All logs are stored in the local database under the `_analytics` schema.
-
-
-
-
-## Stopping local services
-
-When you are finished working on your Supabase project, you can stop the stack (without resetting your local database):
-
-```bash
-supabase stop
-```
-
-With the default ports, `supabase start` runs one local project per machine, because every project's `config.toml` uses the same ports. To run several local projects or git worktrees at the same time, see [Running multiple local projects](/docs/guides/local-development/running-multiple-local-projects).
+ ```sh
+ supabase stop --no-backup
+ ```
## Telemetry
@@ -413,18 +448,19 @@ The Supabase CLI collects telemetry data about general usage. Participating in t
### How to opt out
-You can disable telemetry by running:
+1. Disable telemetry:
-```bash
-supabase telemetry disable
-```
+ ```bash
+ supabase telemetry disable
+ ```
-You can check the current status and re-enable with:
+2. Confirm the current setting:
-```bash
-supabase telemetry status
-supabase telemetry enable
-```
+ ```bash
+ supabase telemetry status
+ ```
+
+To enable telemetry again, run `supabase telemetry enable`.
You can also opt out using the `SUPABASE_TELEMETRY_DISABLED=1` environment variable. The broader `DO_NOT_TRACK=1` convention is also respected.
diff --git a/apps/docs/content/guides/local-development/docker-and-native-runtimes.mdx b/apps/docs/content/guides/local-development/docker-and-native-runtimes.mdx
index ec1da3c4bc3..113bc12b773 100644
--- a/apps/docs/content/guides/local-development/docker-and-native-runtimes.mdx
+++ b/apps/docs/content/guides/local-development/docker-and-native-runtimes.mdx
@@ -30,10 +30,12 @@ The following table compares how each runtime runs services, what it needs, and
| Flag | `--runtime docker` or `--runtime podman` | `--runtime native` |
| How services run | One container per service, from images the CLI pulls | One process per service, from archives the CLI downloads and verifies |
| Requires | A running Docker or Podman engine | A supported platform, `tar` on the host, and, on first start, network access to the archive download hosts |
-| Database data | A Docker volume when the Docker client and daemon are both version 26 or later, or a host directory otherwise | `~/.supabase/stacks//` in your home directory |
+| Database data | Set when the local project is created. A Docker volume if the Docker client and daemon are both version 26 or later, otherwise a host directory | `~/.supabase/stacks//` in your home directory |
| Downloaded artifacts | The engine's image cache | `~/.supabase/cache/stack` in your home directory, shared by all local projects |
| Isolation between local projects | Separate network namespaces and file systems | Shared host process table and file locks |
+A Docker local project keeps the database storage it was created with. A local project created on Docker 25 keeps its host directory after you upgrade to Docker 26.
+
Both runtimes keep each local project's saved settings under `~/.supabase/stacks//`. The native runtime keeps its database data there too. If you set `SUPABASE_HOME`, the CLI uses that directory instead of `~/.supabase`.
In both runtimes, Postgres starts right away and other services start on their first request. By default, services that started this way stop again after 60 seconds idle, and Studio after 5 minutes. Functions doesn't stop on its own. A service also stays up while a running service depends on it, such as pgmeta while Studio runs. Pass `--eager` to start every enabled service before the command returns and turn off idle stops.
@@ -69,7 +71,7 @@ The native runtime runs on these platforms:
The native runtime doesn't run as `root`, because Postgres `initdb` can't. To run it as `root`, such as in a CI container, set `SUPABASE_NATIVE_POSTGRES_USER` to a non-root account. Create the account first if it doesn't exist. The CLI then runs Postgres as that user. In some supported coding agent sandboxes, the CLI finds a suitable non-root account without the variable.
-On first start, the CLI downloads service archives from the [`supabase/slim-services` GitHub releases](https://github.com/supabase/slim-services/releases), with `supabase-cli-artifacts.s3.us-east-1.amazonaws.com` as a fallback. It verifies their checksums and extracts them with the system `tar`, so install `tar` in minimal sandbox images. In a sandbox with an allowlist, allow `github.com` or that S3 host. Allowing only `ghcr.io` isn't enough.
+On first start, the CLI downloads service archives from the [`supabase/slim-services` GitHub releases](https://github.com/supabase/slim-services/releases), with `supabase-cli-artifacts.s3.us-east-1.amazonaws.com` as a fallback. It verifies their checksums and extracts them with the system `tar`, so install `tar` in minimal sandbox images. In a sandbox with an allowlist, allow the S3 host. To use GitHub instead, allow both `github.com` and `release-assets.githubusercontent.com`, because GitHub redirects release downloads to that host. Allowing only `ghcr.io` isn't enough.
To download the archives ahead of time without starting any services, such as when you build a sandbox image, run:
diff --git a/apps/docs/content/guides/local-development/running-multiple-local-projects.mdx b/apps/docs/content/guides/local-development/running-multiple-local-projects.mdx
index b1766586047..e718ac1c1b8 100644
--- a/apps/docs/content/guides/local-development/running-multiple-local-projects.mdx
+++ b/apps/docs/content/guides/local-development/running-multiple-local-projects.mdx
@@ -57,13 +57,15 @@ You need:
The `supabase stack` commands exist only when the `[experimental] stack` setting is on. Without it, `supabase stack` commands fail with `Unknown subcommand "stack"`.
-1. Add this to `supabase/config.toml` in your app directory:
+1. In `supabase/config.toml` in your app directory, add `stack = true` under the existing `[experimental]` table. If your `config.toml` doesn't have an `[experimental]` table, add one:
```toml
[experimental]
stack = true
```
+ Don't add a second `[experimental]` table. A duplicate table makes `config.toml` invalid. The stack commands then stay off. Commands that read the config fail with `failed to load config`.
+
In a directory without a `config.toml`, set the environment variable instead:
```bash
@@ -166,7 +168,7 @@ One app can run several local projects by name. This keeps a destructive test ru
A local project started without `--stack` is the app directory's default local project. Named local projects and the default one are independent of each other.
-The `db`, `migration`, and other database commands use the default local project for the current branch. They can't target a named local project. See [Limitations](#limitations).
+The `db`, `migration`, and other database commands don't accept `--stack`. When they run against a local database, they use the default local project for the current branch. `supabase db pull`, `supabase db push`, and `supabase db dump` target the linked project unless you pass `--local`. To reach a named local project from a command that accepts `--db-url`, pass the `DB_URL` from `supabase status --stack --env`. See [Limitations](#limitations).
## Find a local project's endpoints and keys
@@ -253,9 +255,9 @@ supabase stack destroy --stack test --yes
`destroy` keeps two kinds of data:
- Storage upload files, which live in your app at `supabase/.temp/stack-uploads//`. Delete that directory to remove them.
-- Container engine resources, if Docker or Podman isn't running. If no process for the local project is still running, the command removes the local project's registration. It prints cleanup commands to run after the engine starts again. Otherwise it fails and asks you to start the engine and retry.
+- Container engine resources, if Docker or Podman isn't running. The command can still remove the local project's registration if no process for it is still running and your user can delete its database files on the host. It then prints cleanup commands to run after the engine starts again. Otherwise it fails, keeps the local project, and asks you to start the engine and retry. Your user might not be able to delete database files that a container wrote to a host directory. Local projects created with Docker version 25 or earlier store data that way.
-The printed cleanup command deletes only this local project's database data, from a Docker volume that other local projects share. Don't delete the volume itself.
+If the local project stores its database data in a Docker volume, other local projects share that volume. The printed cleanup command deletes only this local project's data from it. Don't delete the volume itself.
## Differences from the default `supabase start`
@@ -275,7 +277,7 @@ Keep these differences in mind when you turn the setting on for an app that used
- Storage Analytics and Storage vector buckets
- A custom `edge_runtime.deno_version`
- OrioleDB, and `db.major_version` values other than 15 and 17
-- Database commands can't target a named local project. `supabase db reset`, `supabase db diff`, `supabase db pull`, `supabase db dump`, and the other local database commands use the app directory's default local project for the current branch, and don't accept `--stack`.
+- Database commands don't accept `--stack`. `supabase db reset` and `supabase db diff` run against the current branch's default local project. `supabase db pull`, `supabase db push`, and `supabase db dump` target the linked project unless you pass `--local`. To reach a named local project, pass the `DB_URL` from `supabase status --stack --env` to a command that accepts `--db-url`.
- Switching branches switches local projects. The git branch is part of the identity. After you check out another branch in the same directory, `supabase start` creates or resumes a different local project with its own data. The first branch's local project keeps running until you stop it.
- Moving or renaming an app directory creates a new local project, because the identity includes the project root path. The previous local project's data stays until you destroy it.
- `supabase stack logs` streams new log lines only. It has no history.
diff --git a/apps/docs/content/guides/platform/access-control.mdx b/apps/docs/content/guides/platform/access-control.mdx
index 73bea005159..36717e0e9e8 100644
--- a/apps/docs/content/guides/platform/access-control.mdx
+++ b/apps/docs/content/guides/platform/access-control.mdx
@@ -11,10 +11,11 @@ For each organization and project, a member can have one of the following roles:
- **Administrator**: full access to everything in organization and project resources **except** updating organization settings, transferring projects outside of the organization, and adding new owners.
- **Developer**: read-only access to organization resources and content access to project resources but cannot change any project settings.
- **Read-Only**: read-only access to organization and project resources.
+- **No-access**: no access to any organization or project resources by default. Use this for members who should only see the specific projects they're granted a [project-scoped role](#organization-scoped-roles-vs-project-scoped-roles) on.
-Read-Only role is only available on the [Team and Enterprise plans](/pricing).
+Read-Only and No-access roles are only available on the [Team and Enterprise plans](/pricing).
@@ -67,6 +68,8 @@ Each member in the organization can be assigned a role that is scoped either to
This allows for more granular control, ensuring that users only have visibility and access to the projects relevant to their role.
+To default a member to zero project access and grant access on a per-project basis, assign them the organization-level **No access** role, then grant project-scoped roles for the specific projects they need. The **No-access** role is omitted from the permission tables below since it has none of the listed permissions by default.
+
### Organization permissions across roles
The table below shows the actions each role can take on the resources belonging to the organization.
diff --git a/apps/docs/content/troubleshooting/how-to-interpret-and-explore-the-postgres-logs-OuCIOj.mdx b/apps/docs/content/troubleshooting/how-to-interpret-and-explore-the-postgres-logs-OuCIOj.mdx
index d588c5a938f..c0ab18146cb 100644
--- a/apps/docs/content/troubleshooting/how-to-interpret-and-explore-the-postgres-logs-OuCIOj.mdx
+++ b/apps/docs/content/troubleshooting/how-to-interpret-and-explore-the-postgres-logs-OuCIOj.mdx
@@ -369,7 +369,7 @@ By default, only failed queries are logged. The [PGAudit extension](/docs/guides
#### Logging within database functions
-To track or debug functions, logging can be configured by following the [function debugging guide](/docs/guides/database/functions#general-logging)
+To track or debug functions, logging can be configured by following the [function debugging guide](/docs/guides/database/debugging-functions#general-logging)
## Frequently Asked Questions
@@ -389,7 +389,7 @@ To see the default types of events that are logged, you can check this [guide](h
- [Regex for filtering logs](https://github.com/orgs/supabase/discussions/22640)
- [Debugging with the DB API logs](https://github.com/orgs/supabase/discussions/22849)
-- [Debugging Database Functions](/docs/guides/database/functions#debugging-functions)
+- [Debugging Database Functions](/docs/guides/database/debugging-functions)
- [pg_audit](/docs/guides/database/extensions/pgaudit)
- [Supabase Logging](/docs/guides/observability/logs)
- [Self-Hosting Logs](/docs/reference/self-hosting-analytics/introduction)
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 83159679774..4f6ca7227c6 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
@@ -117,4 +117,4 @@ We support PGAudit extension, which extends Postgres’s built-in logging capabi
For detailed configuration instructions and logging options, refer to the complete documentation: [PGAudit Configuration Guide](/docs/guides/database/extensions/pgaudit?queryGroups=database-method&database-method=dashboard#configure-the-extension)
**c. Debugging Functions**
-For more information on how to debug functions in Supabase, refer to the official guide: [Debugging Functions](/docs/guides/database/functions?queryGroups=language&language=js#debugging-functions).
+For more information on how to debug functions in Supabase, refer to the official guide: [Debugging Functions](/docs/guides/database/debugging-functions).
diff --git a/apps/docs/data/content-listings/functions.data.ts b/apps/docs/data/content-listings/functions.data.ts
index bb69d870704..7400312387c 100644
--- a/apps/docs/data/content-listings/functions.data.ts
+++ b/apps/docs/data/content-listings/functions.data.ts
@@ -21,9 +21,9 @@ export const functionsExamplesSupabase: ContentListingGroup = {
type: 'grid',
items: [
{
- title: 'With supabase-js',
+ title: 'Securing Edge Functions',
href: '/guides/functions/auth',
- description: 'Use the Supabase client inside your Edge Function.',
+ description: 'Declare which credentials your Edge Function accepts.',
},
{
title: 'Connect to Postgres',
diff --git a/apps/docs/features/SearchV2/SearchV2.utils.test.tsx b/apps/docs/features/SearchV2/SearchV2.utils.test.tsx
index d79b031082c..632dafa905b 100644
--- a/apps/docs/features/SearchV2/SearchV2.utils.test.tsx
+++ b/apps/docs/features/SearchV2/SearchV2.utils.test.tsx
@@ -20,13 +20,13 @@ describe('formatHeadingPath', () => {
function renderHighlight(text: string, query: string) {
const result = highlightMatches(text, query)
- if (typeof result === 'string') return { html: result, strongTexts: [] as string[] }
+ if (typeof result === 'string') return { html: result, markTexts: [] as string[] }
const html = renderToStaticMarkup(<>{result}>)
const $ = load(html)
return {
html: $.root().text(),
- strongTexts: $('strong')
+ markTexts: $('mark')
.map((_, el) => $(el).text())
.get(),
}
@@ -37,19 +37,19 @@ describe('highlightMatches', () => {
const result = highlightMatches('Bring your own MCP', 'mcp server')
expect(typeof result).not.toBe('string')
- const { strongTexts, html } = renderHighlight('Bring your own MCP', 'mcp server')
- expect(strongTexts).toEqual(['MCP'])
+ const { markTexts, html } = renderHighlight('Bring your own MCP', 'mcp server')
+ expect(markTexts).toEqual(['MCP'])
expect(html).toBe('Bring your own MCP')
})
it('highlights multiple non-overlapping token matches independently', () => {
- const { strongTexts } = renderHighlight('MCP servers for your server', 'mcp server')
- expect(strongTexts).toEqual(['MCP', 'server', 'server'])
+ const { markTexts } = renderHighlight('MCP servers for your server', 'mcp server')
+ expect(markTexts).toEqual(['MCP', 'server', 'server'])
})
it('merges overlapping/adjacent matches into a single run', () => {
- const { strongTexts } = renderHighlight('server', 'server serv')
- expect(strongTexts).toEqual(['server'])
+ const { markTexts } = renderHighlight('server', 'server serv')
+ expect(markTexts).toEqual(['server'])
})
it('returns the original string unchanged when there is no match', () => {
@@ -58,8 +58,8 @@ describe('highlightMatches', () => {
})
it('is case-insensitive but preserves the original casing of the matched text', () => {
- const { strongTexts } = renderHighlight('Bring your own MCP', 'MCP')
- expect(strongTexts).toEqual(['MCP'])
+ const { markTexts } = renderHighlight('Bring your own MCP', 'MCP')
+ expect(markTexts).toEqual(['MCP'])
})
it('returns the text unchanged for an empty or whitespace-only query', () => {
@@ -68,8 +68,8 @@ describe('highlightMatches', () => {
})
it('excludes common prepositions/articles/conjunctions from highlighting', () => {
- const { strongTexts } = renderHighlight('The best MCP server for you', 'the mcp server')
- expect(strongTexts).toEqual(['MCP', 'server'])
+ const { markTexts } = renderHighlight('The best MCP server for you', 'the mcp server')
+ expect(markTexts).toEqual(['MCP', 'server'])
})
it('returns the text unchanged when the query is made up entirely of ignored words', () => {
diff --git a/apps/docs/features/SearchV2/SearchV2.utils.tsx b/apps/docs/features/SearchV2/SearchV2.utils.tsx
index d7db0f83e3c..b309ec1f7df 100644
--- a/apps/docs/features/SearchV2/SearchV2.utils.tsx
+++ b/apps/docs/features/SearchV2/SearchV2.utils.tsx
@@ -1,5 +1,13 @@
+import type { useDocsSearchV2 } from 'common'
import type { ReactNode } from 'react'
+type DocsSearchV2State = ReturnType['searchState']
+
+interface GetIsSearchingParams {
+ searchState: DocsSearchV2State
+ query: string
+}
+
/** Common English prepositions/articles/conjunctions, excluded from highlighting so a query like "the mcp server" doesn't bold "the". */
const IGNORED_WORDS = new Set([
'a',
@@ -65,7 +73,7 @@ function getMatchRanges(text: string, query: string): Array<[number, number]> {
/**
* Highlight every case-insensitive, per-word partial match of `query` inside `text`.
* Returns the plain string when there's no match, otherwise a fragment with matches
- * wrapped in , preserving the original casing of `text`.
+ * wrapped in , preserving the original casing of `text`.
*/
function highlightMatches(text: string, query: string): ReactNode {
const ranges = getMatchRanges(text, query)
@@ -75,7 +83,11 @@ function highlightMatches(text: string, query: string): ReactNode {
let cursor = 0
ranges.forEach(([start, end], i) => {
if (start > cursor) nodes.push(text.slice(cursor, start))
- nodes.push({text.slice(start, end)})
+ nodes.push(
+
+ {text.slice(start, end)}
+
+ )
cursor = end
})
if (cursor < text.length) nodes.push(text.slice(cursor))
@@ -83,4 +95,16 @@ function highlightMatches(text: string, query: string): ReactNode {
return <>{nodes}>
}
-export { formatHeadingPath, highlightMatches }
+// drives the loading beam: on while a request is in flight or about to be sent
+function getIsSearching({ searchState, query }: GetIsSearchingParams): boolean {
+ if (searchState.status === 'loading') return true
+ if (searchState.status === 'error') return false
+
+ const trimmedQuery = query.trim()
+ const settledQuery = 'query' in searchState ? searchState.query : null
+
+ // so a search is on its way and the beam starts on the keystroke, not when the request fires
+ return trimmedQuery !== '' && trimmedQuery !== settledQuery
+}
+
+export { formatHeadingPath, getIsSearching, highlightMatches }
diff --git a/apps/docs/features/SearchV2/SearchV2Dialog.tsx b/apps/docs/features/SearchV2/SearchV2Dialog.tsx
index ee12c97675a..e0ffd7f79c7 100644
--- a/apps/docs/features/SearchV2/SearchV2Dialog.tsx
+++ b/apps/docs/features/SearchV2/SearchV2Dialog.tsx
@@ -1,10 +1,10 @@
'use client'
+import { LoadingBeam } from '~/features/ui/LoadingBeam'
import { useDocsSearchV2, type DocsSearchV2Result } from 'common'
-import { Loader2 } from 'lucide-react'
import { useRouter } from 'next/navigation'
import { VisuallyHidden } from 'radix-ui'
-import { useEffect, useState } from 'react'
+import { useEffect, useRef, useState, type KeyboardEvent } from 'react'
import {
Command,
CommandEmpty,
@@ -16,34 +16,76 @@ import {
DialogContent,
DialogDescription,
DialogTitle,
+ KeyboardShortcut,
} from 'ui'
-import { formatHeadingPath, highlightMatches } from './SearchV2.utils'
+import { getIsSearching } from './SearchV2.utils'
+import { SearchV2Result } from './SearchV2Result'
+import { useSendTelemetryEvent } from '@/lib/telemetry'
interface SearchV2DialogProps {
open: boolean
onOpenChange: (open: boolean) => void
}
+interface SearchV2PanelProps {
+ onResultSelect: (path: string) => void
+ onResultOpen: () => void
+}
+
export function SearchV2Dialog({ open, onOpenChange }: SearchV2DialogProps) {
const router = useRouter()
+
+ function handleSelect(path: string) {
+ router.push(path)
+ onOpenChange(false)
+ }
+
+ function handleResultOpen() {
+ onOpenChange(false)
+ }
+
+ return (
+
+ )
+}
+
+function SearchV2Panel({ onResultSelect, onResultOpen }: SearchV2PanelProps) {
+ const sendTelemetryEvent = useSendTelemetryEvent()
const { searchState, handleDocsSearchDebounced, resetSearch } = useDocsSearchV2()
- const [highlightQuery, setHighlightQuery] = useState('')
+ const [query, setQuery] = useState('')
+ const [isDeleting, setIsDeleting] = useState(false)
+ const inputRef = useRef(null)
- // Clear stale results once the dialog closes
- useEffect(() => {
- if (!open) resetSearch()
- }, [open, resetSearch])
+ // highlight with the query the visible results belong to, not the one being typed
+ const highlightQuery =
+ 'query' in searchState
+ ? searchState.query
+ : 'staleQuery' in searchState
+ ? searchState.staleQuery
+ : ''
- // Only update the highlighted query once a new result set actually lands, so highlights
- // don't shift on every keystroke while the debounced search is still in flight.
useEffect(() => {
if (searchState.status === 'results' || searchState.status === 'noResults') {
- setHighlightQuery(searchState.query)
- } else if (searchState.status === 'initial') {
- setHighlightQuery('')
+ sendTelemetryEvent({
+ action: 'docs_search_v2_search_submitted',
+ properties: { query: searchState.query },
+ })
}
- }, [searchState])
+ }, [searchState, sendTelemetryEvent])
const results: DocsSearchV2Result[] =
'results' in searchState
@@ -52,7 +94,25 @@ export function SearchV2Dialog({ open, onOpenChange }: SearchV2DialogProps) {
? searchState.staleResults
: []
+ const isListVisible =
+ results.length > 0 || searchState.status === 'noResults' || searchState.status === 'error'
+ const isSearching = getIsSearching({ searchState, query })
+
+ function handleClear() {
+ setQuery('')
+ resetSearch()
+ inputRef.current?.focus()
+ }
+
+ // cmdk's root turns enter into "open highlighted result", so keep it on the button.
+ // other keys must bubble, or tab never reaches the dialog's focus trap
+ function handleClearKeyDown(event: KeyboardEvent) {
+ if (event.key === 'Enter') event.stopPropagation()
+ }
+
function handleValueChange(value: string) {
+ setIsDeleting(value.length < query.length)
+ setQuery(value)
if (value) {
handleDocsSearchDebounced(value)
} else {
@@ -61,8 +121,11 @@ export function SearchV2Dialog({ open, onOpenChange }: SearchV2DialogProps) {
}
function handleSelect(path: string) {
- router.push(path)
- onOpenChange(false)
+ sendTelemetryEvent({
+ action: 'docs_search_v2_result_clicked',
+ properties: { resultPath: path, query: highlightQuery },
+ })
+ onResultSelect(path)
}
// Announced via the aria-live region below — a sighted user sees the spinner/list update,
@@ -79,69 +142,89 @@ export function SearchV2Dialog({ open, onOpenChange }: SearchV2DialogProps) {
}
return (
-
+
+ {getStatusMessage()}
+
+
+ {searchState.status === 'noResults' && No results found.}
+ {searchState.status === 'error' && (
+ Something went wrong. Please try again.
+ )}
+ {results.length > 0 && (
+
+ {results.map((result) => (
+
+ ))}
+
+ )}
+
+ {results.length > 0 ? : null}
+
+ )
+}
+
+function SearchV2Footer() {
+ return (
+
)
}
diff --git a/apps/docs/features/SearchV2/SearchV2Result.tsx b/apps/docs/features/SearchV2/SearchV2Result.tsx
new file mode 100644
index 00000000000..d19147e9d8c
--- /dev/null
+++ b/apps/docs/features/SearchV2/SearchV2Result.tsx
@@ -0,0 +1,61 @@
+'use client'
+
+import type { DocsSearchV2Result } from 'common'
+import Link from 'next/link'
+import { useRef, type MouseEvent } from 'react'
+import { CommandItem } from 'ui'
+
+import { formatHeadingPath, highlightMatches } from './SearchV2.utils'
+
+interface SearchV2ResultProps {
+ result: DocsSearchV2Result
+ highlightQuery: string
+ onResultSelect: (path: string) => void
+ onResultOpen: () => void
+}
+
+export function SearchV2Result({
+ result,
+ highlightQuery,
+ onResultSelect,
+ onResultOpen,
+}: SearchV2ResultProps) {
+ // clicks navigate via the link and also fire onSelect, so onSelect only navigates for Enter
+ const isPointerSelectRef = useRef(false)
+
+ function handleLinkClick(event: MouseEvent) {
+ isPointerSelectRef.current = true
+ const isNewTab = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey
+ if (!isNewTab) onResultOpen()
+ }
+
+ function handleSelect() {
+ if (isPointerSelectRef.current) {
+ isPointerSelectRef.current = false
+ return
+ }
+ onResultSelect(result.path)
+ }
+
+ return (
+
+
+
+ onClose()}>
+
+
+ Spend cap
}>
{
About spend cap
-
- }
- tooltip={!canUpdateSpendCap ? 'You do not have permission to update spend cap' : undefined}
- >
-
-
-
- Use the spend cap to manage project usage and costs, and control whether the project can
- exceed the included quota allowance of any billed line item in a billing cycle
-
+
-
-
-
-
-
- How are each resource charged after exceeding the included quota?
-
-
-
-
-
-
-
Item
-
-
-
Rate
-
- >
+
+
+
+ Use the spend cap to manage project usage and costs, and control whether the project
+ can exceed the included quota allowance of any billed line item in a billing cycle
+
+
+
+
+
+
+
+ How is each resource charged after exceeding the included quota?
+
+ {selectedOption === 'on'
+ ? 'Upon clicking confirm, spend cap will be enabled for your organization and you will no longer be charged any extra for usage.'
+ : 'Upon clicking confirm, spend cap will be disabled for your organization and you will be charged for any usage beyond the included quota.'}
+
+
+ Toggling spend cap triggers an invoice and there might be prorated charges for any
+ usage beyond the Pro Plans quota during this billing cycle.
+
- {selectedOption === 'on'
- ? 'Upon clicking confirm, spend cap will be enabled for your organization and you will no longer be charged any extra for usage.'
- : 'Upon clicking confirm, spend cap will be disabled for your organization and you will be charged for any usage beyond the included quota.'}
-
-
- Toggling spend cap triggers an invoice and there might be prorated charges for any
- usage beyond the Pro Plans quota during this billing cycle.
-