7

Mins

Supabase Database Functions vs Edge Functions: A Practical Guide to Choosing the Right One

Explore Supabase Database vs. Edge Functions understand their differences, use cases, and best practices for scalable backend logic

supabase-database-vs-edge-functions

CONTENT

Key Takeaways

  • Database Functions: Run inside Postgres, ideal for fast data transformations, validations, and triggers.

  • Edge Functions: Serverless, TypeScript-based, great for APIs, third-party integrations, and background jobs.

  • Rule of thumb: Use DB Functions for data logic; Edge Functions for external APIs, custom endpoints, or async tasks.

Many developers initially approach Supabase as a modern alternative to Firebase or simply as a hosted PostgreSQL database with some extra conveniences. And while that’s true to an extent, stopping there means you're barely scratching the surface. Supabase offers a robust set of features that allow you to build full-stack applications without spinning up separate backend servers - and one of the most powerful tools in this ecosystem is Supabase Functions.

In this guide, we’ll explore the two main types of functions Supabase offers: Database Functions (PostgreSQL functions) and Edge Functions (serverless HTTP functions). We’ll cover what each type is, when and why to use one over the other, and walk through practical code examples and real-world use cases. We'll also talk performance considerations and best practices to help you build scalable, maintainable logic on top of your Supabase project.

1. What Are Supabase Functions?

Supabase supports two kinds of backend logic:

  1. Database Functions (also known as Postgres Functions) — These are written in SQL or PL/pgSQL and run inside your PostgreSQL database. They are executed close to the data layer and are ideal for performing data transformations, validations, and other operations tightly coupled to your database logic.

  2. Edge Functions — These are written in TypeScript using the Deno runtime. They are deployed globally and run in Supabase's serverless infrastructure. Edge Functions are designed for handling HTTP requests, integrating with third-party APIs, and performing operations that fall outside the scope of what you would typically want your database to handle.

Choosing between these two depends on your application’s requirements. Do you need low-latency, data-level operations? Or do you need to call out to a third-party API, send an email, or expose a custom API endpoint?

2. Database Functions in Supabase

What Are Database Functions?

Database Functions in Supabase are PostgreSQL functions that run inside the database engine. Since they are native to Postgres, they offer extremely low-latency execution and are excellent for any logic that directly involves your data tables.

You can create these functions using the SQL Editor in the Supabase dashboard or by including them in your migration files for better version control.

How to Create a Database Function

Here’s an example where we automatically assign a manager to a newly inserted listing. This logic would typically be used to enforce business rules like load-balancing between available managers.

create or replace function assign_manager()
returns trigger as $$
begin
  update listings
  set manager_id = (
    select id from user_profiles
    where role = 'manager'
    order by random()
    limit 1
  )
  where id = new.id;
  return new;
end;
$$ language

create or replace function assign_manager()
returns trigger as $$
begin
  update listings
  set manager_id = (
    select id from user_profiles
    where role = 'manager'
    order by random()
    limit 1
  )
  where id = new.id;
  return new;
end;
$$ language

create or replace function assign_manager()
returns trigger as $$
begin
  update listings
  set manager_id = (
    select id from user_profiles
    where role = 'manager'
    order by random()
    limit 1
  )
  where id = new.id;
  return new;
end;
$$ language

You would then attach this function to a trigger:

create trigger auto_assign_manager
after insert on listings
for each row
execute function assign_manager()

create trigger auto_assign_manager
after insert on listings
for each row
execute function assign_manager()

create trigger auto_assign_manager
after insert on listings
for each row
execute function assign_manager()

How to Access a Database Function via RPC (HTTP)

Once you've created a Database Function in Supabase (especially one that returns a result), Supabase automatically exposes it through its PostgREST API layer. This means you can call it from any frontend or backend client using either the Supabase client SDK or a direct HTTP request.

Using the Supabase Client (JS/TS)

const { data, error } = await supabase
  .rpc('get_recommended_locations', { budget: 1500 });
const { data, error } = await supabase
  .rpc('get_recommended_locations', { budget: 1500 });
const { data, error } = await supabase
  .rpc('get_recommended_locations', { budget: 1500 });

