Cloudflare Workers Queues allow you to offload long-running tasks from your HTTP request lifecycle to a background processing system.

Let’s see it in action. Imagine you have a website that allows users to upload images. After an image is uploaded, you might want to perform several background tasks: resizing the image to different dimensions, applying filters, and sending a notification to the user. These tasks can take time and would lead to a poor user experience if they were executed directly within the HTTP request.

Here’s how you’d set up a Worker to send a job to a Queue:

// worker.js
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  if (request.method === 'POST' && request.url.endsWith('/upload')) {
    const imageBlob = await request.blob();

    // Send the image data to the queue
    await env.IMAGE_PROCESSING_QUEUE.send({
      image: await imageBlob.arrayBuffer(), // Send as ArrayBuffer for efficiency
      userId: 'user-123',
      taskId: crypto.randomUUID()
    });

    return new Response('Image upload received. Processing in background.', { status: 202 });
  }

  return new Response('Not found', { status: 404 });
}

And here’s the Worker that consumes jobs from the Queue:

// queue_worker.js
export default {
  async fetch(request, env, ctx) {
    return env.IMAGE_PROCESSING_QUEUE.fetch(request, env, ctx);
  },
  async queue(batch, env) {
    for (const message of batch) {
      const { image, userId, taskId } = message.body;

      console.log(`Processing task ${taskId} for user ${userId}`);

      // Simulate image processing
      await simulateImageProcessing(image);

      console.log(`Finished processing task ${taskId}`);
      message.ack(); // Acknowledge the message to remove it from the queue
    }
  }
};

async function simulateImageProcessing(imageBuffer) {
  // In a real-world scenario, you would perform image resizing, filtering, etc.
  // For demonstration, we'll just sleep for a bit.
  await new Promise(resolve => setTimeout(resolve, 5000));
  console.log(`Processed image of size: ${imageBuffer.byteLength} bytes`);
}

In your wrangler.toml file, you’d define the Queue:

name = "my-image-processor"
main = "worker.js"
compatibility_date = "2023-10-04"

[[queue]]
queue_name = "IMAGE_PROCESSING_QUEUE"
worker = "queue_worker.js"

This setup decouples the immediate HTTP response from the potentially time-consuming image processing. The /upload endpoint returns a 202 Accepted status quickly, letting the user know their request was received. The actual processing happens asynchronously in the queue_worker.js.

The core problem this solves is the limitation of synchronous HTTP requests. Web servers are designed to respond quickly. If a task takes too long, the connection can time out, or the server resources might be tied up, preventing it from handling other incoming requests. Queues provide a buffer. You enqueue a job, and a separate worker process consumes and executes it at its own pace. This makes your application more resilient and scalable.

Internally, Cloudflare Queues are a managed message queue service. When you call env.QUEUE_NAME.send(), the message is persisted by Cloudflare. The worker designated to handle that queue (queue_worker.js in our example) is then invoked by Cloudflare whenever there are messages to process. Cloudflare manages the delivery, retries, and acknowledgment of messages. You don’t need to provision or manage separate queue infrastructure. The batch object in the queue handler contains up to 100 messages, allowing for efficient batch processing. Acknowledging a message (message.ack()) tells Cloudflare that the processing for that specific message is complete and it can be removed from the queue. If a worker fails to acknowledge a message (e.g., due to an unhandled error or the worker crashing), Cloudflare will automatically retry delivering that message up to a configurable limit.

A common pattern is to include a unique identifier for each job in the message body. This ID can be used to track the job’s progress through an external system (like a database or a status API) or to correlate logs related to that specific task, making debugging significantly easier.

When you configure a queue in wrangler.toml, you are telling Cloudflare to provision and manage a queue named IMAGE_PROCESSING_QUEUE and to direct messages sent to it to the queue_worker.js script for processing. The [[queue]] block is the declarative way to set this up within your Worker project.

The message.ack() method is crucial; failing to call it means the message will be retried indefinitely (or until a retry limit is hit), potentially leading to duplicate processing if your worker isn’t idempotent.

The next concept you’ll likely explore is implementing dead-letter queues for messages that repeatedly fail processing.

Want structured learning?

Take the full Cloudflare course →