Scheduled jobs

Run background work on a schedule — digests, cleanups, syncs. You write named job handlers in api/cron.js, and schedule them with the cron MCP tools. Each job runs with the same env bindings as your routes.

Define handlers in api/cron.js

api/cron.js is a special file (not an HTTP route). Default-export a map of job name → handler. Handlers receive (env, ctx):

// api/cron.js
export default {
  async daily_digest(env, ctx) {
    const { results } = await env.DB
      .prepare("SELECT email FROM subscribers WHERE digest = 1")
      .all();
    for (const row of results) {
      await env.EMAIL.send({
        to: row.email,
        subject: "Your daily digest",
        html: "<p>Here's what's new…</p>",
      });
    }
  },

  async cleanup_temp(env, ctx) {
    // …
  },
};

An array of { name, handler } objects works too — use whichever reads better.

Schedule jobs with the MCP tools

  • set_cron_job — create or update a schedule, pointing at a job name from api/cron.js.
  • list_cron_jobs — see an app's scheduled jobs.
  • run_cron_job — trigger a job immediately (handy for testing).
  • delete_cron_job — stop a schedule. (The handler stays in api/cron.js until you remove it.)

In practice you just ask your assistant: “run daily_digest every morning at 7am” — it writes the handler and calls set_cron_job.

Limits

The number of jobs per app, and whether you can schedule more often than once a day, depend on your plan — see Limits & quotas.

Jobs run against your production bindings. Use run_cron_job to dry-run a job on demand before relying on the schedule.