Skip to main content

Overview

The publish endpoint is a protected mutation procedure that publishes a chat message to a specified channel. It includes content validation, rate limiting, and support for reply threads.

Procedure Type

Mutation - Protected procedure (authentication required)

Authentication

This endpoint requires a valid JWT token. The authenticated user’s information (from ctx.user) is automatically attached to the published message.

Input Parameters

content
string
required
The message content to publish. Will be trimmed before validation.
channel
string
required
The channel identifier to publish the message to
replyToId
string | null
Optional message ID to reply to. Use null or omit for top-level messages.

Response Structure

id
string
Unique identifier for the message (UUID v4)
author
JwtUser
The authenticated user who sent the message
author.robloxUserId
string
User’s Roblox user ID
author.username
string
User’s Roblox username
author.displayName
string
User’s Roblox display name
author.picture
string
URL to user’s profile picture
content
string
The trimmed message content that was published
replyToId
string | null
Message ID this message is replying to, or null if not a reply

Validation Rules

Empty Message Check

After trimming whitespace, the content must not be empty. Error: BAD_REQUEST - “Message cannot be empty.”

Message Length Validation

The message content length must not exceed the maxMessageLength limit for the channel. Error: BAD_REQUEST - “Message exceeds characters.”

Rate Limiting

Rate limiting is enforced per user (by robloxUserId) per channel. The limits are retrieved using getChatLimitsForChannel(input.channel):
  • Limit Count: rateLimitCount messages
  • Window: rateLimitWindowMs milliseconds
When the rate limit is exceeded: Error: TOO_MANY_REQUESTS - “Rate limit hit. Try again in s.” The error message includes the retry-after time in seconds.

Message Broadcasting

Successfully published messages are broadcast to all subscribers on the channel using the global pub/sub system (globalPubSub.emit).

Example Usage

import { trpc } from './trpc';

// Publish a simple message
const message = await trpc.chat.publish.mutate({
  content: 'Hello, world!',
  channel: 'global'
});

console.log(`Message published with ID: ${message.id}`);

// Publish a reply to another message
const reply = await trpc.chat.publish.mutate({
  content: 'Thanks for the help!',
  channel: 'support',
  replyToId: 'abc123-def456-789'
});

Example Response

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "author": {
    "robloxUserId": "123456789",
    "username": "johndoe",
    "displayName": "John Doe",
    "picture": "https://tr.rbxcdn.com/..."
  },
  "content": "Hello, world!",
  "replyToId": null
}

Error Handling

try {
  const message = await trpc.chat.publish.mutate({
    content: '   ',  // Empty after trim
    channel: 'global'
  });
} catch (error) {
  if (error.data?.code === 'BAD_REQUEST') {
    console.error('Validation error:', error.message);
  } else if (error.data?.code === 'TOO_MANY_REQUESTS') {
    console.error('Rate limited:', error.message);
  }
}

Implementation Details

The publish endpoint is implemented at chat.ts:19-68 and uses:
  • getChatLimitsForChannel for retrieving channel limits (line 28)
  • ratelimit service for rate limit enforcement (lines 45-50)
  • crypto.randomUUID() for message ID generation (line 59)
  • globalPubSub.emit for broadcasting messages (line 65)

Build docs developers (and LLMs) love