Introduction
When building modern apps, you often need to perform complex database operations filtering data, calculating values, or combining multiple queries. Writing all this logic directly in your Flutter app can quickly become messy and inefficient. That’s where RPC (Remote Procedure Call) functions in Supabase come in they let you write SQL functions inside your database and call them from your app as if they were APIs.
- Keep business logic inside the database (cleaner architecture)
- Reduce network calls (better performance)
- Improve security (controlled access to data)
- Reuse logic across multiple clients (Flutter, Web, etc.)
Prerequisites
- A Flutter project set up
- A Supabase project created
- Supabase Flutter SDK installed
- Basic knowledge of SQL (SELECT, WHERE, etc.)
Step 1: Create an RPC Function in Supabase
Let’s say we have a tasks table and want to fetch all completed tasks for a specific user. Go to Supabase Dashboard → SQL Editor and run:
create or replace function get_completed_tasks(p_user_id uuid)
returns table (
id uuid,
title text,
is_completed boolean
)
language sql
as $$
select id, title, is_completed
from tasks
where user_id = p_user_id
and is_completed = true;
$$;
create or replace functioncreates the RPC function.p_user_idinput parameter.returns tabledefines output structure.select ...actual query logic.
Step 2: Test the Function in Supabase
Before using it in Flutter, test it in the SQL Editor:
select * from get_completed_tasks('your-user-id-here');
You should see only completed tasks for that user.
Step 3: Call RPC Function in Flutter
Add Supabase Dependency
dependencies:
supabase_flutter: latest_version
Initialize Supabase
await Supabase.initialize(
url: 'YOUR_SUPABASE_URL',
anonKey: 'YOUR_SUPABASE_ANON_KEY',
);
Call RPC Function
final supabase = Supabase.instance.client;
Future<List> getCompletedTasks(String userId) async {
final response = await supabase.rpc(
'get_completed_tasks',
params: {'p_user_id': userId},
);
return response;
}
rpc()calls the Supabase function.'get_completed_tasks'function name.paramsmust match SQL parameter names exactly.
Use in UI
FutureBuilder(
future: getCompletedTasks(userId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final tasks = snapshot.data as List;
return ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
final task = tasks[index];
return ListTile(
title: Text(task['title']),
subtitle: Text("Completed"),
);
},
);
},
);
Real-World Use Cases
- Calculating total revenue
- Fetching user-specific dashboards
- Complex joins across tables
- Role-based data filtering
Common Mistakes
1. Parameter Name Mismatch
params: {'userId': userId} // ❌ WRONG must match SQL name exactly
params: {'p_user_id': userId} // ✅ CORRECT
2. Forgetting Permissions (RLS)
If Row Level Security (RLS) is enabled, make sure your policies allow access to the function.
3. Returning Wrong Data Type
If your function returns json, handle it properly in Flutter instead of expecting a list.
4. Not Testing in SQL First
Always test your function in the Supabase SQL Editor before calling it from Flutter.
Best Practices
- Keep functions simple and focused one function, one responsibility.
- Use meaningful names
get_user_tasks✔ vsfetch_data❌. - Secure with RLS policies don’t expose sensitive data.
- Use indexes for performance especially for large datasets.
- Prefer SQL functions over multiple API calls cleaner and faster.
Conclusion
RPC functions in Supabase are a powerful way to move logic closer to your database, making your Flutter apps faster, cleaner, and more secure. You can now create SQL functions, test them in Supabase, and call them seamlessly from Flutter. Next steps: add pagination, use PostgreSQL joins inside functions, explore Edge Functions vs RPC, and build a reusable backend logic library.