Queues
Whilst building a webapp you want to be able to respond as quickly as possible to user interactions, but sometimes you need to do things that take a long time! For example, you might need to send an email when a user submits a form, or you need to process a payment, or do something magic with AI - and you don’t want the user to wait for these things to complete.
To handle these you’ll use background tasks. Background tasks are managed by the Cloudflare queue system. You send a message to a queue, where a worker will process the message, but on a different worker - so it doesn’t block the main one.
Setup
First thing you’ve got to do is create a queue and bind the queue producers and consumers to your worker.
npx wrangler queues create my-queue-name
Replace my-queue-name
with the name of your queue, and place the following in your wrangler.jsonc
file:
{ "queues": { "producers": [ { "binding": "QUEUE", "queue": "my-queue-name", } ], "consumers": [ { "queue": "my-queue-name", "max_batch_size": 10, "max_batch_timeout": 5 } ] }}
This will bind the queue to the env.QUEUE
object in the worker. So you’ll be able to send messages.
Naming Queues
Queue names must match the following RegEx pattern: ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$
Valid queue names
my-queue
my-awesome-queue-123
queue1
1queue
my-queue-v2
Invalid queue names
My_Queue
(uppercase letters not allowed)MY_QUENE_NAME
(uppercase letters and underscores not allowed))-queue-
(cannot start or end with hyphen)really-really-really-really-really-really-really-long-queue-name
(max 63 chars)queue_name
(underscores not allowed)
Sending messages
import { env } from "cloudflare:workers";
export default defineApp([ route('/pay-with-ai', () => { // Post a message to the queue env.QUEUE.send({ userId: 1, amount: 100, currency: 'USD', })
return new Response('Done!') })])
Receiving messages
In order to “consume messages” from the queue you need to change the shape of the default
export of your worker. You’ll add another function called queue
that will receive a batch of messages.
const app = defineApp([ /* routes... */])
export default { fetch: app.fetch, async queue(batch) { for (const message of batch.messages) { console.log('handling message' + JSON.stringify(message)) } }} satisfies ExportedHandler<Env>;
This will receive a batch of messages, and process them one by one.
Ways to Send Messages
Cloudflare Queues allow Workers to send and process asynchronous messages reliably.
There are three common approaches to send data:
Send Message Body Directly (up to 128KB)
Best for: Small payloads that fit within the 128KB limit.
await queue.send({});
✅ Simple and fast ❌ Hard limit of 128KB per message
Store in R2 and Send Object Key
Best for: Large payloads (e.g., files, JSON blobs, videos).
// Upload to R2 firstawait r2.put("msg/123.json", JSON.stringify(largeData));
// Then send only the key to the queueawait queue.send({ body: JSON.stringify({ r2Key: "msg/123.json" }),});
✅ Great for large data ✅ Persistent and versioned if needed ❌ Slightly more complex (requires R2 integration)
Store in KV and Send KV Key
Best for: Short-lived messages or small-to-medium payloads.
// Save to KVawait kv.put("queue:msg:123", JSON.stringify(data), { expirationTtl: 600 });
// Send reference keyawait queue.send({ body: JSON.stringify({ kvKey: "queue:msg:123" }),});
✅ Fast access ✅ Automatic expiration possible ❌ Not ideal for large data ❌ KV has eventual consistency (meaning that when you write data to Cloudflare KV (via kv.put), it might not be immediately visible to all readers — especially in different Cloudflare data centers.)
Tips
Handling Different Queues
By sending a message on a queue
import { env } from "cloudflare:workers";
export default defineApp([ route('/pay-with-ai', () => { // Post a message to the queue env.QUEUE.send({ userId: 1, amount: 100, currency: 'USD', })
return new Response('Done!') })])
when handling queues and receivng a MessageBatch
, the batch
contains
a collection of messages
and the name of the queue
which you can use to handle
const app = defineApp([ /* routes... */])
export default { fetch: app.fetch, async queue(batch) { if (batch.queue === 'my-queue-name') { for (const message of batch.messages) { console.log('handling my-queue-name message' + JSON.stringify(message)) } } }} satisfies ExportedHandler<Env>;
ℹ️ Note: Having a dedicated Queue for a specific message is a best practice
Handling Different Messages on the Same Queue
Use metadata (e.g. type, source, key) in your message body to help the consumer Worker determine where and how to retrieve the full data.
If for some reason, you decide to share a queue for different types of messages, one pattern is to set a type
(or other attribute) in the message body to specify its purpose and how to handle its contents.
For example, when sending this payment message:
import { env } from "cloudflare:workers";
export default defineApp([ route('/pay-with-ai', () => { // Post a message to the queue env.QUEUE.send({ type: 'PAYMENT', userId: 1, amount: 100, currency: 'USD', })
return new Response('Done!') })])
One can then determine the message type and handle accordingly:
const app = defineApp([ /* routes... */])
export default { fetch: app.fetch, async queue(batch) { for (const message of batch.messages) { const { type, userId, amount, currenct } = message.body as { type: string, userId: number, amount: number, currency: string }; if (type === 'PAYMENT') { console.log('handling payment message' + JSON.stringify(message)) } } }} satisfies ExportedHandler<Env>;