Pulse API Reference
Handler signature
export default {
async pulse(event, env) {
// ...
},
};The pulse export is optional. If present, it is called for every scheduled tick. If absent, the Cell silently ignores any pulse schedules declared in ribo.toml.
The event object
| Property | Type | Description |
|---|---|---|
event.scheduledTime |
Date |
When the pulse was dispatched (UTC). The scheduler polls every 30 seconds, so this is up to ~30 s after the exact cron slot, so don't compare it to the cron boundary for equality; truncate/round down if you need the slot. |
event.cron |
string |
The cron expression that triggered this pulse (e.g. "0 * * * *"). Useful when a Cell has multiple [[pulse]] schedules and needs to distinguish between them. |
The env object
env is identical to the env passed to fetch. All bindings declared in ribo.toml (c3 databases, g7 buckets, text values) are available in the pulse handler.
async pulse(event, env) {
// c3: reads and writes work exactly as in fetch
const { results } = await env.DB.prepare("SELECT COUNT(*) as n FROM jobs").all();
// g7: object storage is fully accessible
await env.BUCKET.put("report.txt", buildReport(results));
// outbound fetch: unrestricted
await fetch("https://hooks.example.com/notify", {
method: "POST",
body: JSON.stringify({ fired: event.scheduledTime }),
});
}Return value
The return value of pulse is ignored. There is no HTTP response to produce. The function can return void, undefined, or a Promise of either. Unhandled exceptions are logged by the platform and do not affect subsequent pulse firings.
Distinguishing multiple schedules
If your Cell has more than one [[pulse]] block, use event.cron to branch:
async pulse(event, env) {
switch (event.cron) {
case "* * * * *":
await runMinuteTask(env);
break;
case "0 * * * *":
await runHourlyTask(env);
break;
case "0 0 * * *":
await runDailyTask(env);
break;
}
}Common patterns
Periodic cleanup
async pulse(event, env) {
const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
await env.DB.prepare("DELETE FROM events WHERE created_at < ?").bind(cutoff).run();
}Aggregate and report
async pulse(event, env) {
const { results } = await env.DB.prepare(
"SELECT COUNT(*) as total FROM orders WHERE created_at > datetime('now', '-1 hour')"
).all();
const count = results[0]?.total ?? 0;
await fetch("https://monitoring.example.com/metric", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ metric: "orders_per_hour", value: count, ts: event.scheduledTime }),
});
}Sync from an external API
async pulse(event, env) {
const res = await fetch("https://api.example.com/data");
const items = await res.json();
for (const item of items) {
await env.DB.prepare(
"INSERT OR REPLACE INTO items (id, name, updated_at) VALUES (?, ?, ?)"
).bind(item.id, item.name, new Date().toISOString()).run();
}
}Limitations
Best-effort delivery. If the Cell is unavailable or throws an unhandled error during a pulse, the event is not retried. Use c3 to record state and detect missed ticks if your use case requires reliability.
Minimum interval: 1 minute. Cron expressions with sub-minute intervals are not supported.
UTC only. All cron schedules are evaluated in UTC. There is no timezone support.
No waitUntil. The ctx argument is not passed to pulse. Long-running work must complete before the handler returns (or await it inline).
See also
- Pulse Overview
- ribo.toml Reference:
[[pulse]]declaration - c3 API Reference: structured data in Cells
- g7 API Reference: object storage in Cells