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 starts at $2.99/mo and scales as you grow, with a 1-minute cadence 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 2 combined schedules + uptime monitors. 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.