This method handles authentication and formatting internally, making it the easiest way to call your SQL functions from JavaScript or frontend tools like WeWeb or FlutterFlow (via plugins).

Calling via HTTP Endpoint (RPC REST API)

Supabase also allows calling database functions directly via HTTP using the /rest/v1/rpc/ route. Here's how you do it with curl:

curl -X POST 'https://<your-project>.supabase.co/rest/v1/rpc/get_recommended_locations' \
  -H "Content-Type: application/json" \
  -H "apikey: YOUR_SUPABASE_ANON_OR_SERVICE_ROLE_KEY" \
  -H "Authorization: Bearer YOUR_SUPABASE_JWT" \
  -d '{ "budget": 1500 }'
curl -X POST 'https://<your-project>.supabase.co/rest/v1/rpc/get_recommended_locations' \
  -H "Content-Type: application/json" \
  -H "apikey: YOUR_SUPABASE_ANON_OR_SERVICE_ROLE_KEY" \
  -H "Authorization: Bearer YOUR_SUPABASE_JWT" \
  -d '{ "budget": 1500 }'
curl -X POST 'https://<your-project>.supabase.co/rest/v1/rpc/get_recommended_locations' \
  -H "Content-Type: application/json" \
  -H "apikey: YOUR_SUPABASE_ANON_OR_SERVICE_ROLE_KEY" \
  -H "Authorization: Bearer YOUR_SUPABASE_JWT" \
  -d '{ "budget": 1500 }'

While this is a valid HTTP call, keep in mind:

  • It always uses the POST method, even for data fetching.

  • You must format your d payload as JSON matching the function parameters.

  • Custom headers or flexible routing are not supported — this is strictly tied to the database structure.


When Should You Use Database Functions?

Database Functions are well-suited for the following scenarios:

  • Automatically assigning or calculating values during inserts or updates

  • Validating data before writing it to the database

  • Transforming or enriching records as part of a trigger

  • Centralizing business logic that must always run on the database side

  • Running complex calculations or filtering logic that benefits from running close to the data

3. Edge Functions in Supabase

What Are Edge Functions?

Supabase Edge Functions are serverless functions written in TypeScript using the Deno runtime. They are deployed to Supabase’s globally distributed infrastructure, which enables low-latency responses anywhere in the world.

Unlike Database Functions, Edge Functions are designed to handle HTTP requests. They are ideal for custom backend logic that needs to be exposed to clients or integrated with third-party systems.

How to Create an Edge Function

Using the Supabase CLI, you can create and deploy an Edge Function with a few simple commands.

This creates a directory under supabase/functions/send-tour-email.

Here is an example of a basic function that sends an email when a user books a tour:

// supabase/functions/send-tour-email/index.ts
import { serve } from "<https://deno.land/std/http/server.ts>";

serve(async (req) => {
  const { email, tour_id } = await req.json();

  // Here you might send an email using a third-party service like Resend, Mailgun, etc.
  console.log(`Sending confirmation to ${email} for tour ID ${tour_id}`);

  return new Response(`Email sent to ${email}`, { status: 200 });
});
// supabase/functions/send-tour-email/index.ts
import { serve } from "<https://deno.land/std/http/server.ts>";

serve(async (req) => {
  const { email, tour_id } = await req.json();

  // Here you might send an email using a third-party service like Resend, Mailgun, etc.
  console.log(`Sending confirmation to ${email} for tour ID ${tour_id}`);

  return new Response(`Email sent to ${email}`, { status: 200 });
});
// supabase/functions/send-tour-email/index.ts
import { serve } from "<https://deno.land/std/http/server.ts>";

serve(async (req) => {
  const { email, tour_id } = await req.json();

  // Here you might send an email using a third-party service like Resend, Mailgun, etc.
  console.log(`Sending confirmation to ${email} for tour ID ${tour_id}`);

  return new Response(`Email sent to ${email}`, { status: 200 });
});

Deploy the function using:

After deployment, the function is accessible via a public URL:

https://<your-project-ref>
https://<your-project-ref>
https://<your-project-ref>

When Should You Use Edge Functions?

