Say you run a Supabase project and need to clean up expired auth sessions every night, refresh an hourly user-activity aggregate into a materialized view, and run a daily LLM summarization pass via an Edge Function. Three credible ways cover those jobs: pg_cron inside the database, Supabase Cron (the UI on top of pg_cron plus pg_net), and external HTTP cron hitting an Edge Function. Most Supabase teams do not run their own always-on workers, so the in-process scheduler path from the Python cron jobs guide collapses into external HTTP cron here. For the Edge-Functions-only deep dive, see cron jobs for Supabase Edge Functions.
Path 1: pg_cron inside the Supabase database
pg_cron is the in-database scheduler, and as of 2026 it is enabled by default on every Supabase project (free, pro, and team):
SELECT cron.schedule(
'nightly-cleanup',
'0 2 * * *',
$$DELETE FROM auth.sessions WHERE expires_at < now()$$
);That fires every night at 02:00 UTC. List with SELECT * FROM cron.job;, unschedule via cron.unschedule('nightly-cleanup'), run history in cron.job_run_details. pg_cron is great for pure-SQL maintenance (VACUUM, materialized view refresh, partition rotation, expired-row cleanup), needs zero extra infrastructure, and supports minute cadence on managed plans.
What it cannot do alone: HTTP. To call an Edge Function or any external URL, pair it with the pg_net extension and SELECT net.http_post(...) from scheduled SQL. The call is fire-and-forget: no retry on a 5xx, no alert on a non-2xx, just a row in net._http_response. Fine for a no-op-on-failure cache warm; wrong for anything that must succeed.
Path 2: Supabase Cron (the UI)
Supabase Cron is the hosted scheduler UI in the dashboard. It wraps pg_cron and pg_net so you can schedule SQL, an Edge Function call, or an HTTP request without writing extension SQL by hand.
Create-schedule flow:
- Dashboard, Integrations, then Cron.
- Create a new cron job, name it, pick a cron expression or preset.
- Pick a type: SQL Snippet, Database Function, HTTP Request, or Supabase Edge Function.
- Save. It fires on the next tick.
Honest take: friendlier than cron.job in psql, but Supabase Cron inherits every limit underneath. Skipped runs are not retried; if a tick fires while the previous one still holds a lock, the new run is dropped. No failure alerting beyond a row in the log table, no heartbeat, and the scheduler runs only while the database is healthy. A paused project, an incident, or a connection-ceiling hit silently stops every schedule.
Path 3: External HTTP cron hitting an Edge Function
Deploy your work as a Supabase Edge Function behind a bearer-or-JWT guard, then point an external cron service at the function URL. The clock lives outside Supabase, so a paused database or an incident does not silently kill the schedule.
// supabase/functions/nightly-cleanup/index.ts
Deno.serve(async (req) => {
const auth = req.headers.get("authorization");
if (auth !== `Bearer ${Deno.env.get("CRON_SECRET")}`) {
return new Response("Unauthorized", { status: 401 });
}
// do work, e.g. summarize new content via an LLM
return new Response(null, { status: 204 });
});In Crontap: register POST https://<project-ref>.supabase.co/functions/v1/nightly-cleanup, set Authorization: Bearer <CRON_SECRET>, schedule 0 2 * * *, pick any IANA timezone like America/New_York. Pro is $3.25/mo annual flat for unlimited HTTP schedules at minute cadence on a 1-minute floor.
Why this when retries and alerts matter: Crontap retries on 5xx with backoff, alerts on a non-2xx via email or webhook (Slack, Discord, Telegram), and surfaces every run's status code, duration, and body. The same dashboard schedules non-Supabase URLs too. For the full Edge Functions setup, see cron jobs for Supabase Edge Functions.
Schedule Supabase work from outside the project. Free forever tier with one schedule. Try Crontap →
How to decide
- SQL-only on Supabase:
pg_cron. Cleanup, materialized view refresh, partition rotation. - Want a UI on top of
pg_cron: Supabase Cron. Same engine, friendlier surface, same silent-skip semantics. - Need retries, failure alerts, or non-Supabase targets too: external HTTP cron against an Edge Function.
The incident case is the strongest argument for Path 3. With pg_cron and Supabase Cron, a paused project or an outage silently pauses every schedule. Watch is Supabase down? when chasing skipped runs.
FAQ
Is pg_cron enabled by default on Supabase?
Yes. As of 2026 every Supabase project ships with pg_cron enabled on free, pro, and team plans. Run SELECT cron.schedule(...) immediately; no setup, no support ticket.
What is the difference between pg_cron and Supabase Cron?
pg_cron is the Postgres extension that does the scheduling. Supabase Cron is a dashboard UI on top of pg_cron plus pg_net. Same engine, different surface.
Can I call an Edge Function on a schedule from inside Postgres?
Yes, with pg_net. Run SELECT net.http_post(url, headers, body) from a pg_cron-scheduled SQL block. The call is fire-and-forget: the response lands in net._http_response, and a 5xx does not retry or alert. When a missed call is a real problem, schedule from outside (Path 3) instead.
What happens to my pg_cron job during a Supabase incident?
It silently does not run. pg_cron fires only while the database is healthy, so an incident, a paused free project, or a connection-ceiling hit pauses every schedule with no alert; the run-history table just has a gap. External HTTP cron sees the failure and pages you.
Related on Crontap
- Cron jobs for Supabase Edge Functions. Edge-Functions-specific deep dive: deploy, JWT auth, multi-project orgs.
- Postgres cron jobs. The broader Postgres pillar covering
pg_cronoutside Supabase too. - Cron troubleshooting hub. Debug failing schedules end to end.
- Is Supabase down?. Live status check for when schedules go quiet.
- Cron job monitoring. Heartbeats and failure alerts for any HTTP target.
