| name | queues |
| description | Use Cloudflare Queues for asynchronous background work, producers, consumers, batching, retries, idempotent message handling, delayed processing, fan-out, and decoupling request latency. Use when Worker code should do something later without needing strict ordering.
|
| compatibility | Cloudflare Workers TypeScript projects using Wrangler; verify current Cloudflare APIs, limits, and pricing before production use. |
| metadata | {"source":"Architecting on Cloudflare plus official Cloudflare Developer Platform docs","generated":"2026-04-28"} |
Queues
Use this skill when a Worker should enqueue background work and return quickly.
Best uses
- Sending emails or webhooks after a request.
- Generating reports or exports.
- Ingesting events.
- Fan-out processing.
- Cache warming.
- Non-blocking AI or embedding jobs.
Use Workflows instead for ordered, inspectable, multi-step processes. Use Durable Objects instead for realtime coordination or shared progress state.
Producer pattern
export interface Env {
JOBS: Queue<JobMessage>;
}
type JobMessage = {
jobId: string;
tenantId: string;
kind: "send-email" | "generate-export";
payload: unknown;
};
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const job: JobMessage = {
jobId: crypto.randomUUID(),
tenantId: "tenant_123",
kind: "generate-export",
payload: { requestedAt: new Date().toISOString() }
};
await env.JOBS.send(job);
return Response.json({ queued: true, jobId: job.jobId }, { status: 202 });
}
} satisfies ExportedHandler<Env>;
Consumer pattern
export default {
async queue(batch: MessageBatch<JobMessage>, env: Env, ctx: ExecutionContext): Promise<void> {
for (const message of batch.messages) {
try {
await handleJob(message.body, env);
message.ack();
} catch (error) {
console.error("queue job failed", {
jobId: message.body.jobId,
error: error instanceof Error ? error.message : String(error)
});
message.retry({ delaySeconds: 30 });
}
}
}
} satisfies ExportedHandler<Env>;
Wrangler config
{
"queues": {
"producers": [
{ "binding": "JOBS", "queue": "jobs" }
],
"consumers": [
{
"queue": "jobs",
"max_batch_size": 10,
"max_batch_timeout": 5
}
]
}
}
Message handling rules
- Treat delivery as at-least-once; make handlers idempotent.
- Do not assume global ordering.
- Keep messages small; store large payloads in R2 or D1 and send references.
- Include
jobId, tenantId, kind, schema version, and creation time.
- Validate the message body before acting.
- Acknowledge only after required durable side effects complete.
- Add observability around retries, failures, and queue depth.
Idempotent consumer example
async function handleJob(job: JobMessage, env: Env) {
const claimed = await env.DB.prepare(
`INSERT INTO processed_jobs (job_id, processed_at)
VALUES (?, ?)
ON CONFLICT(job_id) DO NOTHING
RETURNING job_id`
).bind(job.jobId, new Date().toISOString()).first<{ job_id: string }>();
if (!claimed) return;
}
Anti-patterns
- Queue message contains multi-megabyte file data instead of an R2 key.
- Business logic depends on exact ordering across many producers.
- Consumer does not record idempotency and duplicates cause harm.
- Queue is used as a hidden workflow engine with unclear progress state.