Edge Functions are an excellent choice for use cases that involve:

  • Creating secure custom API endpoints

  • Consuming or responding to third-party webhooks (e.g., Stripe, Slack)

  • Performing server-side logic that cannot run inside the database (e.g., fetching external APIs, sending emails)

  • Background tasks such as PDF generation, analytics tracking, or processing user-uploaded files

  • Scheduled jobs (using GitHub Actions or external schedulers to call the functions on a cron schedule)

Security Model: How RLS Works Differently for Database vs Edge Functions

Row Level Security (RLS) in Supabase is not just a feature. It is your core security layer. But the way it behaves changes depending on whether you're working inside the database or through Edge Functions.

RLS in Database Functions

When you use Database Functions, RLS is enforced natively at the database level.

  • Every query automatically passes through RLS policies.

  • Policies are evaluated using the user’s JWT (via auth.uid() and claims).

  • There is no way to accidentally bypass RLS unless you explicitly use elevated roles.

This makes Database Functions inherently secure by design. Even if someone directly queries your database API, RLS still protects your data.

What this means in practice:

  • You don’t need to write extra authorization logic.

  • Security stays consistent across frontend, API, and internal calls.

  • Ideal for multi-tenant apps where strict data isolation matters.

RLS in Edge Functions

Edge Functions sit outside the database. So RLS is not automatically applied unless you explicitly pass user context.

Here’s where things get nuanced:

  • If you call the database using a user’s JWT → RLS is enforced.

  • If you use a service role key → RLS is bypassed entirely.

  • If you forget to forward auth headers → queries may fail or return empty results.

This happens because RLS only works when the database can identify the user making the request.

What this means in practice:

  • You are responsible for forwarding and validating auth tokens.

  • You can intentionally bypass RLS for admin or backend tasks.

  • You gain flexibility, but also take on more security responsibility.

The Real Difference (Simple Mental Model)

  • Database Functions → Security is automatic and enforced

  • Edge Functions → Security is optional and must be handled correctly

If you want “secure by default,” lean on the database. If you want “controlled flexibility,” use Edge Functions carefully.

4. Choosing Between Database Functions and Edge Functions

Here’s a simplified comparison to help you decide which type of function to use based on your use case:

Feature / Use Case

Database Function (SQL)

Edge Function (TypeScript)

Runs inside database

Yes

No

Can be triggered by table events

Yes

No

Can call external APIs

Difficult

Yes

Low-latency execution

Very fast (internal)

Fast (network-dependent)

Exposed as HTTP endpoint

Yes

(via Supabase’s auto-generated RPC endpoint — POST /rest/v1/rpc/<function>)

Yes

(deployed as a fully customisation HTTP endpoint — supports GET, POST, custom paths, and headers)

Integrates with frontend apps

Yes

(can be called via HTTP or client SDK, but limited to JSON format and POST only)

Yes

(full HTTP control — headers, query params, custom routes — ideal for frontend or external API use)

Best for

Data transformation, validation, enforcement

APIs, external integration, background jobs

A good rule of thumb: use Database Functions for anything that strictly involves data manipulation within your database. Use Edge Functions when you need to step outside of the database — whether it’s sending an email, processing a file, or communicating with another service.

5. Real-World Use Case Examples

Normalizing Survey Responses with a Database Function

You might receive raw JSON responses from a frontend survey tool. To ensure data consistency, you can use a Database Function to normalize the structure before storing it:

create or replace function normalize_response(response json)
returns json as $$
begin
  -- Apply normalization logic
  return response; -- Example: sanitized or restructured version
end;
$$ language

create or replace function normalize_response(response json)
returns json as $$
begin
  -- Apply normalization logic
  return response; -- Example: sanitized or restructured version
end;
$$ language

create or replace function normalize_response(response json)
returns json as $$
begin
  -- Apply normalization logic
  return response; -- Example: sanitized or restructured version
end;
$$ language

This function can then be used in an insert trigger or even called manually during an ETL (extract-transform-load) process.

Sending a Welcome Email on Client Registration with an Edge Function

Here’s a case where a new client signs up and you want to send a welcome email. You could trigger an Edge Function from your frontend:

