RabbitMQ exchange types explained: direct vs topic vs fanout vs headers

February 20, 20268 min readRabbitMQ tutorial

RabbitMQ exchange types explained: direct vs topic vs fanout vs headers

Introduction

RabbitMQ has four built-in exchange types: direct, fanout, topic, and headers. Each uses different routing rules to decide which queues receive a message.

This guide covers what each exchange type does, how routing keys and wildcards work, and how to pick the right one, with animated diagrams and runnable examples.

RabbitMQ exchange types at a glance

ExchangeRouting logicRouting keyUse case
DirectExact key matchExact matchTask queues, command routing
FanoutBroadcast to allIgnoredNotifications, cache invalidation
TopicWildcard pattern matchPattern matchEvent systems, hierarchical routing
HeadersHeader attribute matchIgnoredMulti-dimensional filtering

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 → Queue → Consumer

More precisely, the binding sits between the exchange and the queue:

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. Exchanges, queues, and bindings are all part of the AMQP protocol that RabbitMQ implements.

RabbitMQ exchange vs queue

An exchange receives messages from producers and decides where to route them. A queue stores messages until they are consumed.

ExchangeQueue
Receives messages from producersUsually no
Routes messages
Stores messages
Delivers to consumers
Uses routing rules

Put together:

Producer → Exchange → Queue → Consumer

An exchange holds nothing. If no queue is bound to it with a matching binding, a published message is simply dropped. A queue, on the other hand, keeps messages until a consumer acknowledges them, see message acknowledgment in RabbitMQ.

A queue can receive messages directly in one case only: the default exchange, which makes sendToQueue look like publishing straight to a queue.

What is a routing key in RabbitMQ?

A routing key is a string attached to a message when it is published to an exchange. Depending on the exchange type, RabbitMQ uses the routing key to determine which queues should receive the message.

The routing key is set by the producer at publish time. Each binding also has a binding key, and the exchange type decides how the two are compared:

ExchangeRouting key behavior
DirectExact match
FanoutIgnored
TopicWildcard pattern matching
HeadersIgnored

By convention, routing keys are dot-delimited words such as order.created.eu. This is mandatory for topic exchanges and merely a good habit everywhere else.

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.

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, see setting up dead-letter queues).

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 multiple queues are bound to the same exchange with the same routing key, the message is delivered to all matching queues. This allows for multiple consumers processing the same type of message.

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

Fanout exchange

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

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

RabbitMQ topic exchange: wildcards, routing keys and examples

A RabbitMQ topic exchange routes messages using wildcard patterns in routing keys. Use * to match exactly one word and # to match zero or more words.

Routing keys must be dot-delimited words (e.g., order.created.eu), and the wildcards live in the binding key, not in the routing key of the published message.

RabbitMQ routing key wildcards: * vs #

WildcardMeaningExample
*Exactly one wordorder.*.created
#Zero or more wordsorder.#

Concrete examples, assuming three-word routing keys like order.created.eu:

  • order.# matches order.created.eu, order.canceled.fr, and order itself, but not user.created.eu. # also matches zero words.
  • *.*.eu matches order.created.eu and user.created.eu, but not order.eu (two words) or order.created.eu.b2b (four words). Each * must consume exactly one word.
  • *.created.* matches order.created.eu and user.created.ca, but not order.created (missing the third word) or user.deleted.ca.

Topic exchange example

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

A message that matches several bindings is delivered once per matching queue, never twice to the same queue.

When to use topic exchanges

  • Event-driven systems where consumers subscribe to subsets of events, the foundation of an event bus built on RabbitMQ
  • 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.

What is the RabbitMQ default exchange?

RabbitMQ's default exchange is an unnamed direct exchange represented by an empty string (""). Every queue is automatically bound to it using its 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"));

Two things to know about it:

  • You cannot bind or unbind queues on the default exchange yourself. RabbitMQ manages those bindings, and it cannot be deleted.
  • It is convenient for quick tests and simple worker queues, but it couples producers to queue names. Any real routing topology should use a named exchange, so consumers can change without touching publishers.

If you just want to drop a message on a queue by hand, see how to manually publish messages to RabbitMQ.

How to choose the right RabbitMQ exchange type

If you need...Use
Exact routingDirect
Broadcast to everyoneFanout
Flexible patternsTopic
Header-based filteringHeaders

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. More guidance on topology design in our RabbitMQ best practices.

RabbitMQ exchange types FAQ

What are the 4 exchange types in RabbitMQ?

Direct, fanout, topic, and headers. Direct matches the routing key exactly, fanout ignores it and broadcasts to every bound queue, topic matches wildcard patterns, and headers routes on message header attributes.

What is the difference between an exchange and a queue?

An exchange receives messages from producers and routes them using bindings. A queue stores messages until a consumer reads them. Exchanges never store anything, queues never route anything.

What is a topic exchange in RabbitMQ?

A topic exchange routes messages by matching dot-delimited routing keys against wildcard binding patterns, where * matches exactly one word and # matches zero or more words.

What is the default exchange?

The default exchange is an unnamed direct exchange ("") to which every queue is automatically bound by its own name. Publishing to "" with a routing key equal to a queue name delivers straight to that queue.

What is a routing key?

A routing key is a string attached to a message at publish time. Direct exchanges match it exactly, topic exchanges match it against wildcard patterns, and fanout and headers exchanges ignore it.

Which exchange type uses exact routing key matches?

A direct exchange. It delivers a message only to queues whose binding key is exactly equal to the message's routing key.

Can a queue be bound to several exchanges?

Yes. A queue can have any number of bindings, on the same exchange or across different exchanges and different exchange types. Each matching binding delivers a copy, but the same queue never receives the same message twice from one publish.

Inspecting exchanges in production

Understanding how messages flow through your exchanges is critical for debugging routing issues. We wrote an 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. To watch live traffic without consuming the main queue, see how to spy on real-time queue traffic, or browse messages without consuming them.

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 ACK vs NACK: acknowledgements, requeue & reject explainedRabbitMQ tutorialRabbitMQ ACK vs NACK: acknowledgements, requeue & reject explainedLearn how RabbitMQ ACK, NACK and reject work. Understand manual vs auto acknowledgements, requeue=true, redelivery, prefetch and how to avoid infinite requeue loops.

RabbitGUI, the missing RabbitMQ IDE

Debug, monitor, and manage RabbitMQ with a modern developer interface.
Available on Windows, Mac, and Linux.

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!