This site is not affiliated with or endorsed by Cloudflare, Inc. It showcases deployable experiments across most Cloudflare products.
Stateful & Async

Cron Heartbeat

Cron Triggers reference: scheduled Worker runs with heartbeat metadata stored in KV - pattern for periodic edge jobs

A minimal reference for Cron Triggers: a scheduled() handler runs every 5 minutes and writes run metadata to Workers KV. An HTTP route reads that metadata back.

API Reference

GET /status

Returns metadata from the most recent scheduled run.

Example Request

curl "https://your-worker.workers.dev/status"

Response Fields

lastRun string | null

ISO timestamp of the last cron execution.

lastCron string | null

Cron expression that fired (e.g. */5 * * * *).

runCount number

Total scheduled runs since deploy.

Example Response

{
  "lastRun": "2025-06-20T12:00:00.000Z",
  "lastCron": "*/5 * * * *",
  "runCount": 12
}

Before the first cron fires, lastRun and lastCron are null and runCount is 0.

Use Cases

  • Learn how to implement a scheduled() handler in Workers
  • Periodic health checks, cache warming, or cleanup jobs
  • Background maintenance without external cron services
  • Storing cron run history in KV for observability

Limitations

  • Cron schedule is fixed in wrangler.json; change and redeploy to adjust timing
  • Stores last-run metadata in KV only; not a full job scheduler
  • Demonstration worker; no alerting or retry logic on failed runs

Use in your project

Copy these files into an existing Worker. Prefer Deployment to try the full experiment first. Source: apps/experiments/cron-heartbeat.

DependenciesNoneBindingsKVPlatformWorkers runtime
import type { Env } from "../types/env";import type { HeartbeatStatus } from "../types/status";import { STATUS_KEYS } from "../constants/keys";export async function getHeartbeatStatus(env: Env): Promise<HeartbeatStatus> {  const [lastRun, lastCron, runCountRaw] = await Promise.all([    env.STATUS.get(STATUS_KEYS.lastRun),    env.STATUS.get(STATUS_KEYS.lastCron),    env.STATUS.get(STATUS_KEYS.runCount),  ]);  return {    lastRun,    lastCron,    runCount: Number.parseInt(runCountRaw ?? "0", 10),  };}export async function recordHeartbeat(env: Env, cron: string): Promise<HeartbeatStatus> {  const current = await getHeartbeatStatus(env);  const nextCount = current.runCount + 1;  const lastRun = new Date().toISOString();  await Promise.all([    env.STATUS.put(STATUS_KEYS.lastRun, lastRun),    env.STATUS.put(STATUS_KEYS.lastCron, cron),    env.STATUS.put(STATUS_KEYS.runCount, String(nextCount)),  ]);  return {    lastRun,    lastCron: cron,    runCount: nextCount,  };}

Deployment

Click the deploy button

Deploy to Cloudflare Workers

Create KV namespace

Update wrangler.json with your KV namespace ID for the STATUS binding.

Check status

curl "https://your-worker.workers.dev/status"

Local Development

cd apps/experiments/cron-heartbeat
npm install
npm run dev

Trigger a cron manually in local dev:

curl "http://localhost:8787/cdn-cgi/handler/scheduled"

Configuration

wrangler.json declares:

  • Cron trigger */5 * * * * (every 5 minutes)
  • KV binding STATUS for run metadata

Cloudflare Features Used

On this page