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.
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).
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.
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"));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.
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
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 middlelogs.error.eu, logs.info.us)# makes a topic exchange behave like a fanout exchange (matches everything)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 valuesx-match: any — the message must contain at least one matching headerawait 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.
Headers exchanges are the least commonly used type due to their added complexity and slightly lower performance compared to topic exchanges.
| Exchange | Routing logic | Use case |
|---|---|---|
| Direct | Exact key match | Task queues, command routing |
| Fanout | Broadcast to all | Notifications, cache invalidation |
| Topic | Wildcard pattern match | Event systems, hierarchical routing |
| Headers | Header attribute match | Multi-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.
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.
RabbitMQ 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 tutorialRabbitMQ Monitoring APIComplete documentation on how to monitor RabbitMQ using its HTTP monitoring API with detailed explanations of available metrics and examples.
RabbitMQ tutorialRabbitMQ Message Acknowledgment ExplainedUnderstand how message acknowledgments work in RabbitMQ. Covers automatic and manual acks, nack, reject, prefetch, and common pitfalls to avoid.Debug, monitor, and manage RabbitMQ with a modern developer interface.
Try now
Cheat sheetRabbitMQ Javascript Cheat-SheetEverything you need to know to get started with RabbitMQ in NodeJs and Docker with code examples ready to go.
ProductHow to log into your CloudAMQP RabbitMQ instanceUse RabbitGUI to connect to your CloudAMQP instance and manage your dead letter queues with ease
ProductHow security is built into RabbitGUIRabbitGUI was built with security as a top priority for its users, and here is how it was done!