Docs

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.

Updated Oct 20, 2026
Schema › Functions with a list of functionsSchema › Functions with a list of functions
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 forUse 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 deleteWebhooks 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.

ColumnShows
NameThe function and its arguments, as you call it: add_points(user_id uuid, amount integer).
ReturnsIts return type: a value, setof rows, void or trigger.
Languageplpgsql, sql, or the language of an extension function.
Runs asCaller (security invoker) or Owner (security definer) — see [[#security]].
Used byHow 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

  1. New function

    Under Schema › Functions, click New function. It is created in the schema selected at the top of the page.

  2. 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.

  3. Returns

    Pick a type, void for nothing, setof <table> for rows of a table, or trigger for a function a trigger runs (it then takes no arguments).

  4. Language, behaviour and who it runs as

    PL/pgSQL for variables, if and loops; SQL for a single query (faster to plan). Behaviour and runs-as are explained below; the defaults (volatile, caller) are safe.

  5. Body and description

    Write the body. The description is saved as the function's comment and shown in the list.

  6. 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

ReturnsExampleOver REST you get
One valuereturns integer, returns text, returns jsonbThe value itself: 42
Rows of a tablereturns setof postsAn array of row objects (filter, order and limit them like a table)
Rows you definereturns table (id uuid, score numeric)An array of objects with those keys
Nothingreturns voidnull
A trigger resultreturns triggerNot 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.

BehaviourPromiseUse for
Volatile (default)None: may write, and may return something different each call.Anything that writes, or uses random() or clock_timestamp().
StableDoesn't write; returns the same result for the same arguments within one statement.Lookups and helpers used in policies, like is_member(team_id).
ImmutableDepends 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 asRow-level securityWhen
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; GET takes 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), and db.rpc() in Dart, Swift and Kotlin. An error the function raises comes back with the database's code and message in 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.

FreeProUltraTeamEnterprise
Stored procedures205002,00010,00050,000

A function runs inside the statement that called it, so the statement timeout covers it (Pooling and timeouts).

Errors

ErrorMeansFix
42883 function … does not existNo 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 functionThe function has an empty search_path.Write the schema: public.posts.
42501 permission denied / refused by policyNo access policy allows fn:<name> for this user.Add one (Row-level security and access).
42P13 cannot change return typecreate or replace can't change the signature.Delete the function, then create it.
2BP01 cannot drop … because other objects depend on itA trigger, view or policy uses it.Remove those first; the error lists them.
57014 canceling statement due to statement timeoutIt 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?

PlatformWhat this is called there
SupabaseSupabase Database Functions
SovariaDatabase functions

Moving an existing app? Migrate from Supabase or Firebase imports users (with their passwords), data, files and rules.