Skip to content
 

JS SDK Reference

The @vercel/queue SDK lets JavaScript and TypeScript apps publish and consume Vercel Queues messages. For Python, see the Python SDK Reference.

Terminal
pnpm i @vercel/queue
Terminal
yarn add @vercel/queue
Terminal
npm i @vercel/queue
Terminal
bun add @vercel/queue

Import the top-level helpers directly from @vercel/queue. A lazily-created default client resolves credentials from the Vercel environment.

app/api/queues/process-order/route.ts
import { send, handleCallback } from '@vercel/queue';

Create a QueueClient when you need to target a specific region, set default options, or manage multiple clients.

lib/queue.ts
import { QueueClient } from '@vercel/queue';
 
const queue = new QueueClient({ region: 'sfo1' });
 
export const { send, handleCallback } = queue;

Then import from your module instead of @vercel/queue.

app/api/orders/route.ts
import { send } from '@/lib/queue';
 
export async function POST(request: Request) {
  const body = await request.json();
  const { messageId } = await send('orders', body);
  return Response.json({ messageId });
}
app/api/queues/process-order/route.ts
import { handleCallback } from '@/lib/queue';
 
export const POST = handleCallback(async (message, metadata) => {
  await processOrder(message);
});

Use send to publish a message to a topic. The message can be any JSON-serializable value.

app/api/orders/route.ts
import { send } from '@vercel/queue';
 
export async function POST(request: Request) {
  const body = await request.json();
  const { messageId } = await send('orders', {
    orderId: body.orderId,
    action: 'process',
  });
  return Response.json({ messageId });
}
app/api/orders/route.ts
await send('orders', payload, {
  region: 'sfo1',
  retentionSeconds: 3600,
  delaySeconds: 60,
  idempotencyKey: 'order-123',
  headers: { 'x-trace-id': 'abc-123' },
});
OptionTypeDefaultDescription
regionstringAuto-detectedTarget a specific region for this message
retentionSecondsnumber24 hoursMessage TTL. Minimum 60 seconds, maximum 7 days (604,800 seconds)
delaySecondsnumberZero secondsDelay before message becomes visible. Maximum 7 days, capped at message TTL
idempotencyKeystring-Deduplication key for the message
headersRecord<string, string>-Custom headers to include with this message

Use handleCallback to create a push mode consumer. Messages are automatically acknowledged when your handler completes, and retried if the handler throws.

For Express, Connect, or Next.js Pages Router apps, use handleNodeCallback instead, which accepts (req, res) arguments. Unlike the top-level exports, handleNodeCallback is only available on a QueueClient instance.

pages/api/queues/process-order.ts
import { QueueClient } from '@vercel/queue';
 
const queue = new QueueClient();
 
export default queue.handleNodeCallback(async (message, metadata) => {
  await processOrder(message);
});

First, configure the consumer in vercel.json.

vercel.json
{
  "functions": {
    "app/api/queues/process-order/route.ts": {
      "experimentalTriggers": [
        { "type": "queue/v2beta", "topic": "orders" }
      ]
    }
  }
}

Then create the handler.

app/api/queues/process-order/route.ts
import { handleCallback } from '@vercel/queue';
 
export const POST = handleCallback(async (message, metadata) => {
  await processOrder(message);
});

The metadata object includes:

FieldTypeDescription
messageIdstringUnique message identifier
deliveryCountnumberNumber of times this message has been delivered
createdAtDateWhen the message was published
expiresAtDateWhen the message expires
topicNamestringTopic the message was published to
consumerGroupstringConsumer group receiving the message
regionstringRegion where the message is stored

Pass an options object as the second argument to handleCallback to configure visibility timeout and retry behavior.

OptionTypeDefaultDescription
visibilityTimeoutSecondsnumber5 minutesHow long the message stays in-flight before redelivery
retryfunction-Custom retry logic. See custom retry behavior

