RabbitMQ Exchange Types Explained

February 20, 20264 min readRabbitMQ tutorial

RabbitMQ Exchange Types Explained

What is an exchange in RabbitMQ?

An exchange is the routing layer in RabbitMQ. Producers never send messages directly to queues. Instead, they publish messages to an exchange, which then routes them to one or more queues based on rules called bindings and routing keys.

The flow looks like this:

Producer → Exchange → Binding (routing key) → Queue → Consumer

Every exchange has a type that determines how it evaluates routing keys and delivers messages. RabbitMQ ships with four built-in exchange types: direct, fanout, topic, and headers.

Direct exchange

A direct exchange routes messages to queues whose binding key exactly matches the message's routing key. This is the simplest and most common exchange type.

Direct exchange schema in rabbitmq

In this example, the queues have the same name as the routing key, but this is not mandatory. The important part is that the binding key of the queue matches exactly the routing key of the message for it to be delivered.

If a message is published with a routing key that doesn't match any binding key, it gets dropped (or dead-lettered if the exchange has a dead-letter exchange configured).

await channel.assertExchange("orders", "direct");
 
await channel.assertQueue("order.created");
await channel.bindQueue("order.created", "orders", "order.created");
 
await channel.assertQueue("order.cancelled");
await channel.bindQueue("order.cancelled", "orders", "order.cancelled");
 
channel.publish("orders", "order.created", Buffer.from("new order"));

In this example, only the order.created queue receives the message because the routing key matches exactly.

When to use direct exchanges

  • Point-to-point messaging where each message type goes to a specific queue
  • Task distribution across workers consuming from the same queue
  • Any scenario where you need exact routing key matching

The default exchange

RabbitMQ has a special unnamed direct exchange (empty string "") that every queue is automatically bound to using the queue name as the routing key. This is why sendToQueue works without declaring an exchange:

channel.sendToQueue("my_queue", Buffer.from("hello"));
// equivalent to:
channel.publish("", "my_queue", Buffer.from("hello"));

Fanout exchange

A fanout exchange broadcasts every message to all bound queues, regardless of routing keys. Routing keys are completely ignored.

Fanout exchange schema in rabbitmq
await channel.assertExchange("notifications", "fanout");
 
await channel.assertQueue("email_notifications");
await channel.bindQueue("email_notifications", "notifications", "");
 
await channel.assertQueue("push_notifications");
await channel.bindQueue("push_notifications", "notifications", "");
 
channel.publish("notifications", "", Buffer.from("new notification"));

Both email_notifications and push_notifications receive the message.

When to use fanout exchanges

  • Broadcast events to all consumers (e.g., cache invalidation, notifications)
  • Publish/subscribe patterns where every subscriber gets every message
  • Logging pipelines where every message should be duplicated to multiple destinations

Topic exchange

A topic exchange routes messages based on wildcard pattern matching against the routing key. Routing keys must be dot-delimited words (e.g., order.created.eu).

Two wildcard characters are available in binding keys:

  • * matches exactly one word
  • # matches zero or more words
Topic exchange schema in rabbitmq
await channel.assertExchange("events", "topic");
 
await channel.assertQueue("all_order_events");
await channel.bindQueue("all_order_events", "events", "order.#");
 
await channel.assertQueue("eu_events");
await channel.bindQueue("eu_events", "events", "*.*.eu");
 
await channel.assertQueue("created_events");
await channel.bindQueue("created_events", "events", "*.created.*");
 
channel.publish("events", "order.created.eu", Buffer.from("EU order"));

In this example, all three queues receive the message because:

  • order.# matches any key starting with order.
  • *.*.eu matches any three-word key ending in eu
  • *.created.* matches any three-word key with created in the middle

When to use topic exchanges

  • Event-driven systems where consumers subscribe to subsets of events
  • Geographical or category-based routing (e.g., logs.error.eu, logs.info.us)
  • Any pattern where you need flexible, hierarchical routing

Topic exchange edge cases

  • A binding key of # makes a topic exchange behave like a fanout exchange (matches everything)
  • A binding key with no wildcards makes it behave like a direct exchange (exact match only)

Headers exchange

A headers exchange routes messages based on message header attributes instead of routing keys. The routing key is completely ignored.

When binding a queue to a headers exchange, you specify a set of key-value pairs and a matching mode:

  • x-match: all — the message must contain all specified headers with matching values
  • x-match: any — the message must contain at least one matching header
await channel.assertExchange("imports", "headers");
 
await channel.assertQueue("csv_imports");
await channel.bindQueue("csv_imports", "imports", "", {
  "x-match": "all",
  format: "csv",
  source: "upload",
});
 
await channel.assertQueue("any_upload");
await channel.bindQueue("any_upload", "imports", "", {
  "x-match": "any",
  source: "upload",
  source: "api",
});
 
channel.publish("imports", "", Buffer.from("data"), {
  headers: { format: "csv", source: "upload" },
});

Both queues receive the message: csv_imports because both headers match, and any_upload because source: "upload" matches.

When to use headers exchanges

  • Routing based on metadata that doesn't fit a dot-delimited key structure
  • Multi-attribute filtering (e.g., content type + priority + region)
  • Scenarios where routing logic is complex and spans multiple dimensions

Headers exchanges are the least commonly used type due to their added complexity and slightly lower performance compared to topic exchanges.

Choosing the right exchange type

ExchangeRouting logicUse case
DirectExact key matchTask queues, command routing
FanoutBroadcast to allNotifications, cache invalidation
TopicWildcard pattern matchEvent systems, hierarchical routing
HeadersHeader attribute matchMulti-dimensional filtering

A good rule of thumb: start with direct for simple routing, move to topic when you need pattern-based flexibility, use fanout for broadcast scenarios, and reserve headers for edge cases where key-based routing falls short.

Inspecting exchanges in production

Understanding how messages flow through your exchanges is critical for debugging routing issues. We wrote en entire article dedicated to this topic: How to inspect RabbitMQ exchanges in production. It covers how to use RabbitGUI to visualize your exchanges, bindings, and message flow in real-time.

Read more RabbitMQ tutorials

RabbitMQ Delayed MessagesRabbitMQ tutorialRabbitMQ Delayed MessagesLearn how to implement delayed messages in RabbitMQ using the delayed message exchange plugin and the message TTL with dead-letter queue pattern.RabbitMQ Monitoring APIRabbitMQ tutorialRabbitMQ Monitoring APIComplete documentation on how to monitor RabbitMQ using its HTTP monitoring API with detailed explanations of available metrics and examples.RabbitMQ Message Acknowledgment ExplainedRabbitMQ tutorialRabbitMQ Message Acknowledgment ExplainedUnderstand how message acknowledgments work in RabbitMQ. Covers automatic and manual acks, nack, reject, prefetch, and common pitfalls to avoid.

RabbitGUI, the missing RabbitMQ IDE

Debug, monitor, and manage RabbitMQ with a modern developer interface.

Try nowRabbitGUI screenshot

More articles about RabbitMQ

RabbitMQ Javascript Cheat-SheetCheat sheetRabbitMQ Javascript Cheat-SheetEverything you need to know to get started with RabbitMQ in NodeJs and Docker with code examples ready to go.How to log into your CloudAMQP RabbitMQ instanceProductHow to log into your CloudAMQP RabbitMQ instanceUse RabbitGUI to connect to your CloudAMQP instance and manage your dead letter queues with easeHow security is built into RabbitGUIProductHow security is built into RabbitGUIRabbitGUI was built with security as a top priority for its users, and here is how it was done!