Files
supabase/apps/docs/content/guides/database/functions.mdx
Miranda Limonczenko e29f4e0736 docs(database): style pass on the database functions guide (#50820)
Inline rewording only. Nothing moves and no claim changes.

Addresses reader-facing "we", UI labels in quotes rather than bold, "allows
you to", "e.g.", future tense, and title-case common nouns in body prose.
2026-09-30 14:36:17 -07:00

660 lines
15 KiB
Plaintext

---
id: 'functions'
title: 'Database Functions'
description: 'Creating and using Postgres functions.'
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).
## Quick demo
<YouTube id="MJZCCpCYEqk" title="Creating and calling Postgres functions" />
## Getting started
Create a database function from the Dashboard, or write the SQL yourself against a
[direct connection](../../guides/database/connecting-to-postgres).
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]
Create a basic database function that returns the string "hello world".
```sql
create or replace function hello_world() -- 1
returns text -- 2
language sql -- 3
as $$ -- 4
select 'hello world'; -- 5
$$; --6
```
<details>
<summary>Show/Hide Details</summary>
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. 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 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.
</details>
<Admonition type="caution">
Overloaded functions aren't supported. Give every function a unique name.
</Admonition>
After you create the function, you can run it inside the database with SQL, or with one of the client libraries.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="sql"
queryGroup="language"
>
<TabPanel id="sql" label="SQL">
```sql
select hello_world();
```
</TabPanel>
<TabPanel id="js" label="JavaScript">
```js
const { data, error } = await supabase.rpc('hello_world')
```
Reference: [`rpc()`](../../reference/javascript/rpc)
</TabPanel>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
```dart
final data = await supabase
.rpc('hello_world');
```
Reference: [`rpc()`](../../reference/dart/rpc)
</TabPanel>
</$Show>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
```swift
try await supabase.rpc("hello_world").execute()
```
Reference: [`rpc()`](../../reference/swift/rpc)
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
val data = supabase.postgrest.rpc("hello_world")
```
Reference: [`rpc()`](../../reference/kotlin/rpc)
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
```python
data = supabase.rpc('hello_world').execute()
```
Reference: [`rpc()`](../../reference/python/rpc)
</TabPanel>
</$Show>
<$Show if="sdk:csharp">
<TabPanel id="csharp" label="C#">
```c#
await supabase.Rpc("hello_world", null);
```
Reference: [`Rpc()`](../../reference/csharp/rpc)
</TabPanel>
</$Show>
</Tabs>
## 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:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="data"
queryGroup="example-view"
>
<TabPanel id="data" label="Data">
### Planets
```
| id | name |
| --- | -------- |
| 1 | Tatooine |
| 2 | Alderaan |
| 3 | Kashyyyk |
```
### People
```
| id | name | planet_id |
| --- | ---------------- | --------- |
| 1 | Anakin Skywalker | 1 |
| 2 | Luke Skywalker | 1 |
| 3 | Princess Leia | 2 |
| 4 | Chewbacca | 3 |
```
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
create table planets (
id serial primary key,
name text
);
insert into planets
(id, name)
values
(1, 'Tattoine'),
(2, 'Alderaan'),
(3, 'Kashyyyk');
create table people (
id serial primary key,
name text,
planet_id bigint references planets
);
insert into people
(id, name, planet_id)
values
(1, 'Anakin Skywalker', 1),
(2, 'Luke Skywalker', 1),
(3, 'Princess Leia', 2),
(4, 'Chewbacca', 3);
```
</TabPanel>
</Tabs>
The following function returns all the planets:
```sql
create or replace function get_planets()
returns setof planets
language sql
as $$
select * from planets;
$$;
```
Because this function returns a table set, you can apply filters and selectors to it. To get the first planet only:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="sql"
queryGroup="language"
>
<TabPanel id="sql" label="SQL">
```sql
select *
from get_planets()
where id = 1;
```
</TabPanel>
<TabPanel id="js" label="JavaScript">
```js
const { data, error } = supabase.rpc('get_planets').eq('id', 1)
```
</TabPanel>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
```dart
final data = await supabase
.rpc('get_planets')
.eq('id', 1);
```
</TabPanel>
</$Show>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
```swift
let response = try await supabase.rpc("get_planets").eq("id", value: 1).execute()
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
val data = supabase.postgrest.rpc("get_planets") {
filter {
eq("id", 1)
}
}
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
```python
data = supabase.rpc('get_planets').eq('id', 1).execute()
```
</TabPanel>
</$Show>
</Tabs>
## 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.
```sql
create or replace function add_planet(name text)
returns bigint
language plpgsql
as $$
declare
new_row bigint;
begin
insert into planets(name)
values (add_planet.name)
returning id into new_row;
return new_row;
end;
$$;
```
You can run this function inside your database with a `select` query, or with the client libraries:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="sql"
queryGroup="language"
>
<TabPanel id="sql" label="SQL">
```sql
select * from add_planet('Jakku');
```
</TabPanel>
<TabPanel id="js" label="JavaScript">
```js
const { data, error } = await supabase.rpc('add_planet', { name: 'Jakku' })
```
</TabPanel>
<$Show if="sdk:dart">
<TabPanel id="dart" label="Dart">
```dart
final data = await supabase
.rpc('add_planet', params: { 'name': 'Jakku' });
```
</TabPanel>
</$Show>
<$Show if="sdk:swift">
<TabPanel id="swift" label="Swift">
Using `Encodable` type:
```swift
struct Planet: Encodable {
let name: String
}
try await supabase.rpc(
"add_planet",
params: Planet(name: "Jakku")
)
.execute()
```
Using `AnyJSON` convenience` type:
```swift
try await supabase.rpc(
"add_planet",
params: ["name": AnyJSON.string("Jakku")]
)
.execute()
```
</TabPanel>
</$Show>
<$Show if="sdk:kotlin">
<TabPanel id="kotlin" label="Kotlin">
```kotlin
val data = supabase.postgrest.rpc(
function = "add_planet",
parameters = buildJsonObject { //You can put here any serializable object including your own classes
put("name", "Jakku")
}
)
```
</TabPanel>
</$Show>
<$Show if="sdk:python">
<TabPanel id="python" label="Python">
```python
data = supabase.rpc('add_planet', params={'name': 'Jakku'}).execute()
```
</TabPanel>
</$Show>
<$Show if="sdk:csharp">
<TabPanel id="csharp" label="C#">
```c#
await supabase.Rpc("add_planet", new Dictionary<string, object> { { "name", "Jakku" } });
```
</TabPanel>
</$Show>
</Tabs>
## 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.
### 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()
returns text
language plpgsql
security definer set search_path = ''
as $$
begin
return 'hello world';
end;
$$;
```
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, any role can run a database function. You can restrict execution in two ways:
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 execution by default, then grant access to the roles that need each function.
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 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;
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)
```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 <some condition>, '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 <function name>: %', 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 <advanced_example>: %', sqlerrm;
end;
$$;
select advanced_example();
```
## Resources
- 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)
## Deep dive
### Create Database Functions
<YouTube id="MJZCCpCYEqk" title="Creating a Postgres function in the dashboard" />
### Call Database Functions using JavaScript
<YouTube id="I6nnp9AINJk" title="Calling a Postgres function from JavaScript" />
### Using Database Functions to call an external API
<YouTube id="rARgrELRCwY" title="Calling an external API from a Postgres function" />