The SDK automatically re-extends the visibility timeout while your handler is running, so you don't need to configure it for most workloads. If you need to override it for advanced use cases, pass visibilityTimeoutSeconds.

app/api/queues/process-order/route.ts
import { handleCallback } from '@vercel/queue';
 
export const POST = handleCallback(
  async (message, metadata) => {
    await processOrder(message);
  },
  {
    visibilityTimeoutSeconds: 600,
  },
);

The SDK defaults visibilityTimeoutSeconds to 300 seconds (5 minutes) and automatically re-extends the lease while your handler is still running. The underlying Queues API defaults to 60 seconds and does not auto-extend.

Control retry timing and handle poison messages with the retry option.

app/api/queues/process-order/route.ts
import { handleCallback } from '@vercel/queue';
 
export const POST = handleCallback(
  async (message, metadata) => {
    await processOrder(message);
  },
  {
    retry: (error, metadata) => {
      if (metadata.deliveryCount > 5) {
        return { acknowledge: true };
      }
      const delay = Math.min(300, 2 ** metadata.deliveryCount * 5);
      return { afterSeconds: delay };
    },
  },
);

The retry callback can return:

Return valueBehavior
{ afterSeconds: number }Retry after the specified delay
{ acknowledge: true }Acknowledge the message (stop retrying)
undefinedUse default retry behavior

Nitro v3 and frameworks built on it integrate with Vercel Queues through the Vercel preset. Declare topic triggers in nitro.config.ts, and Nitro generates the consumer function and trigger configuration during the build.

nitro.config.ts
export default defineConfig({
  vercel: {
    queues: {
      triggers: [
        { topic: 'orders' },
        {
          topic: 'notifications',
          retryAfterSeconds: 60,
          initialDelaySeconds: 5,
        },
      ],
    },
  },
});

Each trigger accepts the following options:

OptionTypeRequiredDescription
topicstringYesTopic to subscribe to
retryAfterSecondsnumberNoDelay before a failed message is retried
initialDelaySecondsnumberNoDelay before a newly published message is first delivered

Process incoming messages with the vercel:queue runtime hook in a Nitro plugin. The hook receives the decoded message, its metadata, and a send function for publishing follow-up messages.

server/plugins/queues.ts
export default definePlugin((nitro) => {
  nitro.hooks.hook('vercel:queue', ({ message, metadata }) => {
    console.log(`[${metadata.topicName}] ${metadata.messageId}`, message);
  });
});

Send messages with send from @vercel/queue in any server route.

server/routes/api/orders.post.ts
import { send } from '@vercel/queue';
 
export default defineHandler(async (event) => {
  const order = await event.req.json();
  const { messageId } = await send('orders', order);
  return { messageId };
});

Queues also run in nitro dev. Run vercel link and vercel env pull first so the SDK can authenticate, then send delivers messages straight to your vercel:queue hook for local testing.

The SDK provides typed error classes for each failure mode.

import {
  UnauthorizedError,
  BadRequestError,
  DuplicateMessageError,
  MessageNotFoundError,
  QueueEmptyError,
} from '@vercel/queue';
 
try {
  await send('orders', payload);
} catch (error) {
  if (error instanceof UnauthorizedError) {
    // Invalid or expired token
  } else if (error instanceof DuplicateMessageError) {
    // Idempotency key collision
  }
}

The SDK serializes common JSON, text, and byte payloads from the payload type. Use transports when you need to override serialization, validate payloads, or receive large payloads as streams.

lib/queue.ts
import { QueueClient, BufferTransport, StreamTransport } from '@vercel/queue';
 
const binaryQueue = new QueueClient({
  transport: new BufferTransport(),
});
 
const streamQueue = new QueueClient({
  transport: new StreamTransport(),
});
TransportDescription
JsonTransportDefault. Serializes messages as JSON
BufferTransportSends and receives raw binary data
StreamTransportSends and receives ReadableStream for large payloads
Last updated August 12, 2026

Was this helpful?