Task.define(config). You provide schemas for state and events, then implement handler functions that receive a TaskContext<S> and run as Effect programs.
Task.define(config)
Config parameters
Schema for the task’s persisted state. Must be a pure schema — no service dependencies in encoding or decoding.
Schema for incoming events. Also must be pure.
Runs when an event is sent to this task. Receives the decoded event and a full
TaskContext<S> for reading and writing state, scheduling alarms, and more.Runs when a scheduled alarm fires. The alarm bookmark is cleared before this handler is called.
Optional. Catches errors thrown by
onEvent or onAlarm. If not provided, handler errors propagate as TaskExecutionError. The handler can still call ctx.purge() to clean up state on failure.Optional. Intercepts external
getState() calls. Useful for redacting sensitive fields or computing derived fields before returning state to callers. Does not affect ctx.recall() inside other handlers.TaskContext<S>
Every handler receives aTaskContext<S> as its first argument. This is the only API you need inside a handler.
State
| Method | Signature | Description |
|---|---|---|
recall() | () => Effect<S | null, TaskError> | Read the currently persisted state. Returns null if no state has been saved yet. |
save(state) | (state: S) => Effect<void, TaskError> | Write the full state. |
update(fn) | (fn: (s: S) => S) => Effect<void, TaskError> | Read-modify-write. If current state is null, the update is a no-op. |
Scheduling
| Method | Signature | Description |
|---|---|---|
scheduleIn(delay) | (delay: Duration.Input) => Effect<void, TaskError> | Schedule an alarm relative to now. Accepts any Duration.Input, e.g. "5 seconds", "1 minute". |
scheduleAt(time) | (time: Date | number) => Effect<void, TaskError> | Schedule an alarm at an absolute timestamp. |
cancelSchedule() | () => Effect<void, TaskError> | Cancel the pending alarm. |
nextAlarm() | () => Effect<number | null, TaskError> | Get the scheduled alarm timestamp (milliseconds), or null if none. |
Lifecycle
| Method | Signature | Description |
|---|---|---|
purge() | () => Effect<never, PurgeSignal> | Delete all persisted state and cancel any scheduled alarm. The effect never succeeds — it always short-circuits with a PurgeSignal that the framework catches internally. |
Identity
| Property | Type | Description |
|---|---|---|
id | string | The instance ID passed to send(). |
name | string | The task name (the key used in createTasks). |
Schemas must be pure
State and event schemas cannot have service dependencies for encoding or decoding. They must satisfyPureSchema<T>:
Error handling
DefineonError to recover from handler failures:
onError is not provided, handler errors surface as TaskExecutionError. You can also call ctx.purge() inside onError to clean up state as part of the error recovery path.
Client state hook
UseonClientGetState to transform the state that external callers see when they call getState(). The hook runs only on external state reads — it does not affect ctx.recall() inside your handlers.
- Redact sensitive fields before returning to the client
- Compute derived fields (e.g.
progressfrom internal counters) - Merge data from external services (when combined with
withServices)
Tasks with service dependencies
If your handlers need Effect services, usewithServices() to provide a layer before passing the definition to createTasks. This eliminates the R type parameter:
registerTaskWithLayer(definition, layer) when building a test registry manually.
Multiple tasks in one DO
Pass multiple definitions tocreateTasks. They share a single Durable Object class but each task type maintains its own isolated state per instance ID:
Full example
The counter task from the quick start, withonError added:
