# How to Configure Durable Object Alarm Scheduling in workerd

> Learn to configure Durable Object alarm scheduling in workerd. Implement alarm() and setAlarm() to reliably schedule tasks with automatic retries and jitter.

- Repository: [Cloudflare/workerd](https://github.com/cloudflare/workerd)
- Tags: how-to-guide
- Published: 2026-03-18

---

**Durable Object alarms in workerd are configured by implementing an `alarm()` method in your DO class and calling `ctx.storage.setAlarm(timestamp)` to persist the schedule to SQLite, where the internal AlarmScheduler manages execution with automatic retries and jitter.**

The **workerd** runtime enables **Durable Objects (DOs)** to schedule future `alarm()` invocations that execute independently of client requests. This mechanism relies on a three-layer architecture spanning storage APIs, an in-process scheduler, and the DO interface itself.

## Architecture Overview

Understanding how workerd handles alarms requires examining three distinct layers in the source code.

### Storage-Level API

The `ctx.storage.setAlarm(msSinceEpoch)` method writes the alarm time into the per-object SQLite metadata. This implementation resides in `src/workerd/io/actor-sqlite.c++` (see `setAlarm()` at lines [740-749](https://github.com/cloudflare/workerd/blob/main/src/workerd/io/actor-sqlite.c++#L740-L749)), which delegates persistence to `src/workerd/util/sqlite-metadata.c++` (lines [23-31](https://github.com/cloudflare/workerd/blob/main/src/workerd/util/sqlite-metadata.c++#L23-L31)).

### In-Process Scheduler

The **AlarmScheduler** class manages pending alarms in memory, loading them from SQLite on startup and creating `kj::Promise` objects that fire at the scheduled time. It handles jitter and exponential back-off for retries. Key definitions appear in [`src/workerd/server/alarm-scheduler.h`](https://github.com/cloudflare/workerd/blob/main/src/workerd/server/alarm-scheduler.h) (lines [43-70](https://github.com/cloudflare/workerd/blob/main/src/workerd/server/alarm-scheduler.h#L43-L70)) with implementation logic in `src/workerd/server/alarm-scheduler.c++` (lines [104-121](https://github.com/cloudflare/workerd/blob/main/src/workerd/server/alarm-scheduler.c++#L104-L121)).

### Durable Object Interface

A DO declares an optional `alarm(alarmInfo?)` method that the scheduler invokes when the alarm fires. The optional `alarmInfo` argument contains retry metadata including `isRetry` and `retryCount`. This interface is defined in [`types/defines/rpc.d.ts`](https://github.com/cloudflare/workerd/blob/main/types/defines/rpc.d.ts) (lines [61-63](https://github.com/cloudflare/workerd/blob/main/types/defines/rpc.d.ts#L61-L63)).

## Implementation Steps

Follow these steps to configure alarm scheduling in your Durable Object.

### 1. Declare the Alarm Handler

Add an `async alarm(alarmInfo?: AlarmInvocationInfo)` method to your DO class. You may also use `alarm()` without parameters if retry handling is not required.

### 2. Schedule the Alarm

Inside any request handler or method, call `await this.ctx.storage.setAlarm(Date.now() + delayMs)`. The parameter represents milliseconds since epoch (Unix timestamp), allowing any future point in time.

### 3. Cancel Scheduled Alarms

To clear a pending alarm, invoke `await this.ctx.storage.setAlarm(null)`. This removes the entry from SQLite and cancels the in-memory scheduled task.

### 4. Handle Retry Logic

Examine the `alarmInfo` object to determine if the alarm is a retry (`alarmInfo.isRetry`) and how many retries have occurred (`alarmInfo.retryCount`). Use this data to implement idempotent logic that safely handles repeated executions.

### 5. Persistence Across Restarts

The alarm time is stored in the DO's SQLite metadata (`alarms.sqlite`). On worker startup, `AlarmScheduler::loadAlarmsFromDb()` (defined at lines [60-68](https://github.com/cloudflare/workerd/blob/main/src/workerd/server/alarm-scheduler.c++#L60-L68) in `src/workerd/server/alarm-scheduler.c++`) automatically restores all pending alarms.

## Code Examples

### TypeScript/JavaScript Implementation

```typescript
// src/my-do.ts
export class CounterDO {
  // ctx is a DurableObjectState injected by workerd
  constructor(private readonly ctx: DurableObjectState) {}

  // Optional alarm handler – receives retry info if the alarm is being retried
  async alarm(alarmInfo?: AlarmInvocationInfo) {
    // Example: increment a counter stored in durable storage
    const val = (await this.ctx.storage.get<number>('count')) ?? 0;
    await this.ctx.storage.put('count', val + 1);
    console.log('Alarm fired', {retry: alarmInfo?.isRetry});
  }

  // Regular fetch handler – schedules an alarm 30 seconds from now
  async fetch(request: Request) {
    const delay = 30_000; // 30s
    await this.ctx.storage.setAlarm(Date.now() + delay);
    return new Response('Alarm scheduled');
  }

  // Optional helper to cancel any pending alarm
  async cancelAlarm() {
    await this.ctx.storage.setAlarm(null);
  }
}

```

### Python Implementation

```python

# src/workerd/server/tests/python/durable-object/worker.py

class CounterDO:
    def __init__(self, ctx):
        self.ctx = ctx
        self.alarm_triggered = False

    async def fetch(self, request):
        # schedule alarm 100 ms in the future

        await self.ctx.storage.setAlarm(Date.now() + 100)
        return Response('OK')

    async def alarm(self, alarm_info):
        self.alarm_triggered = True

```

### C++ Internals

The following excerpt from `src/workerd/server/alarm-scheduler.c++` demonstrates how the scheduler persists alarms to SQLite before updating in-memory state:

```c++
// src/workerd/server/alarm-scheduler.c++ (excerpt)
bool AlarmScheduler::setAlarm(ActorKey actor, kj::Date scheduledTime) {
  // Persist to SQLite first; if DB write fails we abort the in-memory update.
  if (!db->runTxn([&](SqliteTransaction& txn) {
        return txn.exec(stmtSetAlarm, actor.uniqueKey, actor.actorId, scheduledTime);
      })) {
    return false;
  }
  // Update in-memory map and (re)queue the task.
  setAlarmInMemory(kj::heap<ActorKey>(actor), scheduledTime);
  return true;
}

```

## Summary

- **Work Flow**: Call `ctx.storage.setAlarm(timestamp)` to write to SQLite, then `AlarmScheduler` manages the execution timeline.
- **Persistence**: Alarms survive process restarts because they are stored in the DO's SQLite metadata and reloaded via `loadAlarmsFromDb()`.
- **Cancellation**: Pass `null` to `setAlarm()` to clear pending alarms.
- **Retries**: The `alarmInfo` parameter provides `isRetry` and `retryCount` for implementing idempotent alarm handlers.
- **Source Files**: Key implementations reside in `src/workerd/io/actor-sqlite.c++`, `src/workerd/server/alarm-scheduler.c++`, and [`types/defines/rpc.d.ts`](https://github.com/cloudflare/workerd/blob/main/types/defines/rpc.d.ts).

## Frequently Asked Questions

### How do I cancel a scheduled alarm in workerd?

Call `await this.ctx.storage.setAlarm(null)` from within your Durable Object. According to the implementation in `src/workerd/io/actor-sqlite.c++`, passing `null` (or `kj::none` in C++) clears the alarm entry from SQLite metadata and triggers the scheduler to remove the pending task from its in-memory queue.

### What happens to scheduled alarms when the worker restarts?

Scheduled alarms persist automatically. The `AlarmScheduler::loadAlarmsFromDb()` method (implemented in `src/workerd/server/alarm-scheduler.c++`) executes on startup, reading all alarm entries from the SQLite database and reconstructing the internal scheduling queue. This ensures alarms survive deployments and process restarts without additional configuration.

### How does retry logic work for failed alarms?

If an `alarm()` handler throws an exception or the process crashes during execution, the `AlarmScheduler` automatically reschedules the alarm with exponential back-off and jitter. When the retry fires, the scheduler passes an `AlarmInvocationInfo` object to your handler with `isRetry: true` and an incremented `retryCount`, allowing you to detect retries and implement appropriate back-off strategies or circuit breakers.

### Can I schedule multiple alarms for the same Durable Object?

No, the current implementation in `workerd` supports only one scheduled alarm per Durable Object instance. Calling `setAlarm()` multiple times overwrites the previous alarm time in SQLite. If you need multiple future events, store an array of timestamps in durable storage and schedule the next imminent alarm, rescheduling subsequent alarms within your `alarm()` handler as each fires.