Back to guides

Guides · April 9, 2026

Supabase cron jobs: pg_cron, scheduled Edge Functions, and external triggers (2026)

Three credible ways to schedule Supabase work in 2026: pg_cron inside the database (enabled by default on every Supabase project), Supabase Cron (the hosted UI on top of pg_cron and Edge Functions), and external HTTP cron hitting an Edge Function when retries and failure alerts matter. Pick by workload, not by what loads first in the dashboard.
crontap.com / guides
Three credible ways to schedule Supabase work: pg_cron inside the database, Supabase Cron (UI over pg_cron + Edge Functions), and external HTTP cron hitting an Edge Function. When to pick which, with code.

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:

  1. Dashboard, Integrations, then Cron.
  2. Create a new cron job, name it, pick a cron expression or preset.
  3. Pick a type: SQL Snippet, Database Function, HTTP Request, or Supabase Edge Function.
  4. 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

From the blog

Read the blog

Guides, patterns and product updates.

Tutorials on scheduling API calls, webhooks and automations, plus deep dives into cron syntax, timezones and reliability.

Alternatives

Vercel Cron every minute: beating the Hobby hourly limit

Vercel Cron caps Hobby at hourly cadence and 5 jobs, and ties every change to a redeploy. Here is the external cron pattern teams use to ship per-minute schedules, per-IANA timezones, and one dashboard across projects without paying $20/mo per user for Pro.

Alternatives

Cloud Run cron without Cloud Scheduler

Cloud Scheduler costs $0.10 per job per month after the first 3 and asks for OIDC plus IAM bindings on every target. Here is the IAM-free pattern Cloud Run teams use to fire their .run.app URLs on a clock with one bearer token and one dashboard across every GCP project.