---
cuppy_hub: azure
cuppy_path: guides/functions/deploy-typescript-function.md
cuppy_section: docs
cuppy_doc: S6Q1-SFPT
cuppy_doc_uuid: a8ad8ed3-60cb-467c-a3b4-ce7e546d7d71
cuppy_permalink: https://cuppy.io/azure/d/S6Q1-SFPT
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/azure/docs/guides/functions/deploy-typescript-function
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 34d5f108efb6de4a5065abe8345e1bd54949faf649ea74f937e12c8095bae484
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Deploy a TypeScript function app to Flex Consumption without keys

Checked with Bicep 0.48.1: `Microsoft.Web` 2025-05-01, 2026-03-15 and 2026-07-15 are stable in the REST specs but have **no Bicep types** (`BCP081`), so this uses **2025-03-01**. Don't create this app with `az functionapp create`: it always writes a key-based connection string (`reference/functions/errors.md`).

## 1. Infrastructure (`main.bicep`)

```bicep
@description('Function app name: globally unique, and keep it under 32 characters (host ID).')
@maxLength(32)
param appName string

@description('Storage account for host storage and the deployment package: 3-24 lowercase letters and numbers.')
param storageName string

param location string = resourceGroup().location

@allowed(['22', '24'])
param nodeVersion string = '22'

resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
  name: 'id-${appName}'
  location: location
}

resource storage 'Microsoft.Storage/storageAccounts@2026-06-01' = {
  name: storageName
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
  properties: {
    minimumTlsVersion: 'TLS1_2'
    allowSharedKeyAccess: false
    allowBlobPublicAccess: false
    defaultToOAuthAuthentication: true
  }
}

resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2026-06-01' = {
  parent: storage
  name: 'default'
}

resource deploymentContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2026-06-01' = {
  parent: blobService
  name: 'app-package'
}

// Storage Blob Data Owner (host minimum), Storage Table Data Contributor (host diagnostic events),
// Storage Queue Data Contributor (the app's own queue output binding)
var storageRoles = [
  'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'
  '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3'
  '974c5e8b-45b9-4653-ba55-5f855dd0fb88'
]

resource storageRoleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for roleId in storageRoles: {
  scope: storage
  name: guid(storage.id, identity.id, roleId)
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
    principalId: identity.properties.principalId
    principalType: 'ServicePrincipal'
  }
}]

resource plan 'Microsoft.Web/serverfarms@2025-03-01' = {
  name: 'plan-${appName}'
  location: location
  kind: 'functionapp'
  sku: {
    name: 'FC1'
    tier: 'FlexConsumption'
  }
  properties: {
    reserved: true
  }
}

resource app 'Microsoft.Web/sites@2025-03-01' = {
  name: appName
  location: location
  kind: 'functionapp,linux'
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${identity.id}': {}
    }
  }
  properties: {
    serverFarmId: plan.id
    httpsOnly: true
    siteConfig: {
      minTlsVersion: '1.2'
      appSettings: [
        {
          name: 'AzureWebJobsStorage__accountName'
          value: storage.name
        }
        {
          name: 'AzureWebJobsStorage__credential'
          value: 'managedidentity'
        }
        {
          name: 'AzureWebJobsStorage__clientId'
          value: identity.properties.clientId
        }
        {
          name: 'OrdersQueue__queueServiceUri'
          value: storage.properties.primaryEndpoints.queue
        }
        {
          name: 'OrdersQueue__credential'
          value: 'managedidentity'
        }
        {
          name: 'OrdersQueue__clientId'
          value: identity.properties.clientId
        }
        {
          name: 'FUNCTIONS_NODE_BLOCK_ON_ENTRY_POINT_ERROR'
          value: 'true'
        }
      ]
    }
    functionAppConfig: {
      deployment: {
        storage: {
          type: 'blobContainer'
          value: '${storage.properties.primaryEndpoints.blob}${deploymentContainer.name}'
          authentication: {
            type: 'UserAssignedIdentity'
            userAssignedIdentityResourceId: identity.id
          }
        }
      }
      scaleAndConcurrency: {
        instanceMemoryMB: 2048
        maximumInstanceCount: 100
      }
      runtime: {
        name: 'node'
        version: nodeVersion
      }
    }
  }
  dependsOn: [
    storageRoleAssignments
  ]
}

output appUrl string = 'https://${app.properties.defaultHostName}'
output identityClientId string = identity.properties.clientId
```

`main.bicepparam`:

```bicep
using './main.bicep'

param appName = 'func-orders-prod'
param storageName = 'stfuncordersprod'
```

Deploy with `az deployment group create -g <rg> -f main.bicep -p main.bicepparam`. The app waits on the role assignments (`dependsOn`), but role propagation can still take minutes: if the first start logs storage 403s, restart the app after 10–30 minutes. No setting holds a key; there's no `FUNCTIONS_WORKER_RUNTIME` or `WEBSITE_RUN_FROM_PACKAGE` (Flex rejects them). Add `APPLICATIONINSIGHTS_CONNECTION_STRING` for telemetry (observability family).

## 2. The function (`src/functions/createOrder.ts`)

```ts
import { app, HttpRequest, HttpResponseInit, InvocationContext, output } from "@azure/functions";

// Queue "orders" through the identity-based connection "OrdersQueue"
// (app settings OrdersQueue__queueServiceUri, OrdersQueue__credential, OrdersQueue__clientId).
const ordersQueue = output.storageQueue({ queueName: "orders", connection: "OrdersQueue" });

interface Order {
  id: string;
  amount: number;
}

function isOrder(value: unknown): value is Order {
  const v = value as Partial<Order> | null;
  return typeof v?.id === "string" && typeof v?.amount === "number";
}

export async function createOrder(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
  const body: unknown = await request.json().catch(() => undefined);
  if (!isOrder(body)) {
    return { status: 400, jsonBody: { error: "Expected { id: string, amount: number }" } };
  }
  context.extraOutputs.set(ordersQueue, body);
  context.log(`Queued order ${body.id}`);
  return { status: 202, jsonBody: { id: body.id } };
}

app.http("createOrder", {
  methods: ["POST"],
  authLevel: "function",
  route: "orders",
  extraOutputs: [ordersQueue],
  handler: createOrder,
});
```

`package.json` needs `"main": "dist/src/functions/*.js"` (the compiled output, not `.ts`), `@azure/functions` 4.x in `dependencies`, and a `build` script running `tsc`. A wrong `main` is the usual cause of `No job functions found.` For local runs, `local.settings.json` holds the same connection names; locally they authenticate as your `az login` identity, which needs the same roles.

## 3. Deploy the code

```bash
npm ci && npm run build
zip -r app.zip host.json package.json package-lock.json dist
az functionapp deployment source config-zip -g <rg> -n <app> --src app.zip --build-remote true
```

`--build-remote true` runs `npm install` on the platform, so `node_modules` stays out of the zip. Then call `https://<app>.azurewebsites.net/api/orders` with a function key (`az functionapp function keys list -g <rg> -n <app> --function-name createOrder`), and confirm a message reaches the `orders` queue. Failures: `reference/functions/errors.md`.

Telemetry for the app: `guides/monitor/instrument-node-app-with-opentelemetry.md` and `reference/monitor/application-insights-errors.md`.


---
<!-- 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 -->
