Back to guides

Guides · April 9, 2026

Supabase cron jobs: Supabase Cron, pg_cron, and Edge Functions

Enable Integrations > Cron to turn it on. Supabase Cron is the Dashboard interface over pg_cron. External HTTP cron fits when you want retries, attempt history, and configured failure notifications outside the project.
crontap.com / guides
Database clock connected by an arrow to an Edge Function lightning-bolt icon.

To create a Supabase cron job, enable Integrations > Cron. Then call cron.schedule(...) in SQL or use the Dashboard to target SQL, a database function, an HTTP request, or an Edge Function. Both options run inside the project on pg_cron. Use external HTTP cron when the Edge Function is already the worker and you need bounded retries, attempt history, and configured failure notifications from outside Supabase. For the Edge-Functions-only setup, see cron jobs for Supabase Edge Functions.

Path 1: pg_cron inside the Supabase database

pg_cron is the in-database scheduler. It is not enabled by default. Open Integrations, then Cron in the Dashboard and enable the module before scheduling a job:

select cron.schedule(
  'nightly-cleanup',
  '0 2 * * *',
  $$delete from public.app_sessions where expires_at < now()$$
);

That runs every night at 02:00 UTC. Supabase documents recurring schedules in GMT, which has the same offset as UTC. List jobs with select * from cron.job;, remove one with select cron.unschedule('nightly-cleanup');, and inspect runs in cron.job_run_details. The Supabase Cron quickstart also supports intervals from 1 to 59 seconds, such as '30 seconds', on Postgres 15.1.1.61 or later.

pg_cron runs different jobs in parallel, but only one instance of a specific job at a time. If the same job becomes due while its previous run is still working, the new run queues and starts after the current one finishes.

Supabase recommends no more than eight jobs running concurrently and a maximum runtime of ten minutes per job. Run-history rows are not removed automatically, even when you unschedule a job, so clean up old cron.job_run_details rows before they become material database usage.

For HTTP, pair pg_cron with pg_net. net.http_post(...) is asynchronous and returns a request ID; the HTTP request starts after the transaction commits. cron.job_run_details records the scheduler run that submitted it. The eventual HTTP status or error is separate in net._http_response, where responses are retained for six hours by default. This supported path does not add bounded retries or final-failure handling on its own.

Path 2: Supabase Cron (the UI)

Supabase Cron is the Dashboard interface over pg_cron. It creates the same database jobs and uses pg_net for HTTP targets, so you can schedule SQL, a database function, an HTTP request, or an Edge Function without writing the extension calls by hand.

Create-schedule flow:

  1. In the Dashboard, open Integrations, then Cron, and enable it.
  2. Open Jobs, choose Create job, name it, and set a cron expression, natural-language schedule, or supported seconds interval.
  3. Pick SQL Snippet, Database Function, HTTP Request, or Supabase Edge Function.
  4. Save. It fires on the next tick.

Supabase Cron is friendlier than calling pg_cron functions in SQL, but it keeps the same execution semantics. It does not retry a failed run, and overlapping instances of the same job queue rather than disappear. Every pg_cron invocation and its scheduler status is recorded in cron.job_run_details; the Dashboard exposes that data under History. For HTTP and Edge Function targets, inspect net._http_response separately for the eventual status code or error from the asynchronous request. If the scheduler run itself fails, use Supabase's pg_cron debugging guide to inspect the return message, scheduler process, and Postgres logs.

The clock also lives inside the project. Free projects can pause after low activity, and their database jobs cannot run while paused. Paid projects are not automatically paused for inactivity.

Path 3: External HTTP cron hitting an Edge Function

Deploy the work as a Supabase Edge Function behind a bearer or JWT guard, then point an external cron service at its URL. Supabase can also schedule Edge Functions internally with pg_cron and pg_net; move the clock outside when you want retries, attempt history, and configured failure notifications to continue outside the project.

For the custom bearer secret below, disable the platform JWT check for this function. The handler still rejects requests that do not carry your secret:

