Pass a scheduled function to run work on a cron trigger instead of in response to a request. Apiker exports it as the Worker's scheduled handler.

1export const handleScheduled = async ({ state }) => {
2 const settings = await getDueSettings(state); // however your app tracks work to do
3 const ids = Object.keys(settings);
4 if (!ids.length) return;
5
6 // Resume where the last run left off, in fixed-size batches
7 const { currentIndex = 0 } = (await state("Common", "scheduled-job").get("processing-state")) || {};
8 const batchSize = 20;
9 const endIndex = Math.min(currentIndex + batchSize, ids.length);
10
11 for (let i = currentIndex; i < endIndex; i++) {
12 const id = ids[i];
13 const info = await state("Common", id).get("info");
14
15 if (!info?.entitlements?.includes("your-feature")) continue; // gate on entitlement
16
17 try {
18 await runDueWork(id, settings[id]);
19 } catch (err) {
20 console.error(`Scheduled job failed for ${id}`, err); // one bad entry can't stall the batch
21 }
22 }
23
24 await state("Common", "scheduled-job").put("processing-state", {
25 currentIndex: endIndex >= ids.length ? 0 : endIndex
26 });
27};

The function receives the same state factory your handlers use, plus the Cloudflare event, env and execution context. Batching a fixed number of entries per run, tracking a resume cursor in Common, gating on entitlements, and catching errors per entry is the shape a real scheduled job needs once it processes more than a handful of records.

It's wired in like any other apiker.init option, alongside your usual routes and objects:

apiker.init({ routes, objects, exports, firewall: { limitRequestsPerMinute: 140 }, adminPanel: true, scheduled: handleScheduled });

Declaring the schedule

Cloudflare decides when to call it, so the cron itself lives in your configuration:

app.toml
TOML
[triggers] crons = ["0 * * * *"]
There is no request behind a scheduled run, so helpers that read the caller — signed IP, client id, the current user — have nothing to work from. Address instances explicitly instead.

Apiker wraps the callback in a try/catch and only logs an uncaught error, so a bad run cannot break the next one — but that only protects across runs. Within a single run an unhandled error still stops the loop early, which is why the batch above catches errors per entry.