const res = await fetch('https://<project-ref>.functions.supabase.co/send-welcome', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'client@example.com' }),
});
const res = await fetch('https://<project-ref>.functions.supabase.co/send-welcome', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'client@example.com' }),
});
const res = await fetch('https://<project-ref>.functions.supabase.co/send-welcome', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'client@example.com' }),
});

The Edge Function might then send the email, log the signup in a Slack channel, and return a success message to the frontend.

Using Edge Functions and Database Functions Together

This is where Supabase becomes powerful. You don’t choose one or the other. You combine them.

The best production setups use Database Functions for data logic and Edge Functions as the orchestration layer.

The Ideal Architecture

Think of it like this:

  • Database Functions → Handle data-level rules

  • Edge Functions → Handle application-level logic

A typical flow looks like:

  1. Client calls an Edge Function

  2. Edge Function validates input, handles auth, or calls external APIs

  3. Edge Function calls a Database Function (RPC)

  4. Database Function executes logic with RLS enforced

  5. Response is returned to the client

This pattern keeps your system both secure and scalable.

Why This Combo Works So Well

1. Security stays centralized
Your core rules live in the database with RLS. Even if your API layer changes, your data stays protected.

2. Cleaner separation of concerns

  • Database handles data integrity and constraints

  • Edge handles workflows, integrations, and APIs

3. Better performance where it matters
Database Functions run close to the data with very low latency, while Edge Functions handle global requests and external calls efficiently.

Real-World Example

Let’s say you're building a SaaS app:

  • Edge Function:

    • Accepts a “create order” request

    • Calls Stripe API for payment

    • Validates input

  • Database Function:

    • Inserts order

    • Assigns ownership

    • Enforces tenant-level RLS

Even if someone bypasses your Edge Function and hits the database directly, RLS still protects the data.

When to Use What (Quick Rule)

  • Use Database Functions when logic is tightly tied to your data

  • Use Edge Functions when logic involves APIs, workflows, or external systems

  • Use both when building real-world, production-grade apps

6. Performance Considerations

When it comes to performance, both function types are optimized for their respective domains, but there are differences.

  • Database Functions are incredibly fast because they are executed inside the database engine. There is no network overhead, and operations are optimized at the SQL level.

  • Edge Functions are also fast due to their serverless edge deployment, but they involve network hops and startup time (albeit minimal due to Deno’s low cold start times).

You should avoid pulling data from the database into an Edge Function just to perform a transformation that could have been done using SQL or a Database Function. This helps reduce latency and unnecessary traffic between layers.

7. Best Practices and Common Pitfalls

  • Keep your functions modular and small. Whether you're using SQL or TypeScript, separating concerns improves maintainability.

  • For Edge Functions, avoid heavy startup dependencies. The Deno runtime is fast, but bloated functions will incur cold start delays.

  • Use environment variables for secure credentials in Edge Functions — don’t hardcode API keys or tokens.

  • Monitor your function logs and usage through the Supabase dashboard or CLI tools to avoid hitting rate limits, especially on the free tier.

  • Combine function types when needed. For example, you can have an Edge Function call a Database Function if both forms of logic are necessary for a complete workflow.

8. Conclusion

Supabase Functions allow you to move beyond treating Supabase as just a database. With Database Functions, you can embed business logic right at the data layer. With Edge Functions, you can build modern, serverless APIs that extend your application’s capabilities to external services.

To summarize:

  • Use Database Functions for data-centric operations, validation, and triggers.

  • Use Edge Functions for HTTP APIs, third-party integrations, and background tasks.

  • Don’t hesitate to mix both in a single workflow for maximum flexibility.

Understanding when and how to use these tools is key to building performant and scalable Supabase-powered applications. Once you do, you’ll find that you rarely need to reach for a traditional backend framework.

Frequently asked questions

Frequently asked questions

Q1: Can I call a Database Function from an Edge Function?

Yes, you can call a PostgreSQL database function from a Supabase Edge Function. Use the Supabase client library within your Edge Function to make a request to the database.

Q2: Do Edge Functions support Python?

No, Supabase Edge Functions currently support only TypeScript/JavaScript using the Deno runtime. Python is not supported. All function logic should be written using Deno-compatible TypeScript, following Supabase's .

SHARE