send
- Realtime Messaging
Method Signatures:
type Filter = (clientId: string, client: subscriptions.Client) => boolean
// Send a message to clients
function send(topic: string, message: string, filter?: Filter): void
// Capture and send rendered content
function send(topic: string, filter?: Filter): void
- Description: Sends a realtime message to connected clients. Messages can be broadcast to all clients or filtered to specific recipients. By default, messages are filtered to only reach the current authenticated user.
See Client interface documentation for details about the available client methods. Note that client.get('auth')
returns a core.Record auth record when the client is authenticated.
Parameters
topic
: The message topic/channelmessageOrFilter
: (Optional) Either the message content to send, or a filter functionfilter
: (Optional) Function to filter which clients receive the message. Defaults to only sending to the current authenticated user.
Basic Usage
<%
// Send to current authenticated user only (default behavior)
send('notifications', 'Hello!')
// Send to all clients subscribed to "chat"
send('chat', 'Hello everyone!', () => true)
// Send to specific user
send('notifications', 'New message', (clientId, client) => {
return client.get('auth')?.id === '123'
})
%>
Render Capture Mode
You can use send()
to capture and broadcast the rendered output of a template. This is useful for real-time updates where the server renders HTML that should be sent to clients. The captured content is automatically JSON stringified to safely handle newlines and special characters.
<%
// Activate render capture mode - the rendered template output
// will be sent to the 'chat' topic
send('chat')
// Optional: Specify a custom filter
send('chat', (clientId, client) => client.get('auth')?.id === userId)
%>
<!-- This content will be captured and sent -->
<div class="message">
<strong><%= username %>:</strong>
<%= message %>
</div>
Example Chat Implementation
<!-- api/chat.ejs -->
<%
const { message } = body()
// Activate render capture mode for 'chat' topic
send('chat')
%>
<div class="message">
<%= message %>
</div>
<!-- Client-side -->
<form hx-post="/api/chat" hx-swap="none">
<input name="message" />
<button>Send</button>
</form>
<div id="messages" hx-ext="sse" sse-connect="/api/sse" sse-swap="chat">
<!-- Messages will appear here -->
</div>
Default Filtering
The default filter implementation:
// This is the built-in default filter
;(clientId, client) =>
// If user is authenticated, only send to their sessions
api.auth?.id
? client.get('auth')?.id === api.auth?.id
: // If no user is authenticated, send to all clients
true
Common Use Cases
Chat Application
<%
if (request.method === 'POST' && formData.message) {
// Get user info
const userId = request.auth?.id
const username = request.auth?.get('username')
// Create message payload
const payload = stringify({
userId,
username,
message: formData.message,
timestamp: new Date().toISOString()
})
// Broadcast to all chat participants by overriding default filter
send('chat', payload, () => true)
// Optionally store in database
$app.dao().createRecord('messages', {
userId,
message: formData.message
})
}
%>
<!-- Chat form -->
<form method="POST">
<input type="text" name="message" placeholder="Type a message..." />
<button type="submit">Send</button>
</form>
<!-- Client-side listener -->
<script>
pb.realtime.subscribe('chat', (data) => {
const msg = JSON.parse(data)
appendMessage(msg)
})
</script>
Notifications
<%
// Send notification to specific user
function notifyUser(userId, message) {
send('notifications', stringify({ message }), (clientId, client) => {
return client.get('auth')?.id === userId
})
}
// Usage in form handler
if (request.method === 'POST') {
// Process form...
// Notify relevant users
notifyUser('123', 'Your form was processed')
notifyUser('456', 'New submission requires review')
}
%>
Live Updates
<%
// After updating a record
if (request.method === 'POST' && formData.action === 'update') {
const record = $app.dao().findRecordById('products', formData.id)
// Activate render capture mode
send(`product-${record.id}`, () => true)
%>
<!-- Updated product view will be captured and sent -->
<div class="product">
<h2><%= record.name %></h2>
<p><%= record.description %></p>
<span class="price">$<%= record.price %></span>
</div>
<% } %>
Important Notes
Message Formats:
- Direct messages are sent as strings
- Use
stringify()
for sending structured data - In render capture mode, the rendered HTML is sent automatically
Render Capture Mode:
- Activated by calling
send(topic)
orsend(topic, filter)
- Captures all rendered output after the
send()
call - Content is automatically JSON stringified for safe transmission
- Useful for sending server-rendered HTML updates
- Common with HTMX SSE integration
- Activated by calling
Client Filtering:
- Default filter only sends to current authenticated user
- Filter function receives client ID and client object
- Return
true
to send to that client - Return
false
to skip that client - Pass
() => true
to broadcast to all subscribers
Security:
- Default filtering helps prevent message leaks
- Validate message content
- Consider authentication state
- Filter sensitive information
- Protect against message injection