---
cuppy_hub: azure
cuppy_path: reference/functions/durable-functions.md
cuppy_section: docs
cuppy_doc: B462-59WP
cuppy_doc_uuid: b494ab66-55ae-4e7e-9963-b36cd5566f76
cuppy_permalink: https://cuppy.io/azure/d/B462-59WP
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/azure/docs/reference/functions/durable-functions
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 6b63096bc700e5f08137a4a687ea8d8af204a03a4cd4988f8921b419af7c0675
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Durable Functions

## Pick a backend

| Backend | Use when | Notes |
|---|---|---|
| **Durable Task Scheduler** (recommended) | New apps, any Functions plan | Managed service with its own state store; gRPC to `{name}.{region}.durabletask.io` with Entra identity, no keys (`Authentication=ManagedIdentity` in Azure; `DefaultAzure` or `AzureCLI` locally); dashboard; private endpoints; Dedicated or Consumption SKU |
| Azure Storage (default) | Lowest-cost, already-built apps | Queues, tables and blobs in a storage account you own; 1–16 partitions (default 4), set when the task hub is created |
| MSSQL | You need SQL Server | Identity-based connections need runtime-driven scaling |
| Netherite | Existing apps only | **Support ends March 31, 2028**; not on Flex Consumption |

**Task hubs** hold an app's orchestration state. Names are alphanumeric, 3–45 characters. **Each app, and each staging slot, needs its own task hub**: apps sharing one compete for messages and orchestrations get stuck. The default (the app name) avoids this; if you set `hubName` in `host.json`, make it an app setting (`%TASKHUB_NAME%`) and slot-sticky.

## Durable Task Scheduler with a managed identity

```bicep
// Durable Task Scheduler + task hub, with a user-assigned identity granted access to the hub
param location string = resourceGroup().location
param schedulerName string
param taskHubName string = 'orders'
param identityPrincipalId string

param ipAllowlist array // ranges allowed to reach the endpoint

resource scheduler 'Microsoft.DurableTask/schedulers@2026-02-01' = {
  name: schedulerName
  location: location
  properties: {
    ipAllowlist: ipAllowlist
    sku: {
      name: 'Consumption'
    }
  }
}

resource hub 'Microsoft.DurableTask/schedulers/taskHubs@2026-02-01' = {
  parent: scheduler
  name: taskHubName
}

// Durable Task Data Contributor
var durableTaskDataContributor = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '0ad04412-c4d5-4796-b79c-f76d14c8d402')

resource hubAccess 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  scope: hub
  name: guid(hub.id, identityPrincipalId, durableTaskDataContributor)
  properties: {
    roleDefinitionId: durableTaskDataContributor
    principalId: identityPrincipalId
    principalType: 'ServicePrincipal'
  }
}

output endpoint string = scheduler.properties.endpoint
```

- Roles: **Durable Task Data Contributor** (everything; most apps), **Durable Task Worker** (processing only), **Durable Task Data Reader**. Scope to the task hub; the scheduler scope covers all hubs. Without a role for yourself the dashboard shows `Unknown error retrieving details of this task hub`.
- App settings: `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` = `Endpoint=<endpoint>;Authentication=ManagedIdentity;ClientID=<identity client ID>` (omit `ClientID` for a system-assigned identity) and `TASKHUB_NAME`.
- `host.json`: `extensions.durableTask.storageProvider` = `{ "type": "azureManaged", "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING" }`, `hubName` = `%TASKHUB_NAME%`. Non-.NET apps need extension bundle **`[4.32.0, 5.0.0)`** or later. (Microsoft's provider comparison table still says bundle support is "coming soon"; the quickstart is newer.)
- Limits: schedulers and task hubs per region and subscription depend on the SKU; a **Consumption scheduler holds at most five task hubs**, and task hub quotas can't be raised. Each work-item completion is at most **3.9 MB**; larger payloads need the large-payload feature.
- Deleting a scheduler with task hubs in it fails with `CannotDeleteResource` `Cannot delete resource while nested resources exist…`: delete the task hubs first.

**Azure Storage backend with identity:** identity-based `AzureWebJobsStorage` (`reference/functions/hosting-and-host-storage.md`) plus Storage Queue, Blob and Table Data Contributor. On **Flex Consumption**, Microsoft advises one always-ready instance for the `durable` group and a queue polling interval of 10 seconds or less.

## TypeScript (Node.js v4 model, durable-functions 3.x)

```ts
import { type HttpRequest, type HttpResponse, type InvocationContext } from "@azure/functions";
import * as df from "durable-functions";
import { type OrchestrationContext, type OrchestrationHandler, type ActivityHandler } from "durable-functions";

// Orchestrator: deterministic; yield tasks, never await.
const fanOut: OrchestrationHandler = function* (context: OrchestrationContext) {
  const items = context.df.getInput<string[]>() ?? [];
  const retry = new df.RetryOptions(5000, 3);
  const tasks = items.map((id) => context.df.callActivityWithRetry("processItem", retry, id));
  const results: number[] = yield context.df.Task.all(tasks);
  return results.reduce((a, b) => a + b, 0);
};
df.app.orchestration("fanOut", fanOut);

const processItem: ActivityHandler = async (id: unknown, ctx: InvocationContext): Promise<number> => {
  ctx.log(`processing ${String(id)}`);
  return 1; // activities do the I/O
};
df.app.activity("processItem", { handler: processItem });

df.app.client.http("startFanOut", {
  route: "orchestrators/fanOut",
  methods: ["POST"],
  extraInputs: [df.input.durableClient()],
  handler: async (req: HttpRequest, client: df.DurableClient, _ctx: InvocationContext): Promise<HttpResponse> => {
    const instanceId = await client.startNew("fanOut", { input: await req.json() });
    return client.createCheckStatusResponse(req, instanceId);
  },
});
```

Orchestrators **replay** from history, so they must be deterministic: use `context.df.currentUtcDateTime`, durable timers and activities instead of clocks, random values, network calls or `await`. Breaking changes to in-flight orchestrations leave them stuck: version them.

## When orchestrations don't progress

| Symptom | Cause → fix |
|---|---|
| Stuck in `Pending` | The start message isn't being picked up. Check Durable Task Framework traces for the instance ID in Application Insights; on Azure Storage, look in the `<hub>-control-NN` queues; another app on the same task hub may be taking the messages; memory (switch App Service to 64-bit) |
| Stuck in `Running` | Waiting on an activity, timer or external event that never completes, or a breaking code change. Restart the app; look for one control queue that keeps growing |
| Starts late, or runs slowly | Azure Storage provider polling and partition rebalancing (on Flex, lower the polling interval); CPU or memory pressure |
| Scheduler access denied, or the app can't connect | Missing role on the hub, identity not attached to the app, or a wrong `Endpoint`/`ClientID` in the connection string |
| `Encountered an error (ServiceUnavailable) from host runtime` on deploy | Required app settings (connection string, `TASKHUB_NAME`) missing |


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 107 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/azure/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
