Skip to content

Cron jobs

Use cron jobs for recurring application-wide work with a fixed calendar schedule: periodic cleanup, reports, synchronization, rollups, and maintenance. Use one-shot scheduling instead when a job belongs to a specific record, waits for an elapsed delay, or runs at a dynamically calculated instant.

PocketBase provides each calendar tick, and PBVex enqueues that occurrence into its durable job worker. Cron invocations therefore use the same immutable deployment snapshots, concurrency controls, status history, retries, and idempotency rules as one-shot scheduled jobs.

Define recurring jobs

Create pbvex/crons.ts and export exactly one cronJobs() definition as the module default:

ts
// pbvex/crons.ts
import { cronJobs } from 'pbvex/server';
import { internal } from './_generated/api';

const crons = cronJobs();

crons.cron(
  'hourly-expired-session-cleanup',
  '@hourly',
  internal.maintenance.removeExpiredSessions,
  { batchSize: 100 },
);

crons.cron(
  'weekday-summary',
  '0 9 * * 1-5',
  internal.reports.sendDailySummary,
);

export default crons;

Define the target function, run pbvex codegen, and then use its generated reference in crons.ts. The target must be a public or internal mutation/action; queries and HTTP actions cannot be cron targets. Prefer internal functions for operational work that clients should not invoke.

The generated reference preserves argument types and autocomplete. Missing arguments, incorrect field types, and unsupported target kinds fail TypeScript checking. PBVex validates the same contract again when building and activating the deployment.

Cron names must match [a-z][a-z0-9_-]{0,63}, must be unique, and should describe the job rather than repeat its schedule. A deployment may declare up to 64 cron jobs.

Cron expression format

PocketBase uses five space-separated fields evaluated in UTC with one-minute precision:

text
┌───────────── minute (0-59)
│ ┌─────────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌─────── month (1-12)
│ │ │ │ ┌───── day of week (0-6, Sunday is 0)
│ │ │ │ │
* * * * *

Fields support wildcards (*), lists (1,3,5), ranges (1-5), steps (*/10), and stepped ranges (10-30/5). Month and weekday names such as JAN or MON are not supported. When both day-of-month and day-of-week are restricted, PocketBase requires both fields to match.

Supported macros are @yearly/@annually, @monthly, @weekly, @daily/@midnight, and @hourly.

Common schedules

ScheduleExpressionMeaning in UTC
Every minute* * * * *At every minute boundary
Every 5 minutes*/5 * * * *Minutes 0, 5, 10, …, 55
Every 15 minutes*/15 * * * *Minutes 0, 15, 30, and 45
Hourly@hourlyMinute 0 of every hour
Every 6 hours0 */6 * * *00:00, 06:00, 12:00, and 18:00
Daily at midnight@daily00:00 every day
Daily at 09:000 9 * * *09:00 every day
Weekdays at 09:000 9 * * 1-5Monday through Friday at 09:00
Weekly@weeklySunday at 00:00
Monthly@monthlyFirst day of the month at 00:00
Yearly@yearlyJanuary 1 at 00:00

PBVex intentionally leaves the PocketBase app cron timezone at UTC. If a business schedule must follow a user’s changing local time zone or daylight-saving rules, calculate the next exact instant and use runAt, or use an external timezone-aware scheduler.

Choosing cron or one-shot scheduling

RequirementUseWhy
Clean expired sessions every hourCronFixed application-wide cadence
Send a weekday report at 09:00 UTCCronFixed calendar schedule
Remind each user at their chosen instantrunAtEach record has a different timestamp
Retry a workflow after five minutesrunAfterThe delay is relative to the current attempt
Run again based on the previous resultSelf-schedule with runAfter/runAtThe next occurrence is dynamic
Follow 09:00 in each user’s local timezoneCalculate an instant and use runAtPocketBase cron is UTC and application-wide

Execution behavior

Each PocketBase tick immediately enqueues an ordinary durable PBVex job. Keep these consequences in mind:

  • Ticks missed while the server is stopped are not backfilled automatically.
  • A new tick can be enqueued while the previous occurrence is still running.
  • The cron time is the earliest enqueue time, not a strict execution deadline.
  • Delivery is at least once, so every occurrence must be idempotent and safe to overlap or retry.
  • Cron jobs run without a user authentication/session context.
  • An occurrence already enqueued remains pinned to the deployment that produced it.

Activating a deployment replaces the previous PBVex cron registrations. Rolling back restores the selected deployment’s definitions. Existing non-PBVex PocketBase cron jobs are left untouched.

Inspect and trigger cron jobs

PBVex registrations appear in PocketBase Dashboard → Settings → Crons with IDs such as pbvex:hourly-expired-session-cleanup. A superuser can inspect them through GET /api/crons and manually trigger one with POST /api/crons/{id}. A manual trigger follows the same path and enqueues a durable PBVex job.

Monitor the resulting execution through the PBVex scheduler administration endpoints described in Scheduler operations.

When self-scheduling is useful

Use pbvex/crons.ts for a fixed application-wide calendar. Self-schedule when each record has its own cadence, the next occurrence depends on the previous result, or the schedule is stored as application data:

ts
import { HOUR_MS } from 'pbvex/server';

export const sweep = internalMutation({
  args: {},
  handler: async (ctx) => {
    // Process a bounded batch here.
    await ctx.scheduler.runAfter(
      HOUR_MS,
      internal.maintenance.sweep,
      {},
    );
  },
});

This measures from the end of one run, so execution time causes drift. It also stops recurring if the handler reaches a terminal failure before scheduling the next job.

For a wall-clock self-scheduling loop, calculate the next intended instant and use runAt instead of adding DAY_MS. That avoids accumulating execution-time drift, but the application must still handle time zones and daylight-saving rules. See Scheduling one-shot work for durability, retry, cancellation, and idempotency details shared by all PBVex jobs.

Operational checklist

  • Prefer internal mutation/action targets.
  • Keep every occurrence small and bounded; chain batches for large datasets.
  • Re-read current records instead of trusting stale arguments.
  • Make database writes and external calls idempotent.
  • Prevent or safely tolerate overlapping occurrences.
  • Treat cron times as earliest enqueue times, not execution deadlines.
  • Inspect registrations in PocketBase and executions through PBVex scheduler operations.
  • Remember the single-node boundary documented in Limits and boundaries.

Generated API reference. Source of truth is the codebase.