Cron Triggers
Schedule your workers to run on a recurring basis using cron expressions.
Cron Triggers
Ploy Cron Triggers let you schedule your workers to run automatically on a recurring basis. Define cron expressions in your ploy.yaml and implement a scheduled handler to process them.
Configuration
Add cron triggers in your ploy.yaml:
kind: worker
build: pnpm build
out: dist
cron:
EVERY_10_SECONDS: "*/10 * * * * *"
EVERY_MINUTE: "* * * * *"
HOURLY_CLEANUP: "0 * * * *"
DAILY_REPORT: "0 9 * * *"Each key is a trigger name (uppercase with underscores) and the value is a cron expression. The standard form has 5 fields:
# ┌───────────── minute (0-59)
# │ ┌───────────── hour (0-23)
# │ │ ┌───────────── day of month (1-31)
# │ │ │ ┌───────────── month (1-12)
# │ │ │ │ ┌───────────── day of week (0-6, 0=Sunday)
# │ │ │ │ │
# * * * * *For sub-minute schedules, use the extended 6-field form with a leading seconds field:
# ┌───────────── second (0-59)
# │ ┌───────────── minute (0-59)
# │ │ ┌───────────── hour (0-23)
# │ │ │ ┌───────────── day of month (1-31)
# │ │ │ │ ┌───────────── month (1-12)
# │ │ │ │ │ ┌───────────── day of week (0-6, 0=Sunday)
# │ │ │ │ │ │
# * * * * * *A 5-field expression fires at second 0 of every matching minute, so existing schedules behave exactly as before.
Basic Example
export default {
async fetch(request, env) {
return new Response("Hello from cron worker!");
},
async scheduled(event) {
console.log(`Cron triggered: ${event.cron}`);
console.log(
`Scheduled time: ${new Date(event.scheduledTime).toISOString()}`,
);
// Your scheduled logic here
await performCleanup();
},
} satisfies Ploy;ScheduledEvent
The scheduled handler receives a ScheduledEvent with the following properties:
interface ScheduledEvent {
/** The cron expression that triggered this execution */
cron: string;
/** Timestamp when the event was scheduled (ms since epoch) */
scheduledTime: number;
/** Call to signal this invocation should not be retried */
noRetry: () => void;
}Cron Expression Syntax
Standard 5-field cron expressions and extended 6-field expressions (with a leading seconds field) are supported:
| Expression | Description |
|---|---|
*/10 * * * * * | Every 10 seconds |
*/30 * * * * * | Every 30 seconds |
0 * * * * * | Every minute at second 0 |
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour |
0 0 * * * | Every day at midnight |
0 9 * * 1-5 | Weekdays at 9 AM |
0 0 1 * * | First day of every month |
30 2 * * 0 | Sundays at 2:30 AM |
Supported Syntax
- Wildcards:
*matches all values - Specific values:
5matches exactly 5 - Ranges:
1-5matches 1 through 5 - Steps:
*/15matches every 15th value - Lists:
1,3,5matches 1, 3, and 5 - Combined:
1-10/2matches 1, 3, 5, 7, 9 - Seconds: prefix with a sixth leading field for sub-minute schedules (e.g.
*/10 * * * * *)
Multiple Triggers
You can define multiple cron triggers, each identified by a unique name:
cron:
CLEANUP: "0 * * * *"
DAILY_DIGEST: "0 9 * * *"
WEEKLY_REPORT: "0 10 * * 1"Use the event.cron property to distinguish which trigger fired:
async scheduled(event) {
switch (event.cron) {
case "0 * * * *":
await runCleanup();
break;
case "0 9 * * *":
await sendDailyDigest();
break;
case "0 10 * * 1":
await generateWeeklyReport();
break;
}
}Using with Next.js
Next.js apps deploy the framework server as the worker module, so the scheduled handler comes from your Ploy handlers file instead. Export it from ploy.ts (project root, src/, or app/):
import type { Ploy } from "@meetploy/types";
export default {
async scheduled(event, env, ctx) {
console.log(`Cron triggered: ${event.cron}`);
// Your scheduled logic here
},
} satisfies Ploy;If your project pairs the Next.js frontend with a worker entry at worker/index.ts (the same convention the Vite integration uses), its default export is picked up automatically when no ploy.ts exists — a scheduled handler exported there receives cron triggers in production too.
Using with the Start SDK
If you're using @meetploy/start, register a scheduled handler with .scheduled():
import { ploy } from "@meetploy/start";
const app = ploy<PloyEnv>()
.get(
"/",
{
response: z.object({ status: z.string() }),
},
() => ({ status: "ok" }),
)
.scheduled(async (event, env, ctx) => {
console.log(`Cron: ${event.cron}`);
// Your scheduled logic
})
.build();
export default app;Combining with Other Bindings
Cron triggers work alongside other Ploy bindings. Use queues, databases, and caches from your scheduled handler:
kind: worker
build: pnpm build
out: dist
db:
DB: default
queue:
TASKS: tasks
cron:
HOURLY_SYNC: "0 * * * *"async scheduled(event, env) {
// Query your database
const staleRecords = await env.DB.prepare(
"SELECT id FROM records WHERE updated_at < ?"
).bind(Date.now() - 86400000).all();
// Queue work for processing
for (const record of staleRecords.results) {
await env.TASKS.send({ action: "refresh", recordId: record.id });
}
}The scheduled handler has access to the same env bindings as your fetch
handler.
Local Development
When running the emulator with ploy dev, cron triggers are automatically scheduled based on your ploy.yaml configuration. The emulator checks cron expressions every second, so sub-minute (6-field) schedules fire on time, and invokes your scheduled handler when a match occurs.
Cron execution history is visible in the dev dashboard.
Next Steps
How is this guide?
Last updated on