Context Object
The ctx object is passed to every cron and task handler.
| Property | Type | Description |
|---|---|---|
| ctx.executionId | string | Unique ID for this execution |
| ctx.attempt | number | Current retry attempt (1-indexed) |
| ctx.scheduledAt | Date | When this run was scheduled |
| ctx.log(message, meta?) | LogFunction | Add a structured log entry (see below) |
| ctx.task(name, payload) | (string, any) => Promise<void> | Dispatch a child task |
ctx.executionId
A unique UUID for the current execution. Useful for correlating logs or building idempotency keys:
export const processPayment = cron("process", "0 * * * *", async (ctx) => {
await processWithKey(ctx.executionId);
});ctx.attempt
The current retry attempt, starting at 1. Use it to adjust behavior on retries:
export const syncData = cron("sync", "0 */6 * * *", async (ctx) => {
if (ctx.attempt > 1) {
ctx.log(`Retry attempt ${ctx.attempt}`);
}
await sync();
}, { retries: 3 });ctx.log(message, meta?)
Adds a structured log entry visible in the dashboard. Logs include timestamps and appear in the execution detail view. Calling ctx.log() directly logs at the info level. You can also use .warn(), .error(), and .debug() for other levels. All methods accept an optional metadata object as the second argument.
| Method | Signature | Level |
|---|---|---|
| ctx.log() | (message: string, meta?: object) => void | info (default) |
| ctx.log.info() | (message: string, meta?: object) => void | info |
| ctx.log.warn() | (message: string, meta?: object) => void | warning |
| ctx.log.error() | (message: string, meta?: object) => void | error |
| ctx.log.debug() | (message: string, meta?: object) => void | debug |
export const cleanup = cron("cleanup", "0 3 * * *", async (ctx) => {
const expired = await getExpiredSessions();
ctx.log(`Found ${expired.length} expired sessions`);
await deleteAll(expired);
ctx.log("Cleanup complete");
});Structured Logging
Pass a metadata object as the second argument to attach structured data to any log entry. Use different log levels to categorize messages:
export const syncData = cron("sync-data", "0 */6 * * *", async (ctx) => {
ctx.log("Starting sync", { source: "postgres" });
ctx.log.debug("Fetching records", { batchSize: 100 });
const records = await fetchRecords();
ctx.log(`Fetched ${records.length} records`, { count: records.length });
for (const record of records) {
try {
await sync(record);
} catch (err) {
ctx.log.error("Failed to sync record", { recordId: record.id, code: "E001" });
}
}
if (records.length > 10000) {
ctx.log.warn("Large batch detected", { count: records.length, threshold: 10000 });
}
ctx.log("Sync complete", { synced: records.length });
});ctx.task(name, payload)
Dispatches a child task for fan-out. The task is collected and dispatched after the parent handler completes. See the Tasks page for details.
export const sendEmails = cron("send-emails", "*/15 * * * *", async (ctx) => {
const pending = await getPending();
for (const item of pending) {
await ctx.task("send-email", { id: item.id });
}
});