The real magic of output bindings in Azure Functions isn’t about sending data, it’s about how the Functions runtime abstracts away the act of writing entirely.

Let’s see it in action. Imagine we have a function that needs to take some incoming HTTP data and write it to Azure Queue Storage and also to a Cosmos DB document.

{
  "scriptFile": "run.py",
  "entryPoint": "main",
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "outputQueueItem",
      "queueName": "my-output-queue",
      "connection": "AzureWebJobsStorage"
    },
    {
      "type": "cosmosDB",
      "direction": "out",
      "name": "outputDocument",
      "databaseName": "myDatabase",
      "collectionName": "myCollection",
      "createIfNotExists": true,
      "connection": "AzureWebJobsStorage"
    }
  ]
}

Here’s the Python code that uses these bindings:

import logging
import json

def main(req, outputQueueItem: str, outputDocument: object):
    logging.info('Python HTTP trigger function processed a request.')

    try:
        req_body = req.get_json()
    except ValueError:
        return {
            "status": 400,
            "body": "Please pass a valid JSON object in the request body"
        }

    if req_body:
        # Data to be sent to the queue
        queue_message = json.dumps(req_body)
        outputQueueItem = queue_message # Assigning to the output binding variable

        # Data to be sent to Cosmos DB
        # We'll add an ID and a timestamp for uniqueness
        cosmos_doc = req_body.copy()
        cosmos_doc["id"] = cosmos_doc.get("id", str(uuid.uuid4())) # Use provided ID or generate one
        cosmos_doc["timestamp"] = datetime.datetime.utcnow().isoformat() + "Z"
        outputDocument = cosmos_doc # Assigning to the output binding variable

        return {
            "status": 200,
            "body": "Data processed and sent to queue and Cosmos DB."
        }
    else:
        return {
            "status": 400,
            "body": "Please pass a JSON object in the request body"
        }

# Need to import these for the Cosmos DB part
import uuid
import datetime

When you send a POST request to this function with a JSON body like {"name": "Alice", "message": "Hello, world!"}, the Functions runtime handles the heavy lifting. You don’t import azure.storage.queue or import azure.cosmos. You simply assign the data you want to write to the variable name defined in your function.json for that output binding.

The runtime intercepts these assignments. For outputQueueItem, it takes the assigned string, connects to the Azure Storage account specified by AzureWebJobsStorage, and adds a message to the my-output-queue. For outputDocument, it connects to the specified Cosmos DB account, finds (or creates) myDatabase and myCollection, and inserts the assigned JSON object as a document. The id field is crucial for Cosmos DB, and the runtime will use it to upsert (update or insert) the document. If an id isn’t provided in your input JSON, we generate one to ensure uniqueness.

The problem output bindings solve is the boilerplate code required to interact with Azure services. Instead of managing client SDKs, connection strings, retry logic, and serialization for each service individually, you declare your intent in function.json and then assign data in your code. The runtime’s job is to translate those assignments into the necessary API calls. This dramatically simplifies your function code, making it more readable and maintainable, and allows you to focus on your business logic.

One thing most people don’t realize is that output bindings are not limited to simple data types. For the Cosmos DB binding, you can assign complex Python dictionaries or lists of dictionaries, and the runtime will serialize them correctly. The id field is automatically handled for Cosmos DB documents; if you provide one, it’s used; if not, and if the binding configuration supports it (like createIfNotExists: true), the runtime might generate one or expect you to provide it. However, for custom types or more advanced scenarios, you can explicitly serialize to JSON strings before assigning if needed, though it’s often unnecessary for common types.

The next hurdle you’ll likely encounter is handling multiple output bindings of the same type within a single function execution.

Want structured learning?

Take the full Azure-functions course →