Publishing messages
Single, batch, and transactional publish with message groups, idempotency, routing keys, and priorities
Publishing is the core operation in npayload. You send a message to a channel and npayload delivers it to all matching subscriptions. This guide covers every publishing pattern available.
Single publish
The simplest operation: publish one message to one channel.
import { NPayloadAuth, NPayloadClient } from '@npayload/node';
const auth = new NPayloadAuth({
clientId: process.env.NPAYLOAD_CLIENT_ID!,
hmacSecret: process.env.NPAYLOAD_HMAC_SECRET!,
});
const npayload = new NPayloadClient({ auth });
await npayload.messages.publish('orders', {
payload: {
event: 'order.created',
orderId: 'ord_12345',
total: 59.98,
},
});The response includes the message GID, which you can use to track delivery status.
const result = await npayload.messages.publish('orders', {
payload: { event: 'order.created', orderId: 'ord_12345' },
});
console.log(result.messageGid); // "msg_abc123"Batch publish
Publish up to 100 messages in a single request. All messages go to the same channel.
const messages = orders.map((order) => ({
payload: {
event: 'order.created',
orderId: order.id,
total: order.total,
},
}));
const result = await npayload.messages.publishBatch('orders', messages);
console.log(result.published); // number of messages publishedBatch publish is not atomic. If some messages fail validation, the valid ones are still published. Check the response for per-message status.
Transactional publish
Publish messages to multiple channels atomically. Either all messages are published or none are. This is useful when a single business event needs to update several channels.
await npayload.messages.publishTransactional([
{
channel: 'orders',
payload: { event: 'order.created', orderId: 'ord_12345' },
},
{
channel: 'inventory',
payload: { event: 'stock.reserved', sku: 'WIDGET-A', quantity: 2 },
},
{
channel: 'notifications',
payload: { event: 'notify.customer', email: 'buyer@example.com' },
},
]);Transactional publish supports up to 10 channel targets per transaction. All messages are committed together or rolled back on failure.
Message groups (FIFO ordering)
By default, npayload delivers messages in parallel for maximum throughput. When you need strict ordering, use message groups. Messages in the same group are delivered sequentially, in the order they were published.
// All messages for the same order are delivered in order
await npayload.messages.publish('orders', {
payload: { event: 'order.created', orderId: 'ord_12345' },
groupKey: 'ord_12345',
});
await npayload.messages.publish('orders', {
payload: { event: 'order.paid', orderId: 'ord_12345' },
groupKey: 'ord_12345',
});
await npayload.messages.publish('orders', {
payload: { event: 'order.shipped', orderId: 'ord_12345' },
groupKey: 'ord_12345',
});Different group keys are processed independently. Ordering is guaranteed only within the same group.
Idempotency keys
Prevent duplicate messages when retrying failed requests. If you publish with the same idempotency key within 24 hours, npayload returns the original result without creating a duplicate.
await npayload.messages.publish('orders', {
payload: { event: 'order.created', orderId: 'ord_12345' },
idempotencyKey: 'create-ord_12345',
});
// Safe to retry: same key returns the same result
await npayload.messages.publish('orders', {
payload: { event: 'order.created', orderId: 'ord_12345' },
idempotencyKey: 'create-ord_12345',
});Idempotency keys are scoped to your organisation and app. They expire after 24 hours.
Routing keys
Attach a routing key to a message so that subscribers can filter by pattern. Only subscriptions with a matching routing key filter receive the message.
await npayload.messages.publish('events', {
payload: { event: 'user.signup', region: 'eu-west' },
routingKey: 'user.signup.eu-west',
});
await npayload.messages.publish('events', {
payload: { event: 'user.signup', region: 'us-east' },
routingKey: 'user.signup.us-east',
});Subscribers use glob patterns to match routing keys. See Subscriptions and delivery for filter syntax.
Priority messages
When publishing to a priority channel, assign a priority level from 0 (lowest) to 9 (highest). Higher priority messages are delivered first.
// Critical alert: priority 9
await npayload.messages.publish('alerts', {
payload: { level: 'critical', message: 'Database connection pool exhausted' },
priority: 9,
});
// Informational: priority 1
await npayload.messages.publish('alerts', {
payload: { level: 'info', message: 'Deployment completed successfully' },
priority: 1,
});Priority only applies to channels created with type: 'priority'. See Working with channels for setup.
Report messages (receipt confirmation)
Request a delivery receipt from consumers. When a subscriber processes the message and sends back a report, you receive confirmation that processing completed.
const result = await npayload.messages.publish('tasks', {
payload: { task: 'generate-report', reportId: 'rpt_001' },
reportRequested: true,
});
// Later, check for reports
const reports = await npayload.messages.getReports(result.messageGid);
for (const report of reports) {
console.log(`Subscriber ${report.subscriberGid} reported: ${report.status}`);
}Subscribers send reports using the SDK:
await npayload.messages.report(messageGid, {
status: 'processed',
details: { rowsGenerated: 1520 },
});Payload limits and best practices
| Limit | Value |
|---|---|
| Max payload size | 256 KB |
| Max batch size | 100 messages |
| Max transactional targets | 10 channels |
| Idempotency key TTL | 24 hours |
| Routing key max length | 256 characters |
Best practices:
- Use idempotency keys for any publish that might be retried (network errors, timeouts)
- Use message groups only when ordering matters. Ungrouped messages have higher throughput
- Keep payloads small. Store large data externally and publish a reference (URL or ID)
- Use routing keys to reduce unnecessary deliveries rather than filtering on the consumer side
- Use transactional publish when consistency across channels is required
Next steps
Was this page helpful?