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.
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
Section titled “Naming Queues”Queue names must match the following RegEx pattern: ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$
Valid queue names
Section titled “Valid queue names”my-queue
my-awesome-queue-123
queue1
1queue
my-queue-v2
Invalid queue names
Section titled “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
Section titled “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
Section titled “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
Section titled “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)
Section titled “Send Message Body Directly (up to 128KB)”Best for: Small payloads that fit within the 128KB limit.
await queue.send({ body: JSON.stringify({ email: "user@example.com", subject: "Welcome!" }),});
✅ Simple and fast ❌ Hard limit of 128KB per message
Store in R2 and Send Object Key
Section titled “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
Section titled “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.)
Handling Different Queues
Section titled “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
Section titled “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>;