AI-200 Azure Functions: Triggers, Bindings, Configuration and Deployment
Azure Functions is Azure's serverless compute service: you write small functions that a trigger (an HTTP request, a timer, a queue message, a new blob) starts, and bindings connect them to other services without connection code. This lesson covers both AI-200 skills in this area — building serverless APIs with triggers and bindings, and configuring and deploying function apps — using the Python v2 programming model, from HTTP routes, keys and binding expressions to retries, managed-identity connections, hosting plans, remote builds and deployment slots.
On this page9 sections
- How does the Python v2 programming model define functions?
- How do HTTP routes, prefixes and route constraints work?
- How do you secure an HTTP API: authorization levels, keys, user sign-in and CORS?
- How do input and output bindings and binding expressions work?
- How do the timer, Storage queue and blob triggers behave?
- Retry policies or built-in trigger retries: which applies?
- How do app settings, connections and managed identities configure a function app?
- Which hosting plan should a function app use, and how does it scale?
- How do you build, deploy and release a Python function app?
- Structure a Python v2 function app with decorators and blueprints, and diagnose an app that indexes no functions
- Build HTTP APIs with route templates, constraints and route prefixes, and protect them with the right key, App Service Authentication or CORS setting
- Use input and output bindings, binding expressions and SDK type bindings, and know when to use an SDK client instead
- Configure timer, Storage queue and blob triggers, and pick between runtime retry policies and each trigger's built-in retries
- Replace connection strings with identity-based connections and the least-privilege data-plane roles
- Choose between Flex Consumption, Premium, Dedicated and Container Apps hosting, and tune Flex scale settings
- Deploy with remote builds and packages, keep long HTTP work asynchronous, and release safely with slot settings and swaps
How does the Python v2 programming model define functions?
In the Python v2 programming model, every function is a decorated Python function registered on a single func.FunctionApp object in function_app.py; the decorators declare the trigger and bindings, so there is no function.json file per function (that is the v1 model). The Python worker discovers functions by importing function_app.py when the host starts.
import azure.functions as func
app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)
@app.route(route="health", methods=["GET"])
def health(req: func.HttpRequest) -> func.HttpResponse:
return func.HttpResponse("ok")- Organise large apps with blueprints. Declare related functions on a
func.Blueprint()in another module, then callapp.register_functions(bp)infunction_app.py. The result is still one function app, one deployment and one set of app settings. A secondFunctionAppobject in another module is not how the model is extended. - App-wide HTTP defaults. HTTP functions default to the function authorization level. Passing
http_auth_leveltofunc.FunctionAppchanges the default for every HTTP function the app registers, including ones added later;auth_levelon one@app.routeoverrides it for that function. - A failed import hides every function. Because indexing imports
function_app.py, a top-levelimportthat raisesModuleNotFoundErrorstops indexing, and the app lists no functions at all, not just the new one. The usual cause is a package installed in the local virtual environment but missing fromrequirements.txt, which is all a remote build installs.
How do HTTP routes, prefixes and route constraints work?
An HTTP trigger's route is a template relative to the app's route prefix, which is api by default, so route="customers/{customer_id}" is served at /api/customers/7. Never repeat api in the route.
- Route parameters appear in braces and are read from
req.route_params; query-string values come fromreq.params. - Route constraints such as
{id:int},{name:alpha}or an optional{id?}make the host match only values of that shape. A request that doesn't match gets a 404 before your code runs, so no validation code is needed for the shape. - The prefix is app-wide configuration in
host.jsonunderextensions.http.routePrefix. Setting it to an empty string serves every function from the root without editing any route.local.settings.jsonis not the place: it never reaches Azure. - Reserved paths. The host owns
/adminand/runtime. With the default prefix your routes sit under/apiand can't collide; with an empty prefix, a route whose first segment is a reserved name (for exampleruntime/status) falls into the host's space and returns 404. Choose a different first segment (or keep a prefix) — authorization level and methods are only checked after routing reaches the function.
@app.route(route="invoices/{number:int}/lines", methods=["GET"])
def list_lines(req: func.HttpRequest) -> func.HttpResponse:
invoice_number = req.route_params.get("number")
page = req.params.get("page", "1")
...How do input and output bindings and binding expressions work?
Bindings connect a function to another service declaratively: an input binding reads data before your code runs, and an output binding writes data after it returns, so you write no SDK code for simple reads and writes.
- Output bindings. Either return the value (when the return isn't already the HTTP response) or declare a
func.Out[T]parameter and call.set(value). Reassigning the parameter writes nothing. The runtime performs the write after the function finishes. - You can't catch output-binding errors. Because the write happens after your code completes, a failure can't be caught in your code or turned into a response of your choosing. When the code must react to a failed write, use the service's SDK instead.
- Reuse SDK clients. An instance runs many invocations, and each client opens its own connections. Create clients such as
CosmosClientonce at module scope and reuse them; creating one per request exhausts outbound connections under load.
@app.route(route="feedback", methods=["POST"])
@app.queue_output(arg_name="note", queue_name="feedback-in",
connection="FeedbackStorage")
def capture_feedback(req: func.HttpRequest,
note: func.Out[str]) -> func.HttpResponse:
note.set(req.get_body().decode())
return func.HttpResponse("queued", status_code=202)Binding expressions fill binding properties at run time:
| Expression | Resolves to |
|---|---|
{id} | The HTTP route parameter named id (or a property of a JSON trigger payload) |
{Query.id} | The query-string parameter id of an HTTP request |
{queueTrigger} | The content of the triggering Storage queue message |
%NAME% | The value of the app setting NAME — so one package can read orders-dev in one app and orders-prod in another |
{rand-guid}, {DateTime} | Generated values, typically for output blob names |
A Cosmos DB input binding does a point read when it is given the item id and its partition_key value. Here the container is partitioned on /tenant, so the route carries both values:
@app.route(route="tenants/{tenant}/profiles/{profile_id}",
methods=["GET"])
@app.cosmos_db_input(arg_name="profile", database_name="crm",
container_name="profiles",
connection="CrmCosmos",
id="{profile_id}", partition_key="{tenant}")SDK type bindings let a Python v2 binding hand you an Azure SDK object instead of content. With the azurefunctions-extensions-bindings-blob package in requirements.txt (it pulls in azurefunctions-extensions-base), a blob trigger or input parameter can be typed as blob.BlobClient, blob.ContainerClient or blob.StorageStreamDownloader from azurefunctions.extensions.bindings.blob. A BlobClient streams large blobs instead of loading them into memory and exposes operations such as set_blob_metadata(), all through the binding's connection. func.InputStream and bytes are content-only. The extension bundle in host.json is a host-side setting and installs no Python modules.
How do the timer, Storage queue and blob triggers behave?
These triggers start background work without an HTTP caller, and each has defaults that decide timing, retries and concurrency.
Timer trigger
- Schedules are six-field NCRONTAB expressions with a leading seconds field:
{second} {minute} {hour} {day} {month} {day-of-week}. Daily at 04:15 is"0 15 4 * * *";"0 */15 4 * * *"would fire four times in that hour. - The default time zone is UTC. On Premium and Dedicated plans
WEBSITE_TIME_ZONE(Windows) orTZ(Linux) can change it, but Flex Consumption doesn't support either, so convert to UTC yourself: 09:00 in India (UTC+5:30, no daylight saving) is 03:30 UTC, so"0 30 3 * * *". - Scale-out doesn't multiply runs: the timer takes a lock in the host storage account so one instance runs each occurrence. The lock is per app (host ID), so a deployment slot is a second app and runs its own schedule.
run_on_startup=Truealso fires the function whenever the runtime starts — on restarts and scale-out — which produces extra runs at unpredictable times; avoid it in production.use_monitortracks occurrences to detect past-due schedules; it doesn't retry anything.
Storage queue trigger
- When the function raises, the message becomes visible again and is retried. After
maxDequeueCountattempts (default 5) it moves to<queue>-poison. To log or alert on those messages, add a function (or other reader) on the poison queue.visibilityTimeoutsets the delay between attempts. - Concurrency per function per instance is
batchSize + newBatchThreshold. The runtime fetchesbatchSizemessages (default 16) and fetches another batch when in-flight messages drop tonewBatchThreshold, whose default is N × batchSize / 2 for N vCPUs. To cap concurrency at a downstream limit of six, setbatchSize6 andnewBatchThreshold0.maxPollingIntervalonly governs polling of an empty queue.
{
"version": "2.0",
"extensions": {
"queues": {
"maxDequeueCount": 4,
"visibilityTimeout": "00:02:00",
"batchSize": 6,
"newBatchThreshold": 0
}
}
}Blob trigger: polling or Event Grid
- By default the blob trigger discovers new blobs by polling (storage logs plus periodic container scans), which gets slow on containers with millions of blobs. Queue polling settings, more instances or blob parallelism don't change that.
- The Event Grid source (
source="EventGrid"onblob_trigger) receives a BlobCreated event per upload, giving low latency while keeping the blob trigger. It needs an Event Grid subscription on the storage account with a Web Hook endpoint at the blob extension's webhook:https://<app>.azurewebsites.net/runtime/webhooks/blobs?functionName=Host.Functions.<name>&code=<blobs_extension key>. The "Azure Function" endpoint type targets Event Grid triggers, not blob triggers. - Flex Consumption supports only the Event Grid source, so a polling blob trigger moved to Flex never fires until both changes are made.
- The blob trigger also uses the same account's Queue service to track blobs that keep failing (poison blobs) — which matters when you move its connection to a managed identity (see below).
Retry policies or built-in trigger retries: which applies?
Runtime retry policies apply only to the Timer, Azure Cosmos DB, Event Hubs and Kafka triggers; every other trigger either retries on its own or not at all, and HTTP triggers never use retry policies.
| Trigger | What happens when the function raises | How you control it |
|---|---|---|
| Timer, Cosmos DB, Event Hubs, Kafka | Nothing, unless a retry policy reruns the execution | @app.retry(strategy="fixed_delay"|"exponential_backoff", max_retry_count=..., ...) |
| Storage queue | Message becomes visible again; after the dequeue limit it goes to -poison | maxDequeueCount, visibilityTimeout in host.json |
| Service Bus | Received in PeekLock; success completes the message, failure abandons it and the delivery count rises; at the entity's max delivery count Service Bus dead-letters it | The queue or subscription's max delivery count |
| HTTP | The caller gets an error response | The caller retries |
@app.event_hub_message_trigger(arg_name="event",
event_hub_name="telemetry",
connection="TelemetryHub")
@app.retry(strategy="fixed_delay", max_retry_count="3",
delay_interval="00:00:10")
def ingest(event: func.EventHubEvent) -> None:
...A fixed_delay policy takes delay_interval; exponential_backoff takes minimum_interval and maximum_interval. max_retry_count="-1" retries indefinitely. That matters for Event Hubs: the trigger doesn't redeliver failed events, and without a policy the checkpoint advances and the batch is lost. With a policy, the checkpoint isn't written until the retries finish, so an unbounded policy holds the partition until the downstream store recovers. Service Bus dead-letter handling and Event Grid retry settings themselves belong to the messaging lesson.
How do app settings, connections and managed identities configure a function app?
Function app configuration lives in application settings, which the runtime exposes as environment variables (os.environ, os.getenv); a binding's connection property names a setting, or a group of settings with a shared prefix.
local.settings.jsonis local only. Core Tools loads itsValuesinto environment variables forfunc start; the file is never used in Azure, so code that works locally fails with a missing setting after publishing. Create the settings on the app:func azure functionapp publish <app> --publish-local-settings(-i) uploads them, oraz functionapp config appsettings set --settings KEY=VALUE ...sets them (az functionapp config setis site configuration, andfunc settings addedits the local file).- Identity-based connections replace a connection string with settings named
<prefix>__<property>. The property depends on the service:
| Service | Setting |
|---|---|
| Service Bus, Event Hubs | <prefix>__fullyQualifiedNamespace = <namespace>.servicebus.windows.net |
| Blob or Queue (one service) | <prefix>__serviceUri (alias for a single service) |
| Blob trigger (Blob + Queue) | <prefix>__blobServiceUri and <prefix>__queueServiceUri |
| Cosmos DB | <prefix>__accountEndpoint |
| Host storage only | AzureWebJobsStorage__accountName (standard endpoints), or its service URIs for custom DNS |
- A plain URL in the original single setting is parsed as a connection string and fails; the identity form always uses
__properties. - The system-assigned identity is used by default. For a user-assigned identity add
<prefix>__credential=managedidentityand either<prefix>__clientIdor<prefix>__managedIdentityResourceId(never both). Managed identities have no client secret. - Data-plane roles, least privilege: Azure Service Bus Data Receiver for a Service Bus trigger (Data Sender for output); for host storage Microsoft lists Storage Blob Data Owner as the minimum, which also covers the blob locks the timer trigger takes. Reader roles can't write, and management-plane roles such as Storage Account Contributor grant no data access. Key Vault references and App Configuration are covered in the security lesson.
Which hosting plan should a function app use, and how does it scale?
For a new serverless app, Microsoft recommends the Flex Consumption plan: Linux, execution-based billing with scale to zero, virtual network integration, always ready instances and per-function scaling. Choose another plan when a requirement rules Flex out — most often deployment slots.
| Plan | Scaling and billing | VNet | Slots | Cold starts |
|---|---|---|---|---|
| Flex Consumption | Event-driven, per function group; scale to zero; pay per execution plus any always ready baseline | Yes | No (rolling updates, in preview) | Avoided with always ready instances |
| Elastic Premium | Event-driven; at least one warm instance billed | Yes | Yes | Avoided with prewarmed and always ready instances |
| Dedicated (App Service plan) | Manual or autoscale rules; reserved capacity billed | Yes | Yes | None while instances run (Always On) |
| Azure Container Apps | Container Apps scaling | Yes | No | Depends on minimum replicas |
| Consumption (legacy) | Event-driven, scale to zero | No | Yes (Windows only) | Yes |
Flex Consumption scale settings
- Per-function scaling. All HTTP (and SignalR) triggers share the
httpgroup, Event Grid-based blob triggers theblobgroup and Durable triggers thedurablegroup; every other function, such as a Service Bus trigger, scales on its own instances (function:<name>). A bursty background function therefore doesn't take capacity from the API — no app split needed. - Always ready instances are set per group or per function (for example 2 for
function:resize_images), which removes cold starts for that group only and bills a baseline only for it. Prewarmed instances are a Premium concept. - Maximum instance count limits on-demand instances. Always ready instances don't count toward it, and it applies to each independently scaling group, not the app as a whole — so a maximum of 20 with 3 always ready for
httpcan show 23 HTTP instances plus up to 20 for a separately scaling function. - Instance memory (512, 2,048 or 4,096 MB) sets memory and CPU per instance; it doesn't change HTTP time limits.
Long-running HTTP work
functionTimeout in host.json limits how long an execution may run (default 30 minutes and unbounded maximum on Flex Consumption, Premium, Dedicated with Always On, and Container Apps; 5 and 10 minutes on legacy Consumption). Separately, an HTTP-triggered function must respond within 230 seconds on every plan because of the load balancer's idle timeout — the work continues, but the caller loses the response. Return quickly instead: the Durable Functions async HTTP API, or a queue message that a background function processes, with the result stored for the caller to fetch.
import azure.durable_functions as df
app = df.DFApp(http_auth_level=func.AuthLevel.FUNCTION)
@app.route(route="exports", methods=["POST"])
@app.durable_client_input(client_name="client")
async def start_export(req: func.HttpRequest, client):
instance_id = await client.start_new(
"export_orchestrator", None, req.get_json())
return client.create_check_status_response(req, instance_id)create_check_status_response returns 202 Accepted with statusQueryGetUri and the other management URLs; create_http_management_payload returns only the URLs as a dictionary.
How do you build, deploy and release a Python function app?
Deploy a Python function app with Core Tools (func azure functionapp publish <app>), the Azure CLI or a CI/CD pipeline; the decisions that matter are where dependencies are built, where the package lives and how releases reach production.
- Remote build. Python apps run on Linux, so packages with compiled extensions must be built for Linux. A remote build (the Core Tools default for Python, or
--build remote) installsrequirements.txtin Azure's Linux build environment. Zipping a Windows virtual environment ships the wrong binaries;--build localbuilds on your machine.PYTHON_ISOLATE_WORKER_DEPENDENCIESonly addresses clashes with the worker's own packages. - Run from package. With
WEBSITE_RUN_FROM_PACKAGE, the zip is mounted read-only as the app's content directory, including any folder inside it. Write scratch files to the temp directory (Python'stempfile); they don't persist across invocations or instances. - Flex Consumption has one deployment path: the package is uploaded to a blob container in the app's deployment storage (by default the
AzureWebJobsStorageaccount), and each instance loads it on startup. Azure Files content shares (WEBSITE_CONTENTSHARE) and Kudu file deployment belong to other plans.
Deployment slots (Premium and Dedicated)
- A slot is a live app with its own host name; swapping applies the target's settings to the source slot, warms its instances, then switches routing, so production traffic sees no cold start. The command is
az functionapp deployment slot swap --slot staging --target-slot production. - App settings move with the content on swap unless marked as deployment slot settings. Make environment-specific values (database endpoints, connection prefixes) sticky in both slots. Auto swap and swap with preview change when or how a swap happens, not which settings stay.
- Triggers run in every slot: a staging timer fires on its schedule, and the timer lock doesn't coordinate across slots. Disable it in staging with
AzureWebJobs.<FUNCTION_NAME>.Disabled=trueas a sticky slot setting, so it never moves to production. - Flex Consumption and Container Apps hosting have no deployment slots; if a release process requires swaps, choose Elastic Premium or Dedicated.
Tip. Questions on this topic are short Azure Functions scenarios with one deciding requirement, such as keeping one function app, keeping the Flex Consumption plan, no keys in settings, least privilege, no retry loop in code, or a release process built on slot swaps. Many show a Python v2 snippet (route decorators, func.Out, Cosmos DB input bindings, Durable client calls) or a host.json fragment and ask which value completes it; others ask which hosting plan, app setting, key or CLI command meets every stated constraint. Expect near-twin choices: route_params versus params, host versus function keys, retry policies versus built-in trigger retries, __serviceUri versus separate blob and queue URIs, and always ready versus prewarmed instances.
- Python v2 apps have one func.FunctionApp in function_app.py; split code with func.Blueprint and app.register_functions. A failed top-level import leaves the app with no functions.
- Routes are relative to routePrefix (api by default, set in host.json); read path values from req.route_params; with an empty prefix, routes whose first segment is admin or runtime collide with the host's reserved paths.
- FUNCTION level accepts a function key or any host key; ADMIN needs the master key; system keys are for extension webhooks. Keys aren't enforced locally. Use App Service Authentication for user sign-in and platform CORS for browsers.
- Output bindings write after the function returns, so their errors can't be caught; use a module-scope SDK client when you must handle failures.
- Timer schedules are six-field NCRONTAB in UTC; Flex Consumption doesn't support WEBSITE_TIME_ZONE or TZ. One instance runs each occurrence, but every slot runs its own.
- Storage queue messages move to <queue>-poison after maxDequeueCount (default 5) attempts; concurrency per instance is batchSize + newBatchThreshold.
- Retry policies cover only Timer, Cosmos DB, Event Hubs and Kafka; Service Bus uses its max delivery count and dead-letter queue.
- Identity connections use <prefix>__fullyQualifiedNamespace, __serviceUri (or __blobServiceUri + __queueServiceUri for blob triggers), __accountEndpoint, or AzureWebJobsStorage__accountName, with data-plane roles.
- Flex Consumption: per-function scale groups, always ready per group outside the maximum instance count, Event Grid-only blob triggers, blob-container packages and no deployment slots.
- HTTP responses must return within 230 seconds on every plan whatever functionTimeout says; use the Durable async HTTP API or a queue for long work. Mark environment-specific settings as slot settings.
Frequently asked questions
Why does my Azure Functions HTTP endpoint work locally but return 401 in Azure?
Azure Functions Core Tools doesn't enforce authorization levels when you run func start, so a function at the function level accepts calls without a key on your machine. In Azure the level is enforced, so the caller must send a function key or host key in the x-functions-key header or the code query parameter.
What is the difference between a function key, a host key and the master key?
A function key works only for the function it belongs to. A host key works for every function in the app that uses the function authorization level, including functions added later. The master key is a host key that also satisfies the admin level and unlocks the host's admin APIs, so it should rarely be handed out. System keys are separate keys that extensions use for their own webhook endpoints.
Which Azure Functions triggers support retry policies?
Runtime retry policies (fixed delay or exponential backoff) apply to the Timer, Azure Cosmos DB, Event Hubs and Kafka triggers. Storage queue triggers retry through maxDequeueCount and a poison queue, Service Bus triggers through the entity's max delivery count and dead-letter queue, and HTTP triggers rely on the caller to retry.
Can I set a time zone for a timer trigger on the Flex Consumption plan?
No. Timer schedules run in UTC by default, and the WEBSITE_TIME_ZONE and TZ settings that change this on Premium and Dedicated plans aren't supported on Flex Consumption. Convert the local time to UTC in the six-field NCRONTAB expression instead, allowing for daylight saving changes where the region observes them.
Why doesn't my blob trigger fire after moving to Flex Consumption?
Flex Consumption supports only the Event Grid source for blob triggers, not the default polling source. Set source="EventGrid" on the blob trigger and create an Event Grid subscription on the storage account that sends Blob Created events, as a Web Hook, to the app's /runtime/webhooks/blobs endpoint with the blobs_extension system key.
How do I connect an Azure Functions trigger to Service Bus without a connection string?
Replace the connection string setting with <prefix>__fullyQualifiedNamespace set to the namespace host name, such as contoso-bus.servicebus.windows.net, and grant the function app's managed identity the Azure Service Bus Data Receiver role. For a user-assigned identity, also set <prefix>__credential to managedidentity and <prefix>__clientId to its client ID.
Why does an HTTP function time out after about four minutes even though functionTimeout is longer?
functionTimeout limits how long the execution runs, but an HTTP-triggered function must send its response within 230 seconds on every hosting plan because of the Azure load balancer's idle timeout. For longer work, return 202 Accepted immediately and finish in the background, for example with the Durable Functions async HTTP API or a queue-triggered function.
Does the Flex Consumption plan support deployment slots?
No. Flex Consumption has no deployment slots and uses rolling updates (currently in public preview) instead, and Azure Container Apps hosting has none either. If a release process depends on swapping a staging slot into production, host the app on an Elastic Premium or Dedicated (App Service) plan.
Source
This lesson covers the "Connect to and consume Azure services" domain of the official AI-200 exam guide. Vendors revise their guides — check the source for the current version.
- Microsoft AI-200 study guide — Microsoft Learn
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.