You can actually trigger a single Cloud Function from multiple event sources, which is a neat trick for consolidating logic.
Let’s say you have a function that needs to react to a new file being uploaded to a specific Cloud Storage bucket, but also to a new message arriving on a Pub/Sub topic. Instead of writing two separate functions, you can configure one function to listen to both.
Here’s what that looks like in main.py (or your language’s equivalent):
import functions_framework
from cloudevents.http import CloudEvent, from_http
import base64
@functions_framework.cloud_event
def handle_multiple_events(cloud_event: CloudEvent):
"""
Triggered by a Cloud Storage event or a Pub/Sub message.
"""
event_type = cloud_event["type"]
data = cloud_event.data
if event_type.startswith("google.cloud.storage.object."):
# This is a Cloud Storage event
bucket = data["bucket"]
name = data["name"]
print(f"Received Cloud Storage event for file: {name} in bucket: {bucket}")
# Your Cloud Storage specific logic here
if name.endswith(".csv"):
print("Processing CSV file...")
# Example: Download and process CSV content
# storage_client = storage.Client()
# bucket_obj = storage_client.bucket(bucket)
# blob = bucket_obj.blob(name)
# content = blob.download_as_bytes()
# print(f"CSV content length: {len(content)}")
elif event_type.startswith("google.cloud.pubsub.topic.v1.message"):
# This is a Pub/Sub event
if "message" in data:
message_data = data["message"]["data"]
decoded_data = base64.b64decode(message_data).decode('utf-8')
print(f"Received Pub/Sub message: {decoded_data}")
# Your Pub/Sub specific logic here
if "urgent" in decoded_data.lower():
print("This is an urgent message!")
else:
print("Received Pub/Sub event with no message data.")
else:
print(f"Received unhandled event type: {event_type}")
The key here is that the functions_framework decorator, when used with @functions_framework.cloud_event, automatically handles the CloudEvents payload structure. Your code then inspects the type field within the cloud_event object to differentiate between the incoming event sources.
When you deploy this function, you don’t just specify one trigger. You define multiple triggers in your gcloud command or deploy.yaml file.
Here’s how you’d deploy it using gcloud with two triggers:
gcloud functions deploy my-multi-trigger-function \
--runtime python311 \
--trigger-resource my-input-bucket \
--trigger-event google.storage.object.finalize \
--set-env-vars PUBSUB_TOPIC=my-notification-topic \
--entry-point handle_multiple_events \
--region us-central1
gcloud functions add-trigger my-multi-trigger-function \
--trigger-resource my-notification-topic \
--trigger-event google.cloud.pubsub.topic.v1.messagePublished \
--region us-central1
Notice how we first deploy with the Cloud Storage trigger, and then use gcloud functions add-trigger to layer on the Pub/Sub trigger. This is because a function can only have one initial trigger defined during creation; subsequent triggers are added. The entry-point (handle_multiple_events in this case) is the same for all triggers.
The system handles this by creating separate event subscriptions or event bindings behind the scenes for each trigger you define. When an event occurs on any of the configured sources, the corresponding subscription/binding is activated, and the Cloud Function is invoked with a CloudEvent payload. The function’s code then inspects the type field of this payload to determine which source the event came from and execute the appropriate logic.
The most surprising thing about this is that the function code doesn’t need to know which trigger it was invoked by at deployment time. It receives a generic CloudEvent object, and the type field within that object is the universal discriminator. This means your function is decoupled from the specific deployment configuration of its triggers, making it more reusable.
The core problem this solves is reducing operational overhead and code duplication. Instead of managing and deploying multiple functions that might share a lot of common processing logic, you can consolidate that logic into a single function. This simplifies your architecture, reduces the number of resources you need to monitor, and makes updates easier – you change the logic in one place.
Internally, each trigger you configure creates a separate event-driven pathway to your function. For Cloud Storage, it’s an event notification that gets sent to a Pub/Sub topic managed by Cloud Functions. For Pub/Sub, it’s a direct subscription to the specified topic. Both of these eventually result in an invocation of your function’s entry point with a standardized CloudEvent payload.
When you inspect your function in the Google Cloud Console, you’ll see a list of all configured triggers associated with that single function resource.
The one thing most people don’t realize is that the google.cloud.pubsub.topic.v1.messagePublished event type is the only Pub/Sub trigger available for Cloud Functions. You can’t specify a specific topic name in the trigger definition itself in the same way you do for Cloud Storage. Instead, you configure the topic name as an environment variable (PUBSUB_TOPIC in the example) and your code reads that variable to know which topic it should be listening to, or more accurately, how to interpret the incoming Pub/Sub event that the function is triggered by. The gcloud command associates the trigger with a specific topic, but the event type is generic.
This pattern is incredibly powerful for building event-driven systems where a single action might need to kick off multiple, distinct workflows based on different event sources.
The next concept to explore is how to handle event filtering at the trigger level, rather than solely within your function’s code.