# supabase/config.toml
[functions.nightly-cleanup]
verify_jwt = false
// supabase/functions/nightly-cleanup/index.ts
Deno.serve(async (req) => {
  const cronSecret = Deno.env.get("CRON_SECRET");
  const auth = req.headers.get("authorization");
  if (!cronSecret || auth !== `Bearer ${cronSecret}`) {
    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>, choose 0 2 * * *, and pick an IANA timezone such as America/New_York.

New schedules retry 5xx responses, timeouts, and network failures by default. Every plan can turn retries on or off. Pro, Ultra, and legacy Pro can tune one to five retries, the starting delay, and whether to retry 429 or other 4xx responses.

History records each attempt with its status, status code, duration, size, and error summary. After the configured attempts are exhausted, a failed final attempt enters the schedule's configured notification handling. Email is available on every plan, while Slack, Discord, and Telegram notifications are available on paid plans. Configured notification thresholds and routing determine whether and where a notification is sent. Machine webhook Integrations are configured separately from those human-facing Notifications.

An external clock keeps attempting on schedule when Supabase is paused or unavailable, and its history makes those failures visible. It can record the failed call and process configured notifications, but the Edge Function and database still cannot run until Supabase recovers.

If the Edge Function is already the worker, paste its URL, set the timezone, and run a test. Schedule your first job.

How to decide

  • SQL-only work in Supabase: use pg_cron directly for cleanup, aggregate refreshes, and partition rotation.
  • The same engine with a visual setup and history view: use Supabase Cron.
  • Bounded HTTP retries, attempt history, and configured failure notifications: use external HTTP cron against an Edge Function.

When a schedule goes quiet, inspect its run history first, then check is Supabase down?. An external scheduler gives you evidence from outside the project, but it still depends on the Supabase endpoint becoming available.

Related on Crontap

FAQ

Supabase cron job questions

Is pg_cron enabled by default on Supabase?
No. Open Integrations > Cron in the Supabase Dashboard and enable it. This enables the pg_cron extension for the project.
What is the difference between Supabase Cron and pg_cron?
pg_cron is the Postgres extension that schedules and executes jobs. Supabase Cron is the Dashboard interface over pg_cron, with targets for SQL, database functions, HTTP requests, and Edge Functions.
Can Supabase Cron run every second?
Yes. On Postgres 15.1.1.61 or later, pg_cron accepts intervals from 1 to 59 seconds, such as 30 seconds. A five-field cron expression still has one-minute precision.
What happens when the same pg_cron job overlaps?
pg_cron runs only one instance of a specific job at a time. If the next run is due before the current one finishes, the next instance queues and starts when the current one completes.
How do I inspect Supabase Cron failures?
Open the job's History view or query cron.job_run_details for the pg_cron run status and return message. For an asynchronous pg_net request, inspect net._http_response for the eventual HTTP status or error; responses are retained for six hours by default.
How do I invoke a Supabase Edge Function on a schedule?
Choose Supabase Edge Function as the job type in Supabase Cron, or schedule a net.http_post call with pg_cron and pg_net. Store sensitive tokens in Supabase Vault rather than in the job text.
What happens to cron jobs when a Free project is paused?
The database scheduler and Edge Function workload are unavailable until the project resumes. An external scheduler can keep attempting the URL and record failures, but it cannot make the paused Supabase workload succeed.

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.

Product Updates

Introducing AI Integrations

Transform a schedule's HTTP response with a plain-English prompt, return text or JSON, and forward it to Slack, Make, n8n, or your own endpoint. Test on any tier; saving is a Pro feature.

Alternatives

Vercel cron jobs: the Hobby once-per-day limit and how to beat it

Vercel Cron caps Hobby at one run per day, only guarantees timing within the hour, is UTC only, and ties every schedule change to a redeploy. Here is the external cron pattern teams use to ship per-minute, timezone-aware schedules and one dashboard across projects without paying $20/mo per user for Pro.