Supabase’s pg_cron is a PostgreSQL extension that lets you schedule SQL commands directly within your database.

Here’s a quick look at pg_cron in action. Imagine you want to clean up old user sessions every night at 3 AM. You’d use a command like this:

SELECT cron.schedule(
    'cleanup-sessions',
    '0 3 * * *',
    $$ DELETE FROM auth.sessions WHERE last_seen < NOW() - INTERVAL '7 days' $$
);

This SELECT statement, when executed against your Supabase project’s database, registers a cron job named cleanup-sessions. The second argument, '0 3 * * *', is a standard cron schedule string meaning "at 3:00 AM every day." The third argument, the DELETE statement, is the actual work the job will perform: removing session records older than a week.

pg_cron is a powerful tool because it moves job scheduling into the database. Traditionally, you might have a separate server or a cloud function running a cron daemon, polling a queue, or triggering scheduled tasks. With pg_cron, the database itself is the scheduler. This means:

  • Reduced Latency: Jobs can react to database state changes more directly.
  • Simplified Architecture: Fewer external services to manage and monitor.
  • Database-Centric Logic: SQL-based jobs naturally fit within a database-centric application.

The core of pg_cron is its cron schema, which provides functions to manage jobs. The primary functions you’ll interact with are:

  • cron.schedule(job_name, cron_schedule, command): This is for creating or updating a job. If a job with job_name already exists, it will be updated.
  • cron.unschedule(job_name): This removes a scheduled job.
  • cron.schedule_in_timezone(job_name, cron_schedule, command, timezone): Similar to schedule, but allows you to specify a timezone for the schedule.
  • cron.job(job_id): Retrieves information about a specific job.
  • cron.jobs(): Lists all scheduled jobs.

When you run cron.schedule, pg_cron registers the job and ensures it’s executed according to the schedule. It leverages PostgreSQL’s background worker processes to achieve this. A dedicated pg_cron worker wakes up periodically, checks the schedule, and executes any jobs that are due.

The cron_schedule uses the familiar cron syntax:

  • * * * * * (minute, hour, day of month, month, day of week)
  • 0 3 * * * means "at 0 minutes past the 3rd hour, on any day of the month, any month, any day of the week" – i.e., 3:00 AM daily.
  • */15 * * * * means "every 15 minutes."
  • 0 0 1 * * means "at midnight on the 1st of every month."

Supabase projects come with pg_cron pre-installed and enabled. You can access it by connecting to your database using a tool like psql or directly through the SQL Editor in the Supabase dashboard.

The most surprising truth about pg_cron is that it doesn’t actually store the job definitions in a persistent table that you can directly SELECT * FROM. Instead, the job definitions are managed internally by the pg_cron extension itself, and the cron.schedule and cron.unschedule functions are the only programmatic interface for interacting with them. This means if your database restarts, pg_cron automatically reloads its known jobs from its internal state, ensuring continuity without needing an external configuration file or a separate daemon to manage.

The next logical step is understanding how to handle potential job failures and monitor their execution.

Want structured learning?

Take the full Supabase course →