Crons
Schedule your site's worker to run on a recurring UTC schedule — daily digests, hourly polling, retention emails
Cron triggers fire on a schedule (UTC) and call your worker's
scheduled() handler. Use them for daily reminders, weekly digests,
hourly polling, or any recurring background job — without standing up a
separate cron service.
Crons run on the same per-site worker as Functions,
so they share the same bindings (env.SITE_DB, secrets, variables).
Declare schedules in .shiply/crons.json
{
"crons": [
{ "path": "/api/cron/daily-reminder", "schedule": "0 9 * * *" },
{ "path": "/api/cron/hourly-sync", "schedule": "0 * * * *" }
]
}On shiply publish, shiply registers these as Cloudflare Worker cron
triggers. The path is a label that gets passed to your handler — it's
not a route the world can hit; CF dispatches scheduled events directly
to your worker.
Schedule syntax
Standard 5-field crontab, UTC only:
| Expression | Meaning |
|---|---|
0 9 * * * | Every day at 09:00 UTC |
*/5 * * * * | Every 5 minutes |
0 * * * * | Top of every hour |
0 0 1 * * | First of each month at 00:00 UTC |
0 0 * * 0 | Every Sunday at 00:00 UTC |
Full reference: Cloudflare cron triggers.
Handler
Add a scheduled method to your worker default export. Dispatch on
event.cron to handle multiple schedules in one worker:
// worker.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// ... your normal request handling
return env.ASSETS.fetch(request)
},
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
// event.cron === "0 9 * * *" — match on this to dispatch
if (event.cron === '0 9 * * *') {
await sendDailyReminders(env)
}
if (event.cron === '0 * * * *') {
await syncFromUpstream(env)
}
},
}scheduled runs in the same V8 isolate as your fetch handler with the
same bindings — including any secrets and the attached
D1 database (env.SITE_DB).
Manage from the CLI
shiply cron ls <slug> # list configured crons
shiply cron set <slug> /api/cron/daily "0 9 * * *" # add or replace one
shiply cron rm <slug> /api/cron/daily # remove oneManage from MCP
| Tool | Purpose |
|---|---|
list_crons | Read configured crons for a site |
set_cron | Add or replace a cron trigger |
remove_cron | Drop a cron trigger by path |
Limits
- 20 cron triggers per site (shiply cap; Cloudflare's per-Worker limit on the underlying paid plan is higher).
- 30 s CPU per scheduled run — same isolate budget as fetch handlers.
- No sub-minute schedules.
* * * * *(every minute) is the smallest interval; Cloudflare doesn't accept second-precision crontabs.