Back to catalog
Business Logic & Automation

How to Add Realtime Features to Your App With WebSockets

Updated 9 min read Article + video

> quick_answer

Your backend needs to push updates to clients the moment something changes, instead of waiting for them to ask. The standard way is a WebSocket: one long-lived connection either side can send messages over. The real work is the server-side logic that runs when a message arrives.

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?

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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:

  1. A server that clients connect to over a WebSocket, with connection-level checks (is this token valid, is this account active).
  2. Channels that clients join, usually addressed by a path that identifies the room, document or match, such as rooms/{room_id}.
  3. 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:

RealtimeWhat it isAPI equivalent
Realtime serverWhat a client connects to, with its own connection addressAPI group
ChannelWhat a client joins, addressed by a typed path template such as rooms/{room_id}API route
MessageA handler inside a channel: typed inputs, a function stack and a responseAPI 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?

  1. 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.
  2. Define typed channels. Address channels with path templates such as rooms/{room_id}, with a declared type for each parameter (text, int, decimal or bool). Each channel sets its own rules:
    • Who may publish: nobody (listen-only, fed by your backend), anyone or authenticated clients
    • 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_once or at_least_once
  3. 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.
  4. Choose who receives each message. Set delivery per message to the whole channel, just the sender, the others on the channel, or explicit recipients you choose. Delivering to the sender turns the socket into a request/response channel.
  5. 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

  6. 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.
  7. 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.
  8. 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.
Implement this in Xano - Realtime overview

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.

AreaLegacy RealtimeRealtime
Unit of workA channel with a single triggerNamed message handlers, each with its own function stack
PayloadWhatever the client sent; you validate itA declared schema, validated before your logic runs
Channel addressA name pattern such as chatroom/*A typed path template such as rooms/{room_id}
DeliveryBroadcast to the channelPer message: channel, sender, others or explicit
Server-side publishRealtime Event (v1)Realtime Publish
ChangesLive on saveDrafts, 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

Not for in-app features like chat, notifications and live updates. Xano hosts the WebSocket servers, runs your message logic and can publish from any backend process, so you don’t need a separate pub/sub service to fan out events your backend already knows about.
Polling is fine when updates are rare and a few seconds of delay is acceptable. Use a streaming response when the server sends one long answer to one request, such as an LLM reply. Use WebSockets when updates are frequent, go both ways, or need to reach many users at once.
Yes. Legacy Realtime is not deprecated, and existing channels, triggers and connection hashes keep working. Both versions can run at the same time, so you can move one feature at a time, or leave a simple broadcast channel where it is.
Use a join trigger to check the user and the channel’s path parameters against your own data, and reject the join if they don’t match (path values can arrive as text, so convert them before comparing). Channel settings control anonymous access and publishing, and individual messages can require authentication.
Yes. 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.
Yes. The Xano Agent can build realtime servers, channels and messages, and because realtime logic is XanoScript, it pushes and pulls through the Xano CLI, so Claude Code, Cursor and other agents can build it with the Developer MCP.

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.