Azure Functions can react to changes in Cosmos DB, but the mechanism isn’t a direct "event" in the traditional sense; it’s a polling loop that checks for updates.

Let’s see it in action. Imagine you have a products container in Cosmos DB. When a new product is added or an existing one is updated, we want to trigger a function to, say, re-index it in a search service.

Here’s a typical Azure Function trigger for Cosmos DB:

{
  "scriptFile": "__init__.py",
  "entryPoint": "cosmosdb_trigger_handler",
  "bindings": [
    {
      "type": "cosmosDBTrigger",
      "name": "documents",
      "direction": "in",
      "databaseName": "mydatabase",
      "collectionName": "products",
      "connectionStringSetting": "CosmosDBConnection",
      "createLeaseCollectionIfNotExists": true
    },
    {
      "type": "blob",
      "name": "outputBlob",
      "path": "processed/{rand-guid}.txt",
      "direction": "out",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

And the Python function:

import logging

def cosmosdb_trigger_handler(documents: list, outputBlob: str):
    if documents:
        logging.info(f"Processing {len(documents)} documents.")
        for doc in documents:
            logging.info(f"Found document: {doc.get('id')}")
            # Simulate some processing
            processed_data = f"Processed: {doc.get('id')} - {doc.get('name')}"
            outputBlob = processed_data # This will write to the blob
            logging.info(f"Successfully processed document: {doc.get('id')}")
    else:
        logging.info("No documents found to process.")

When you deploy this, the Function Host starts a background process. This process periodically queries the Cosmos DB container for new or modified documents since its last check. It does this by maintaining a "lease" document in a separate, special collection (which createLeaseCollectionIfNotExists helps manage). This lease stores the continuation token for the change feed. The function trigger essentially reads documents from the change feed, processes them, and then updates the lease with the new continuation token.

The core problem this solves is enabling serverless, event-driven processing of data changes in Cosmos DB without constant polling from your application code. It decouples data modification from subsequent actions. You don’t need to write code to constantly ask "has anything changed?"; the Function Host handles that for you. The "documents" parameter in the function receives a list of changed documents. The connectionStringSetting points to the Cosmos DB connection details, and the collectionName specifies the container to monitor.

The createLeaseCollectionIfNotExists flag is crucial. If you don’t have a dedicated lease collection (usually named leases by default), the trigger will create one. This collection stores the state of the change feed processing, ensuring that documents are processed exactly once and that processing can resume from where it left off if the function app restarts.

The change feed itself is a persistent, ordered log of all modifications to documents within a Cosmos DB container. It’s not an "event" that gets pushed to a queue; it’s a stream that the Function Host pulls from. The trigger’s internal mechanism is a loop that checks the change feed for new entries. It uses a continuation token to track its position, and this token is managed within the lease collection. This means that even if your function app is down for a while, when it comes back up, it can pick up processing right where it left off by using the last known continuation token from the lease.

A common misconception is that the change feed is a real-time event stream like Service Bus or Event Hubs. While it’s designed for near real-time processing, it’s fundamentally a polling mechanism. The frequency of these polls is determined by the Function Host’s internal timer and the configuration of the trigger binding, which includes settings like feedOptions (though these aren’t directly exposed in the basic trigger definition but can be influenced by the host’s behavior). The documents parameter you receive in your function is a batch of changes that have occurred since the last poll.

If you’re seeing your function trigger with empty documents lists, it’s usually not that nothing changed, but rather that the Function Host’s polling interval hasn’t picked up anything new yet, or the lease collection is out of sync.

The next concept you’ll likely grapple with is managing the CreateLeaseCollectionIfNotExists setting. While convenient, it’s often better practice in production to pre-create your lease collection manually with specific throughput and indexing policies tailored for lease operations, rather than letting the Function Host create it on the fly. This gives you more control over performance and cost.

Want structured learning?

Take the full Cosmos-db course →