Database functions
Write SQL and PL/pgSQL functions, call them from SQL, REST and supabase-js, choose who they run as, use them in triggers and policies, and manage them under Schema › Functions.


A database function is code stored in your project's Postgres database and run there, next to the data. Use one when several writes must happen together or not at all, when the same logic is needed in many queries or policies, or as the code a trigger runs. For code that calls other services, sends email or takes more than a few seconds, use Edge Functions instead.
| Use a database function for | Use an Edge Function for |
|---|---|
| Several writes in one transaction (transfer points, place an order) | Calling third-party APIs (payments, email, AI models) |
Logic shared by queries and policies (is_member(team_id)) | Work that takes seconds or minutes |
| Computing values next to the data (totals, rankings, search) | Code in Node.js, Python or Go with packages |
| The code a trigger runs on every insert, update or delete | Webhooks from other services into your app |
In the console
Schema › Functions lists the functions in the selected schema. Functions that came with extensions (pgcrypto, pg_trgm…) and the few Sovaria keeps in your schema for documents and table history are hidden; Show N from extensions and Sovaria lists them too.
| Column | Shows |
|---|---|
| Name | The function and its arguments, as you call it: add_points(user_id uuid, amount integer). |
| Returns | Its return type: a value, setof rows, void or trigger. |
| Language | plpgsql, sql, or the language of an extension function. |
| Runs as | Caller (security invoker) or Owner (security definer) — see [[#security]]. |
| Used by | How many triggers run it. Such functions can't be deleted until the triggers are. |
Click a function to see its full definition, comment and facts, copy it, or Open in SQL editor to try it. The row menu has Edit and Delete.
Create a function
New function
Under Schema › Functions, click New function. It is created in the schema selected at the top of the page.
Name and arguments
Lowercase with underscores. Add arguments with a name, a type (
uuid,text,integer,numeric,jsonb,timestamptz…) and an optional default. Callers over REST pass arguments by name, so name them well.Returns
Pick a type,
voidfor nothing,setof <table>for rows of a table, ortriggerfor a function a trigger runs (it then takes no arguments).Language, behaviour and who it runs as
PL/pgSQL for variables,
ifand loops; SQL for a single query (faster to plan). Behaviour and runs-as are explained below; the defaults (volatile, caller) are safe.Body and description
Write the body. The description is saved as the function's comment and shown in the list.
Check, then create
Check runs the change in a transaction that is rolled back, so a typo is caught without changing anything. Create function applies it as a recorded migration: it shows in Version history: see, undo and restore changes, where you can undo it.
The panel shows the exact SQL it will run and the SQL that undoes it. Changing functions, triggers and indexes needs a project owner or admin, as any schema change does (Migrations).
-- Count a user's published posts.
create or replace function post_count(u uuid)
returns integer
language sql
stable
as $
select count(*)::int from posts where author_id = u and published
$;What a function can return
| Returns | Example | Over REST you get |
|---|---|---|
| One value | returns integer, returns text, returns jsonb | The value itself: 42 |
| Rows of a table | returns setof posts | An array of row objects (filter, order and limit them like a table) |
| Rows you define | returns table (id uuid, score numeric) | An array of objects with those keys |
| Nothing | returns void | null |
| A trigger result | returns trigger | Not callable: only triggers run it |
Behaviour: volatile, stable, immutable
Behaviour tells Postgres how often it needs to run the function. Getting it right lets Postgres cache results and use the function in indexes; getting it wrong can return stale results.
| Behaviour | Promise | Use for |
|---|---|---|
| Volatile (default) | None: may write, and may return something different each call. | Anything that writes, or uses random() or clock_timestamp(). |
| Stable | Doesn't write; returns the same result for the same arguments within one statement. | Lookups and helpers used in policies, like is_member(team_id). |
| Immutable | Depends only on its arguments, never on table data. | Pure calculations (slugify(text)); required for use in an index expression. |
Who a function runs as
| Runs as | Row-level security | When |
|---|---|---|
Caller (security invoker, the default) | Applies: the function sees only the rows the caller may see. | Almost always. |
Owner (security definer) | Skipped: the function sees and changes every row, as its owner. | A small, checked operation app users can't do directly, such as joining a team by invite code. |
A security definer function runs with more rights than its caller, so keep it small and check the caller inside it with auth.uid(). The console adds set search_path = '' to definer functions, so a caller can't slip in their own table or function with the same name; write every name with its schema (public.posts, not posts).
create or replace function public.join_team(invite text)
returns uuid
language plpgsql
security definer
set search_path = ''
as $
declare
t uuid;
begin
if auth.uid() is null then
raise exception 'sign in first' using errcode = '42501';
end if;
select team_id into t from public.team_invites where code = invite and expires_at > now();
if t is null then
raise exception 'invite not found or expired' using errcode = 'P0002';
end if;
insert into public.team_members (team_id, user_id) values (t, auth.uid()) on conflict do nothing;
return t;
end;
$;Call a function
// Named arguments, exactly as the function declares them.
const n = await sovaria.data.rpc('post_count', { u: userId }) // → 12
await sovaria.data.rpc('transfer_points', { from_user: from, to_user: to, amount: 50 })
// Functions that only read can use GET; functions that return rows take PostgREST parameters.
const top = await sovaria.data.rpc('top_posts', { days: 7 }, { get: true, query: { select: 'id,title', limit: 10 } })POST /rest/v1/rpc/<name>takes the arguments by name as a JSON object;GETtakes them as query parameters, for functions that only read (HTTP API).- A function that returns one value answers with that value; one that returns rows answers with an array, which you can filter, order and limit like a table (
?select=id,title&order=id.desc&limit=10). - App users can call a function once an access policy allows the resource
fn:<name>— nothing allows it by default (Row-level security and access). The function's own queries still run under row-level security unless it is a security definer. - Every SDK has the helper:
sovaria.data.rpc()in JavaScript (JavaScript: Database and documents),rpc()in Python (Python: Database and documents), anddb.rpc()in Dart, Swift and Kotlin. An error the function raises comes back with the database'scodeandmessagein the error's details. - Server code with the service role key can call any function.
Functions in policies and defaults
A stable function keeps row-level security policies short and fast. Make it a security definer when it has to read a table the caller can't, such as the membership table itself:
create or replace function public.is_member(team uuid)
returns boolean
language sql
stable
security definer
set search_path = ''
as $
select exists (select 1 from public.team_members where team_id = team and user_id = auth.uid())
$;
create policy "members read team projects" on projects
for select using (public.is_member(team_id));Functions also work as column defaults (default gen_random_uuid(), default auth.uid()) and in generated columns and index expressions when they are immutable.
Change or delete a function
- Edit opens the full definition. Saving runs it as
create or replace, and undo restores the previous definition. - Postgres can't change a function's return type or argument names in place (
42P13). Changing the argument types creates a second function with the same name (an overload). In both cases, delete the old one and create the new one. - Delete is refused while a trigger, view or policy still uses the function (
2BP01); remove those first. Undo re-creates it from its saved definition. - Functions from extensions are managed by the extension: turn the extension off to remove them (Extensions).
Limits
Your plan limits how many functions and procedures a project has, across all your schemas. Functions that came with an extension and Sovaria's own (sov_*) don't count. Creating one past the limit fails with 54000 and says which plan raises it.
| Free | Pro | Ultra | Team | Enterprise | |
|---|---|---|---|---|---|
| Stored procedures | 20 | 500 | 2,000 | 10,000 | 50,000 |
A function runs inside the statement that called it, so the statement timeout covers it (Pooling and timeouts).
Errors
| Error | Means | Fix |
|---|---|---|
42883 function … does not exist | No function with that name takes those argument types. | Check the names and types; over REST, the argument names must match the declaration. Cast literals ($1::uuid). |
42P01 relation … does not exist inside a definer function | The function has an empty search_path. | Write the schema: public.posts. |
42501 permission denied / refused by policy | No access policy allows fn:<name> for this user. | Add one (Row-level security and access). |
42P13 cannot change return type | create or replace can't change the signature. | Delete the function, then create it. |
2BP01 cannot drop … because other objects depend on it | A trigger, view or policy uses it. | Remove those first; the error lists them. |
57014 canceling statement due to statement timeout | It ran longer than the timeout. | Add an index for its queries (Indexes) or move slow work to an Edge Function. |
Coming from Firebase or Supabase?
| Platform | What this is called there |
|---|---|
| Supabase | Supabase Database Functions |
| Sovaria | Database functions |
Moving an existing app? Migrate from Supabase or Firebase imports users (with their passwords), data, files and rules.