Browse sections

Durable Functions

The durable orchestration engine coordinates long-running, stateful workflows—chaining steps, fanning out work, and waiting for events or approvals. Every run is checkpointed to Postgres, so runs survive restarts, with no queue or state store for you to operate. For durable retries on your deployed container functions, also see background jobs.

An orchestrator (an async generator) calls activity functions step by step. Every step is checkpointed to Postgres, so progress is durable: if the process restarts, the orchestrator replays its recorded history and resumes exactly where it left off.

How orchestration works

Write the orchestrator as an async generator and yield each step. Activities do the real work (and may be non-deterministic); the orchestrator only coordinates and must stay deterministic — use ctx.callActivity() for results and ctx.currentUtcDateTime for time, never Date.now() or Math.random().

ctx.callActivityWithRetry() retries a flaky activity with exponential backoff, and ctx.continueAsNew() restarts a long loop with fresh state so the history stays bounded.

Durable orchestration turn loop: a resume job replays the generator over history; activities run inline and loop within the turn; a timer or external-event wait appends its command and suspends with status WAITING; when the timer fires or the event arrives, a new resume job is enqueued; when the generator returns, the orchestration is COMPLETED
One turn at a time: replay is cheap because recorded commands are memoized, and waiting costs no compute — the container is freed while the instance is WAITING.
orchestrator.js
// orchestrator.js — coordinate activities, a durable timer, and an approval gate.
// Register it like any function, then start it via global.durable or the HTTP API.
exports.orchestrator = async function* (ctx) {
  const order = ctx.getInput();

  // Each activity result is checkpointed to Postgres (replayed, not re-run, on resume).
  const charged = yield ctx.callActivity('chargeCard', order);

  // Retry a flaky step with exponential backoff.
  yield ctx.callActivityWithRetry('reserveStock', {
    maxNumberOfAttempts: 3,
    firstRetryIntervalInMilliseconds: 1000,
    backoffCoefficient: 2,
  }, order);

  // Durable timer — the orchestration suspends (no container, no compute) for 24h.
  yield ctx.createTimer(new Date(Date.now() + 24 * 60 * 60 * 1000));

  // Wait for a human approval or webhook — resumes when the event is raised.
  const approval = yield ctx.waitForExternalEvent('Approval', 86400000);

  if (approval && approval.approved) {
    return yield ctx.callActivity('ship', order);
  }
  return yield ctx.callActivity('refund', charged);
};

Durable timers & external events

ctx.createTimer() sleeps for minutes, hours, or days, and ctx.waitForExternalEvent() pauses until a named event arrives (a human approval, a webhook callback). While waiting the orchestration is suspended — it holds no container and consumes no compute — and resumes automatically when the timer fires or the event is raised.

Starting & controlling orchestrations

From a function, the injected global.durable client manages instances: startNew(name, id?, input?), getStatus(id), waitForCompletion(id, ms), raiseEvent(id, name, data) and terminate(id, reason). An optional idempotency key on startNew dedupes duplicate starts.

HTTP API

Or drive orchestrations over HTTP: POST /durable/orchestrations/{name}/start returns 202 with the instance id plus status, raise-event, and terminate URIs; poll GET /durable/orchestrations/{id}/status for progress.