Getting a WebSocket open is the easy part. The real work is deciding what runs on the server when a message arrives: who is allowed to connect and join, how payloads are validated, who receives each message, and how that logic is tested, reviewed and observed like the rest of your backend.
In Xano, realtime is built the same way as your APIs. Since Xano 2.6, Workspace Realtime gives you realtime servers that clients connect to, channels they join, and message handlers that each have their own function stack.
What is realtime, and why use WebSockets?
Realtime is any feature where the application delivers updates without the client requesting them. Typical examples:
- Chat and messaging, with presence (who is online)
- In-app notifications and activity feeds
- Live dashboards fed by background jobs or incoming data
- Collaborative editing, where each change fans out to everyone in the document
- Multiplayer or shared state, such as a game room
- Streaming an LLM or agent response back to the user as it is generated
Without a persistent connection, the client has to poll: call an API every few seconds to check for changes. Polling is simple, but it adds latency, wastes requests when nothing has changed and gets expensive as the number of users grows. A WebSocket keeps one connection open per client, so the server can send an update the instant it happens.
When does polling stop being enough?
You are likely ready for realtime when any of these sound familiar:
- Users notice delay: messages, statuses or prices arrive seconds late.
- Most polling requests come back with nothing new, but you are paying for them anyway.
- Several users act on the same data at once and need to see each other’s changes.
- Background work (an import, an AI agent, a scheduled task) finishes and the user should know immediately.
- You are about to add a third-party realtime service just to fan out events your backend already knows about.
Sound familiar? Realtime is worth it. The sections below cover what to plan for.
What should you plan for in a realtime feature?
-
Where the logic runs
Pure broadcast (whatever one client sends, everyone else receives) breaks down quickly. Most real features need server-side logic per message: store the transcript, check the sender can post in this room, enrich the payload, or reply only to the sender.
-
Validating what clients send
A WebSocket accepts whatever a client sends. Treat every incoming message like an API request: declare what a valid payload looks like and reject anything else before it reaches your data or other users.
-
Access control
Decide separately who can connect, who can join which channel, who can publish and who receives each message. Never trust channel names or IDs supplied by the client on their own; authorize them against your own data.
-
Delivery
Not every message should go to everyone: some go to the whole channel, some only back to the sender, some to everyone except the sender, some to specific users. Different recipients may need different versions of the same message.
-
Publishing from the server side
Many realtime updates don’t start with a client. A database change, a webhook, an API call or a scheduled job should be able to push an event onto a channel without a client in the loop.
-
Scale and cost
Every open connection and every fan-out uses server resources. Per-recipient logic runs once per recipient, so keep it lean on busy channels, keep payloads small and rate-limit clients that publish.
-
Testing, change control and observability
Realtime logic is backend logic. It needs the same drafts, review, version history, testing and request logs as your APIs, or it becomes the one part of the system nobody can safely change.
How does a realtime backend work?
A typical design has three layers:
- A server that clients connect to over a WebSocket, with connection-level checks (is this token valid, is this account active).
- Channels that clients join, usually addressed by a path that identifies the room, document or match, such as
rooms/{room_id}. - Message handlers on each channel: named actions with a defined payload, server-side logic and a rule for who receives the result.
Around these sit lifecycle hooks (connect, join, leave, disconnect, and per-recipient delivery) for presence, authorization and cleanup, plus a way for any backend process to publish an event onto a channel.
How is Realtime organized in Xano?
Xano’s Realtime mirrors how you already build APIs. If you can build an API in Xano, you already know the shape:
| Realtime | What it is | API equivalent |
|---|---|---|
| Realtime server | What a client connects to, with its own connection address | API group |
| Channel | What a client joins, addressed by a typed path template such as rooms/{room_id} | API route |
| Message | A handler inside a channel: typed inputs, a function stack and a response | API endpoint |
Realtime is authored in the same editors and follows the same path to production as the rest of your workspace: drafts and publish, branches and diffs, version history, Run & Debug, a Messages tab in Request History, and XanoScript you can push and pull with the Xano CLI.
How do you build a realtime feature in Xano?
- Create a realtime server. Open Realtime in your workspace’s left navigation and create a server. Each server has its own connection address, and a workspace can have several, so you can keep, say, public notifications and internal collaboration apart.
- Define typed channels. Address channels with path templates such as
rooms/{room_id}, with a declared type for each parameter (text,int,decimalorbool). Each channel sets its own rules:- Who may publish:
nobody(listen-only, fed by your backend),anyoneorauthenticatedclients - Anonymous access, presence and client-to-client messages
- A rate limit per client
- A transcript of recent messages for clients that join later
- The delivery guarantee:
at_most_onceorat_least_once
- Who may publish:
- Write message handlers. Each message declares its payload schema, so malformed payloads are rejected before your logic runs. The handler is an ordinary function stack: middleware, database calls, external requests and a response. A message can require authentication even on a channel that anonymous clients may join.
- Choose who receives each message. Set delivery per message to the whole
channel, just thesender, theotherson the channel, orexplicitrecipients you choose. Delivering to the sender turns the socket into a request/response channel. - Add lifecycle triggers. Connect and join triggers can reject a connection or a join, which is where you check membership against your own data. Leave and disconnect triggers handle presence and cleanup. A deliver trigger runs once per recipient and can rewrite or drop that copy.
Using lifecycle triggers? Realtime triggers require a paid instance. Realtime triggers docs · Compare plans
- Publish from anywhere in your backend. Add Realtime Publish (
realtime.publish) to an API, function, database trigger or background task to push a server-authored event onto a channel, for example a scheduled task feeding a live dashboard. It sends your data as-is and doesn’t run a message handler, so authorize in the stack that publishes. - Connect your frontend. Clients connect to
wss://<your-instance>/ws/<server-address>with a native WebSocket or the Xano SDK, passing a signed-in user’s token for authenticated channels, and wait for the join acknowledgement before sending messages. - Test and ship it like any other change. Run & Debug a message handler without a client attached, publish drafts when they are ready, and review message executions in Request History. If you work from the CLI, keep the server’s generated address in its XanoScript when you push, so connected clients keep working.
What changed from Legacy Realtime?
The original Realtime in Workspace Settings is now called Legacy Realtime. It is not deprecated: existing channels, triggers and connection hashes keep working, and both versions can run side by side on separate connection addresses.
| Area | Legacy Realtime | Realtime |
|---|---|---|
| Unit of work | A channel with a single trigger | Named message handlers, each with its own function stack |
| Payload | Whatever the client sent; you validate it | A declared schema, validated before your logic runs |
| Channel address | A name pattern such as chatroom/* | A typed path template such as rooms/{room_id} |
| Delivery | Broadcast to the channel | Per message: channel, sender, others or explicit |
| Server-side publish | Realtime Event (v1) | Realtime Publish |
| Changes | Live on save | Drafts, publish, branches and version history |
Consider moving a feature when you are validating payloads by hand in a channel trigger, parsing IDs out of channel names, need replies to the sender only, or want realtime changes to go through branches and review. Moving is incremental: stand up a new server next to the legacy one and move one feature at a time. Realtime Event (v1) can’t reach the new channels, so switch those stacks to Realtime Publish as you go. Legacy vs Realtime
Watch it in action
This walkthrough shows Xano’s original realtime setup: enabling realtime, configuring channels with nested channel names and permissions, connecting from the JavaScript SDK, sending and receiving messages, and running a channel trigger.
It was recorded with what is now Legacy Realtime and an earlier version of the Xano interface. The concepts carry over, but today you create realtime servers, typed channels and message handlers under Realtime in the workspace’s left navigation.
Common questions about realtime features
realtime.publish can be called from any API, function, task or database trigger, so a scheduled job, a table change or an incoming webhook can push an event to everyone on a channel.Realtime is backend logic
Opening a WebSocket takes minutes. Deciding who can join, what a valid message looks like and who should receive it is the part that has to hold up in production.
Treat that logic like the rest of your backend: validated inputs, authorization against your own data, and changes that are reviewed, versioned and observable. In Xano, it lives next to the APIs, functions and tasks it works with.


