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 |
The intended fire time (UTC). This is the time the cron expression resolved to, not the actual dispatch time. Actual delivery may lag by up to ~30 seconds. |
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