PostgREST Schema Cache Fix¶
Issue Summary¶
Problem: Supabase RPC functions returning 404 errors despite being created successfully.
Root Cause: PostgREST (Supabase's REST API layer) caches function schemas. When functions are created or modified, PostgREST may not see the changes immediately, causing 404 "function not found" errors.
Date Fixed: November 15, 2025
Symptoms¶
{
"event_message": "POST | 404 | ... | /rest/v1/rpc/create_user_record",
"response": {
"status_code": 404
}
}
Backend calls like:
Would fail with 404 even though the function exists in the database.
Solution¶
The Fix (Drop and Recreate Pattern)¶
Following Supabase documentation best practices, we:
- Drop all functions completely to clear PostgREST cache
- Recreate with proper PostgreSQL syntax:
#variable_conflict use_columnpragmalanguage plpgsqldeclaration- Clean
returns tableandreturn querysyntax
Example Pattern¶
-- Drop old function
drop function if exists create_user_record(text);
-- Create with proper syntax
create function create_user_record (
phone_number text
) returns table (
id uuid,
phone text,
-- ... other columns
)
language plpgsql
as $$
#variable_conflict use_column
declare
new_id uuid;
begin
new_id := gen_random_uuid();
insert into users (id, phone, is_provisional, subscription_status, credit_balance)
values (new_id, phone_number, true, 'free', 0);
return query
select
id,
phone::text,
-- ... other columns
from users
where id = new_id;
end;
$$;
Key Differences from Original Implementation¶
❌ Before (Broken)¶
CREATE OR REPLACE FUNCTION get_user_by_phone_number(...)
-- Missing #variable_conflict
-- Inconsistent syntax
✅ After (Working)¶
drop function if exists get_user_by_phone_number(text);
create function get_user_by_phone_number (...)
language plpgsql
as $$
#variable_conflict use_column
begin
return query
...
end;
$$;
Why This Matters¶
- PostgREST Caching: PostgREST caches function signatures for performance
- CREATE OR REPLACE: Doesn't always clear the cache properly
- DROP + CREATE: Forces cache invalidation
- Proper Syntax:
#variable_conflict use_columnprevents naming conflicts between function parameters and table columns
Verification¶
After applying the fix:
-- Test create_user_record
SELECT * FROM create_user_record('+15559999999');
-- ✅ Returns: User record created successfully
-- Test get_user_by_phone_number
SELECT * FROM get_user_by_phone_number('+15559999999');
-- ✅ Returns: User record found
Prevention¶
When Creating New RPC Functions¶
Always use this pattern:
-- 1. Drop first
drop function if exists my_function(param_types);
-- 2. Create with proper syntax
create function my_function (
param_name param_type
) returns table (
col1 type1,
col2 type2
)
language plpgsql
as $$
#variable_conflict use_column
begin
return query
select col1, col2
from my_table
where condition = param_name;
end;
$$;
Railway + Supabase Best Practices¶
- Always use DROP + CREATE (not CREATE OR REPLACE)
- Include
#variable_conflict use_column - Cast VARCHAR to TEXT in return queries
- Test functions immediately after creation
- Use Supabase MCP server for migrations (not Alembic)
Related Issues¶
- PostgREST schema cache doesn't auto-refresh
- Railway transaction pooling (port 6543) prevents direct connections
- Alembic migrations incompatible with transaction pooling
References¶
- Supabase Functions Guide: https://supabase.com/docs/guides/database/functions
- PostgREST Schema Cache: https://postgrest.org/en/stable/schema_cache.html
- Migration History:
20251115_drop_and_recreate_rpc_functions
Testing the Fix¶
Monitor Supabase logs for 404 errors:
Backend logs:
Status: ✅ Fixed Deployed: November 15, 2025 Functions Affected: All 7 RPC functions (users, conversations, messages)