Skip to content

Cross-Project Configuration

FastPubSub can subscribe to topics and publish messages across different Google Cloud projects. This enables architectures where services in one project consume events from another project, or where a shared event bus spans multiple projects.

Why Use Cross-Project Messaging?

Cross-project messaging is useful when:

  • Microservices across projects - Different teams own different projects but need to communicate
  • Shared event bus - A central project hosts common events consumed by multiple projects
  • Data pipelines - Data flows from source projects to processing projects
  • Multi-environment setups - Production services consume events from staging for testing

IAM Permissions Required

Cross-project access requires proper IAM permissions. The service account running your application must have Pub/Sub permissions in the target project.

Cross-Project Subscribers

Override the broker's default project on individual subscribers using the project_id parameter:

# This subscriber uses the default project (project-a)
@broker.subscriber(
    alias="local-handler",
    topic_name="local-events",
    subscription_name="local-events-subscription",
)
async def handle_local_events(message: Message):
    await process_local_event(message.data)


# This subscriber uses a different project (project-b)
@broker.subscriber(
    alias="cross-project-handler",
    topic_name="shared-events",
    subscription_name="project-a-subscription",
    project_id="project-b",
    autocreate=True,
)
async def handle_cross_project_events(message: Message):
    await process_shared_event(message.data)
  1. Default project for all subscribers
  2. Override for this specific subscriber

Subscription Naming

When subscribing across projects, name your subscription to indicate which project owns it (e.g., project-a-subscription). This helps identify which service is consuming events.

Cross-Project Publishers

Create publishers that send messages to topics in different projects:

# Publisher for the default project
local_publisher = broker.publisher("local-events")

# Publisher for a different project
cross_project_publisher = broker.publisher("shared-events", project_id="project-b")


@app.post("/send-event")
async def send_event(data: dict):
    # Publish to local project
    await local_publisher.publish(data)

    # Publish to other project
    await cross_project_publisher.publish(data)
  1. Target topic is in project-b

You can also publish directly with the broker:

# Publish to default project
await broker.publish("local-events", {"event": "local"})

# Publish to specific project
await broker.publish(
    "shared-events",
    {"event": "cross-project"},
    project_id="project-b"
)

Step-by-Step

  1. Decide which project owns each topic and subscription.
  2. Grant IAM permissions across projects.
  3. Override project_id on subscribers or publishers as needed.
  4. Verify access with a test publish/consume.

Router-Level Cross-Project

Use routers to organize subscribers by project. All subscribers in the router inherit the router's project:

# Router for external project
external_router = PubSubRouter(prefix="external", project_id="project-b")


@external_router.subscriber(
    alias="shared-handler",
    topic_name="shared-events",
    subscription_name="project-a-subscription",
)
async def handle_shared(message: Message):
    await process_shared_event(message.data)


@external_router.subscriber(
    alias="analytics-handler",
    topic_name="analytics-events",
    subscription_name="project-a-analytics-subscription",
)
async def handle_analytics(message: Message):
    await process_analytics(message.data)


# Include the router in the broker
broker.include_router(external_router)
  1. All subscribers in this router use project-b
  2. Full alias becomes "external.shared-handler"

Nested Routers

Routers can be nested, with each level potentially overriding the project:

# Create a separate broker for nested router demo
nested_broker = PubSubBroker(project_id="project-a")

# First level router - uses project-b
level1_router = PubSubRouter(prefix="external", project_id="project-b")

# Second level router - uses project-c
level2_router = PubSubRouter(prefix="analytics", project_id="project-c")


# Subscriber uses project-c (inherited from level2)
@level2_router.subscriber(
    alias="handler",
    topic_name="metrics",
    subscription_name="metrics-subscription",
)
async def handle_metrics(message: Message):
    pass


level1_router.include_router(level2_router)
nested_broker.include_router(level1_router)

Complete Example

A service that consumes events from multiple projects:

# Complete example: service consuming from multiple projects
complete_broker = PubSubBroker(project_id="my-service")
complete_app = FastPubSub(complete_broker)


# Local events
@complete_broker.subscriber(
    alias="local-orders",
    topic_name="orders",
    subscription_name="orders-subscription",
)
async def handle_local_orders(message: Message):
    await process_order(message.data)


# Events from shared platform
platform_router = PubSubRouter(prefix="platform", project_id="shared-platform")


@platform_router.subscriber(
    alias="user-events",
    topic_name="user-events",
    subscription_name="my-service-user-subscription",
)
async def handle_user_events(message: Message):
    await sync_user_data(message.data)


@platform_router.subscriber(
    alias="notifications",
    topic_name="notifications",
    subscription_name="my-service-notifications-subscription",
)
async def handle_notifications(message: Message):
    await send_notification(message.data)


complete_broker.include_router(platform_router)


# Publishing to both projects
@complete_app.post("/create-order")
async def create_order(order: dict):
    # Local publish
    await complete_broker.publish("orders", order)

    # Notify platform
    await complete_broker.publish(
        "order-events",
        {"order_id": order["id"], "action": "created"},
        project_id="shared-platform",
    )
See cross-project examples

Check out the complete examples:

IAM Configuration

For cross-project access to work, configure IAM permissions:

Subscribing to Another Project's Topic

Grant your service account the roles/pubsub.subscriber role in the target project:

# Grant subscription permissions in project-b to service account from project-a
gcloud projects add-iam-policy-binding project-b \
    --member="serviceAccount:my-service@project-a.iam.gserviceaccount.com" \
    --role="roles/pubsub.subscriber"

Publishing to Another Project's Topic

Grant your service account the roles/pubsub.publisher role:

# Grant publish permissions in project-b to service account from project-a
gcloud projects add-iam-policy-binding project-b \
    --member="serviceAccount:my-service@project-a.iam.gserviceaccount.com" \
    --role="roles/pubsub.publisher"

Principle of Least Privilege

Grant only the permissions needed. Use topic-level or subscription-level IAM bindings instead of project-level when possible.

Best Practices

Use Descriptive Subscription Names

Include the consuming project in subscription names (e.g., project-a-orders-subscription) to easily identify which services are consuming which topics.

Document Cross-Project Dependencies

Maintain documentation of which services depend on which cross-project topics. This helps during incident response and migration planning.

Test IAM Permissions

Before deploying, verify your service account has the necessary permissions in all target projects using gcloud pubsub topics list or similar commands.

Use Service Accounts

Always use service accounts for cross-project access, not user credentials. This ensures consistent permissions and better security auditing.


Common Pitfalls

  • Missing IAM permissions in the target project.
  • Using ambiguous subscription names across projects.
  • Forgetting to override project_id on cross-project publishers.

Recap

  • Cross-project subscribers use project_id parameter to override the broker's default project
  • Cross-project publishers can target topics in any project with proper permissions
  • Routers simplify cross-project organization by setting project at the router level
  • IAM permissions must be configured in the target project for cross-project access
  • Use descriptive naming to identify which services own which subscriptions