- supa.guide
- Posts
- #2 Rate Limiting with Supabase and a Cron UI
#2 Rate Limiting with Supabase and a Cron UI
Cloudflare, PostgREST Middleware and some JWT Quiz
First and foremost, have an awesome and successful 2025. You can respond to this newsletter btw if you want to send me something into my inbox! But now let’s get started.
Quiz Solution from #1:
In the last newsletter, I asked what’s wrong with this Supabase JS:
await supabase.from('supa_guide')
.select('email_title, email_content')
.order('id', { descending: true })
.limit(1)
The correct answer: There is no descending
option. You enable descending
by setting ascending: false
😉 . The rest was correct.
Quiz #2: Do you know about JWTs?
What is true about JWT?A JWT is an access token which a user gets when logging in with Supabase Auth. However, can you tell which of these is correct? Vote by clicking one of the answers. Only one is true. |
Supabase Snacks
SNACK 1/3: The Supabase Database API is just PostgREST
When you use the Supabase API to fetch data from the db, you’re talking to PostgREST. Many users of Supabase use the API without knowing that it’s actually 100% PostgREST.
Why is that important? It’s important because it means that the API Docs are not to be found on supabase.com but directly here: https://docs.postgrest.org/.
For example if you’re using the Supabase JS library and want to use or()
but don’t know what syntax to use inside of it or which operators are allowed, you can just open this to look it up: https://docs.postgrest.org/en/v12/references/api/tables_views.html#operators .
PS: Steve, the maintainer of PostgREST, is working inside the Supabase team!
SNACK 2/3: Let me rate limit my API requests!
Supabase doesn’t yet have Rate Limiting built in natively. You can apply Rate Limiting, but how?
The Supabase infrastructure has DDOS protection, so if that’s waht you worry about, you can just stop worrying. However if you fear that a single user could trigger too many API requests, then here are a few tips on how to RL your API:
2.1. Using Cloudflare for FREE
Connect a Custom Domain and let Cloudflare manage the DNS of that domain. Then, you can apply some basic Rate Limiting via Cloudflares Rules for free ❤️

If you wanted different duration/periods or more complex rules, you’d have to upgrade your CF account. 🤑
Important: Adding a Custom Domain with CF Rate Limits helps to avoid e.g. buggy behaviour or when a user has too many tabs open or whatever. It would not necessarily prevent malicious attacks because if your my-unicorn-startup.lol domain is mapped, it doesn’t mean that the original project URL, to which your custom domain maps , is replaced. Read here: https://supabase.com/docs/guides/platform/custom-domains#prepare-to-activate-your-domain . But check number 2 in the list now.
2.2. Implementing Rate Limiting DIRECTLY inside your Supabase API 💥
In my book, supa.guide, I am describing what I call the Supabase API Middleware (or more specifically: the PostgREST middleware).
There, I describe how to use it to build your own API with API keys and reject any request that isn’t sending a proper API key along. Cool stuff.
However, now I want to show you how to use it to Rate Limit requests directly in the API, so you reject them before making the actual database request.
The basis of all of this is this snippet → https://supabase.com/docs/guides/api/securing-your-api?queryGroups=pre-request&pre-request=rate-limit-per-ip&queryGroups=database-method&database-method=sql (scroll down to Rate limit per IP
; the Deeplink to the section doesn’t work).
HOWEVER, for the most common use cases - standard GET requests, it DOES NOT WORK ; but before you cry: I got you covered 👇️
The only troublesome thing in GET
requests is this part:
insert into private.rate_limits (ip, request_at) values (req_ip, now());
That’s because when running a GET
request, you cannot INSERT
because PostgREST locks into a read-only
transaction.
However, you can obviously do HTTP requests 😎 - all you have to do is to activate the http
extension in your Supabase.
And then…. you can just fire up a request to your own instance to trigger an RPC which will then insert into your rate_limits
table:
PERFORM http(
('POST',
'https://YOUR_ID.supabase.co/rest/v1/rpc/rl',
ARRAY[http_header('apikey','ANON_KEY')],
'application/json',
'{"ip": "123.123.123.123", "secret": "super_secret_rpc_code"}'::text)::http_request
);
Your RPC would look somewhat like this then:
CREATE OR REPLACE FUNCTION public.rl(ip text, secret text)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$BEGIN
IF (s <> 'secret') THEN
RETURN;
END IF;
INSERT INTO private.rate_limits(ip, now());
END;$function$
;
⚠️ Ultra Important Note for this Implementation:
Since you trigger an RPC now every time the API receives a GET
request, it would also trigger a GET
request to that RPC for the GET
request of the RPC itself! You MUST avoid that or else you have an infinite amount of requests. How? Easy, you only perform the request on certain paths:
In your middlware function where you then have PERFORM http(…)
you would only perform it if the path is e.g. a specific table like my_table
or if the path does not start with /rpc
.
E.g.:
IF (current_setting('request.path') IN ('/my_table')) THEN
-- i want to log the access
PERFORM http(...);
END IF;
Questions? I do free one-time calls btw: https://cal.com/activenode
SNACK 3/3: Manage Cronjobs inside Supabase with a UI
For those that didn’t know: Yes, Supabase has Cronjobs - via the installable pg_cron
extension (go to Database
→ Extensions
or activate here).
On my Todo list since a few months: Create a UI and for managing Cron inside of Supabase. Well, the Supabase team was faster.

Great for running cleanup tasks, AI workflows or whatever else. Just lovely.
🌑 Mini Webdev Snack:
A famous react library becomes framework-less: Framer Motion, former React library, is now Motion and can be used with any framework.
👋 Before I go
I have something upcoming so help me understand if you’re into TypeScript:
See you next time for #3.
Dave / @activenode / activeno.de