diff --git a/apps/docs/content/guides/database/functions.mdx b/apps/docs/content/guides/database/functions.mdx
index 1ae88d1d1dc..b077f4c036a 100644
--- a/apps/docs/content/guides/database/functions.mdx
+++ b/apps/docs/content/guides/database/functions.mdx
@@ -6,7 +6,7 @@ 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 they can be [used with the API](../../reference/javascript/rpc).
+These functions live inside your database, and you can call them from your app with [`rpc()`](../../reference/javascript/rpc).
## Quick demo
@@ -14,14 +14,15 @@ These functions live inside your database, and they can be [used with the API](.
## Getting started
-Supabase provides several options for creating database functions. You can use the Dashboard or create them directly using SQL.
-We provide a SQL editor within the Dashboard, or you can [connect](../../guides/database/connecting-to-postgres) to your database
-and run the SQL queries yourself.
+Create a database function from the Dashboard, or write the SQL yourself against a
+[direct connection](../../guides/database/connecting-to-postgres).
-1. Go to the "SQL editor" section.
-2. Click "New Query".
-3. Enter the SQL to create or replace your Database function.
-4. Click "Run" or cmd+enter (ctrl+enter).
+To use the Dashboard:
+
+1. Go to the **SQL Editor** section.
+2. Click **New Query**.
+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]
@@ -40,26 +41,24 @@ $$; --6
Show/Hide Details
-At it's most basic a function has the following parts:
+At its most basic, a function has the following parts:
-1. `create or replace function hello_world()`: The function declaration, where `hello_world` is the name of the function. You can use either `create` when creating a new function or `replace` when replacing an existing function. Or you can use `create or replace` together to handle either.
-2. `returns text`: The type of data that the function returns. If it returns nothing, you can `returns void`.
+1. `create or replace function hello_world()`: The function declaration, where `hello_world` is the name of the function. Use `create` for a new function, `replace` for one that exists, or `create or replace` when the function might not exist yet.
+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 enclosed inside the `$$` symbols will be part of the function body.
-5. `select 'hello world';`: A basic function body. The final `select` statement inside a function body will be returned if there are no statements following it.
+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.
6. `$$;`: The closing symbols of the function wrapper.
-
-
-When naming your functions, make the name of the function unique as overloaded functions are not supported.
+Overloaded functions aren't supported. Give every function a unique name.
-After the Function is created, we have several ways of "executing" the function - either directly inside the database using SQL, or with one of the client libraries.
+After you create the function, you can run it inside the database with SQL, or with one of the client libraries.
-We could create a function which returns all the planets:
+The following function returns all the planets:
```sql
create or replace function get_planets()
@@ -223,7 +222,7 @@ as $$
$$;
```
-Because this function returns a table set, we can also apply filters and selectors. For example, if we only wanted the first planet:
+Because this function returns a table set, you can apply filters and selectors to it. To get the first planet only:
{ { "name", "Jak
### Database Functions vs Edge Functions
-For data-intensive operations, use Database Functions, which are executed within your database
-and can be called remotely using the [REST and GraphQL API](../api).
+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 which require low-latency, use [Edge Functions](../../guides/functions), which are globally-distributed and can be written in Typescript.
+For use cases that need low latency, use [Edge Functions](../../guides/functions). They're globally distributed and you write them in TypeScript.
### Security `definer` vs `invoker`
-Postgres allows you to specify whether you want the function to be executed as the user _calling_ the function (`invoker`), or as the _creator_ of the function (`definer`). For example:
+Postgres runs a function either as the user _calling_ it (`invoker`) or as its _creator_ (`definer`). For example:
```sql
create function hello_world()
@@ -434,31 +432,31 @@ end;
$$;
```
-It is best practice to use `security invoker` (which is also the default). If you ever use `security definer`, you _must_ set the `search_path`.
-If you use an empty search path (`search_path = ''`), you must explicitly state the schema for every relation in the function body (e.g. `from public.table`).
-This limits the potential damage if you allow access to schemas which the user executing the function should not have.
+Prefer `security invoker`, which is also the default. When you use `security definer`, you must set the `search_path`.
+
+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.
### Function privileges
-By default, database functions can be executed by any role. There are two main ways to restrict this:
+By default, any role can run a database function. You can restrict execution in two ways:
-1. On a case-by-case basis. Specifically revoke permissions for functions you want to protect. Execution needs to be revoked for both `public` and the role you're restricting:
+1. Revoke on a case-by-case basis. Revoke execute for the functions you want to protect, from both `public` and the role you're restricting:
```sql
revoke execute on function public.hello_world from public;
revoke execute on function public.hello_world from anon;
```
-1. Restrict function execution by default. Specifically _grant_ access when you want a function to be executable by a specific role.
+1. Restrict execution by default, then grant access to the roles that need each function.
- To restrict all existing functions, revoke execution permissions from both `public` _and_ the role you want to restrict:
+ To restrict every function that exists, revoke execute from both `public` and the role you want to restrict:
```sql
revoke execute on all functions in schema public from public;
revoke execute on all functions in schema public from anon, authenticated;
```
- To restrict all new functions, change the default privileges for both `public` _and_ the role you want to restrict:
+ To restrict every function created later, change the default privileges for both `public` and the role you want to restrict:
```sql
alter default privileges in schema public revoke execute on functions from public;
@@ -473,7 +471,7 @@ By default, database functions can be executed by any role. There are two main w
### Debugging functions
-You can add logs to help you debug functions. This is especially recommended for complex functions.
+Add logs to help you debug a function. Logs matter most in a complex function.
Good targets to log include:
@@ -482,7 +480,7 @@ Good targets to log include:
#### General logging
-To create custom logs in the [Dashboard's Postgres Logs](/dashboard/project/_/logs/postgres-logs), you can use the `raise` keyword. By default, there are 3 observed severity levels:
+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`
@@ -533,14 +531,14 @@ $$;
select error_if_null(null);
```
-Value checking is common, so Postgres provides a shorthand: the `assert` keyword. It uses the following format:
+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';
```
-Below is an example
+For example:
```sql
create function assert_example(name text)
@@ -567,7 +565,7 @@ $$;
select assert_example('Harry Potter');
```
-Error messages can also be captured and modified with the `exception` keyword:
+You can also capture and modify an error message with the `exception` keyword:
```sql
create function error_example()
@@ -587,7 +585,7 @@ $$;
#### Advanced logging
-For more complex functions or complicated debugging, try logging:
+For a more complex function, or for harder debugging, log the following:
- Formatted variables
- Individual rows