# Cron Triggers Patterns ## API Data Sync ```typescript export default { async scheduled(controller, env, ctx) { const response = await fetch("https://api.example.com/data", {headers: { "Authorization": `Bearer ${env.API_KEY}` }}); if (!response.ok) throw new Error(`API error: ${response.status}`); ctx.waitUntil(env.MY_KV.put("cached_data", JSON.stringify(await response.json()), {expirationTtl: 3600})); }, }; ``` ## Database Cleanup ```typescript export default { async scheduled(controller, env, ctx) { const result = await env.DB.prepare(`DELETE FROM sessions WHERE expires_at < datetime('now')`).run(); console.log(`Deleted ${result.meta.changes} expired sessions`); ctx.waitUntil(env.DB.prepare("VACUUM").run()); }, }; ``` ## Report Generation ```typescript export default { async scheduled(controller, env, ctx) { const startOfWeek = new Date(); startOfWeek.setDate(startOfWeek.getDate() - 7); const { results } = await env.DB.prepare(`SELECT date, revenue, orders FROM daily_stats WHERE date >= ? ORDER BY date`).bind(startOfWeek.toISOString()).all(); const report = {period: "weekly", totalRevenue: results.reduce((sum, d) => sum + d.revenue, 0), totalOrders: results.reduce((sum, d) => sum + d.orders, 0), dailyBreakdown: results}; const reportKey = `reports/weekly-${Date.now()}.json`; await env.REPORTS_BUCKET.put(reportKey, JSON.stringify(report)); ctx.waitUntil(env.SEND_EMAIL.fetch("https://example.com/send", {method: "POST", body: JSON.stringify({to: "team@example.com", subject: "Weekly Report", reportUrl: `https://reports.example.com/${reportKey}`})})); }, }; ``` ## Health Checks ```typescript export default { async scheduled(controller, env, ctx) { const services = [{name: "API", url: "https://api.example.com/health"}, {name: "CDN", url: "https://cdn.example.com/health"}]; const checks = await Promise.all(services.map(async (service) => { const start = Date.now(); try { const response = await fetch(service.url, { signal: AbortSignal.timeout(5000) }); return {name: service.name, status: response.ok ? "up" : "down", responseTime: Date.now() - start}; } catch (error) { return {name: service.name, status: "down", responseTime: Date.now() - start, error: error.message}; } })); ctx.waitUntil(env.STATUS_KV.put("health_status", JSON.stringify(checks))); const failures = checks.filter(c => c.status === "down"); if (failures.length > 0) ctx.waitUntil(fetch(env.ALERT_WEBHOOK, {method: "POST", body: JSON.stringify({text: `${failures.length} service(s) down: ${failures.map(f => f.name).join(", ")}`})})); }, }; ``` ## Batch Processing (Rate-Limited) ```typescript export default { async scheduled(controller, env, ctx) { const queueData = await env.QUEUE_KV.get("pending_items", "json"); if (!queueData || queueData.length === 0) return; const batch = queueData.slice(0, 100); const results = await Promise.allSettled(batch.map(item => fetch("https://api.example.com/process", {method: "POST", headers: {"Authorization": `Bearer ${env.API_KEY}`, "Content-Type": "application/json"}, body: JSON.stringify(item)}))); console.log(`Processed ${results.filter(r => r.status === "fulfilled").length}/${batch.length} items`); ctx.waitUntil(env.QUEUE_KV.put("pending_items", JSON.stringify(queueData.slice(100)))); }, }; ``` ## Queue Integration ```typescript export default { async scheduled(controller, env, ctx) { const batch = await env.MY_QUEUE.receive({ batchSize: 100 }); const results = await Promise.allSettled(batch.messages.map(async (msg) => { await processMessage(msg.body, env); await msg.ack(); })); console.log(`Processed ${results.filter(r => r.status === "fulfilled").length}/${batch.messages.length}`); }, }; ``` ## Monitoring & Observability ```typescript export default { async scheduled(controller, env, ctx) { const startTime = Date.now(); const meta = { cron: controller.cron, scheduledTime: controller.scheduledTime }; console.log("[START]", meta); try { const result = await performTask(env); console.log("[SUCCESS]", { ...meta, duration: Date.now() - startTime, count: result.count }); ctx.waitUntil(env.METRICS.put(`cron:${controller.scheduledTime}`, JSON.stringify({ ...meta, status: "success" }), { expirationTtl: 2592000 })); } catch (error) { console.error("[ERROR]", { ...meta, duration: Date.now() - startTime, error: error.message }); ctx.waitUntil(fetch(env.ALERT_WEBHOOK, { method: "POST", body: JSON.stringify({ text: `Cron failed: ${controller.cron}`, error: error.message }) })); throw error; } }, }; ``` **View logs:** `npx wrangler tail` or Dashboard → Workers & Pages → Worker → Logs ## Durable Objects Coordination ```typescript export default { async scheduled(controller, env, ctx) { const stub = env.COORDINATOR.get(env.COORDINATOR.idFromName("cron-lock")); const acquired = await stub.tryAcquireLock(controller.scheduledTime); if (!acquired) { controller.noRetry(); return; } try { await performTask(env); } finally { await stub.releaseLock(); } }, }; ``` ## Python Handler ```python from workers import WorkerEntrypoint class Default(WorkerEntrypoint): async def scheduled(self, controller, env, ctx): data = await env.MY_KV.get("key") ctx.waitUntil(env.DB.execute("DELETE FROM logs WHERE created_at < datetime('now', '-7 days')")) ``` ## Testing Patterns **Local testing with /__scheduled:** ```bash # Start dev server npx wrangler dev # Test specific cron curl "http://localhost:8787/__scheduled?cron=*/5+*+*+*+*" # Test with specific time curl "http://localhost:8787/__scheduled?cron=0+2+*+*+*&scheduledTime=1704067200000" ``` **Unit tests:** ```typescript // test/scheduled.test.ts import { describe, it, expect, vi } from "vitest"; import { env } from "cloudflare:test"; import worker from "../src/index"; describe("Scheduled Handler", () => { it("executes cron", async () => { const controller = { scheduledTime: Date.now(), cron: "*/5 * * * *", type: "scheduled" as const, noRetry: vi.fn() }; const ctx = { waitUntil: vi.fn(), passThroughOnException: vi.fn() }; await worker.scheduled(controller, env, ctx); expect(await env.MY_KV.get("last_run")).toBeDefined(); }); it("calls noRetry on duplicate", async () => { const controller = { scheduledTime: 1704067200000, cron: "0 2 * * *", type: "scheduled" as const, noRetry: vi.fn() }; await env.EXECUTIONS.put("0 2 * * *-1704067200000", "1"); await worker.scheduled(controller, env, { waitUntil: vi.fn(), passThroughOnException: vi.fn() }); expect(controller.noRetry).toHaveBeenCalled(); }); }); ``` ## See Also - [README.md](./README.md) - Overview - [api.md](./api.md) - Handler implementation - [gotchas.md](./gotchas.md) - Troubleshooting