> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xano.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Realtime Servers & Channels

> A realtime server is what a client connects to. A channel is what it joins.

<Info>
  This page covers the current version of Realtime. For the original Workspace Settings–based channels, see [Realtime (Legacy)](/realtime/realtime-in-xano).
</Info>

## Realtime servers

A **realtime server** is what a client opens a websocket connection to. It's the Realtime equivalent of an API group: a named container that holds channels, has its own settings, and — most importantly — has its own connection address, called its **canonical**.

You can create as many realtime servers in a workspace as you need. Each one is independent:

* Its own canonical, so clients connect to exactly the surface you intend
* Its own channels and their access rules
* Its own connect and disconnect triggers

That independence is what makes it safe to stand something new up next to an existing implementation. A legacy Realtime setup and a new realtime server use **separate connection addresses**, so both can run at once and neither is aware of the other.

<Tip>
  Use separate servers to draw a hard line between audiences — for example, a public server for anonymous notification listeners and a second server for authenticated in-app collaboration. A client that connects to one can't reach the other's channels.
</Tip>

### Finding Realtime in your workspace

<Steps>
  <Step>
    ### Open the Realtime section

    In the left side navigation of your workspace, click **Realtime**. The landing page is the **Realtime Servers** list.

    If you don't see the entry, your role doesn't have Read on the **Workspace Realtime Features** permission — the nav item isn't rendered at all without it. See [Access Control](/realtime/access-control#builder-permissions-rbac).
  </Step>

  <Step>
    ### Create a server

    Use **New Server** from the Realtime nav entry's create dropdown, or the primary action on the server list.

    Canonicals may contain letters, numbers, dashes, and underscores; they must be unique, can't be a reserved value, and can't contain `mvp-`. Leave it blank when creating and Xano generates one for you.
  </Step>

  <Step>
    ### Add channels and messages

    Open a server and use **Add Channel**; open a channel and use **Add Message**. Every one of these panels has a **XanoScript** toggle that swaps the guided form for a script editor.
  </Step>
</Steps>

### Finding the connection address

The canonical is shown in three places:

| Where               | What you see                                                                                           |
| ------------------- | ------------------------------------------------------------------------------------------------------ |
| **Server list**     | Each server card shows its canonical top-right beside a link icon. The whole element is click-to-copy. |
| **Server detail**   | Ellipsis menu → **Security** → the **Canonical ID** field, editable alongside the GUID.                |
| **Server settings** | Displayed read-only as `Canonical: <value>` under the name field.                                      |

<Tip>
  On the server detail page, each channel card's link affordance copies a **composed reference** — `<server-canonical>/<channel-path>` — not the bare channel name. That pairing is what a client needs: the server canonical to connect, and the channel path to join.
</Tip>

### In XanoScript

```java XanoScript lines icon="code" theme={null}
realtime_server "chat" {
  description = "Realtime server for chat"
  active = true
  history = "inherit"
  tags = ["chat"]
}
```

Only the name is required. `canonical` is also accepted as a clause — omit it when creating and the platform generates one.

<Warning>
  After creating a server, pull its XanoScript and **preserve the generated `canonical` on later pushes**. In dev validation, pushing the same server again without its canonical regenerated the address, and clients using the previous address received `Unknown connection hash`. Keep the frontend connection URL aligned with the pulled canonical.
</Warning>

For a complete native WebSocket example, including the V2 URL and join acknowledgement, see [Connecting a Client](/realtime/connecting-a-client).

## Channels

A **channel** is what a client joins once it's connected. It's the Realtime equivalent of an API route, and like a route it's addressed by a **path template**:

```text Channel path template theme={null}
rooms/{room_id}
```

A client joining `rooms/42` matches this channel.

### Path template rules

* Letters, numbers, dashes, underscores, slashes, and `{curly params}`
* Every `{param}` needs a matching typed entry in the channel's `input` block. The builder enforces this and offers an **Add missing** button when a parameter has no declaration.
* Names beginning with `_` are reserved for platform lifecycle handlers.

### Typed path parameters

Declare a type for each path parameter in the channel input schema:

| Type      | Declared value                  |
| --------- | ------------------------------- |
| `text`    | Any string segment              |
| `int`     | A whole number                  |
| `decimal` | A number with a fractional part |
| `bool`    | A boolean value                 |

The schema declaration does not mean that every representation of the path parameter is already coerced to that type. In dev validation, both the join acknowledgement's `payload.params` and `realtime.get_session`'s `params` contained `{room_id: "42"}` — a string — even though the channel declared `int room_id`.

<Note>
  Treat path values from acknowledgement and session objects as transport values. Validate or convert them before numeric comparisons or database use. Message payload validation is a separate boundary; see [Messages](/realtime/messages#payload-schema).
</Note>

<Tip>
  Prefer a typed template over a catch-all. `rooms/{room_id}` with `room_id` declared as `int` tells you — and Xano — far more than a wildcard does, and identifies the parameter your join trigger must validate and authorize.
</Tip>

### In XanoScript

A channel declares which server it belongs to by name. The path is unique only within its server.

```java XanoScript lines icon="code" theme={null}
channel "rooms/{room_id}" {
  realtime_server = "chat"
  description = "One chat room per room_id"
  access = {anonymous: false, presence: true}
  publish = {who: "authenticated", direct: false}
  conversation = {active: true, limit: 50, ttl: 86400}
  delivery = {guarantee: "at_least_once", per_recipient: true}
  rate_limit = {messages_per_minute: 60}
  history = "inherit"

  input {
    int room_id
  }

  tags = ["chat"]
}
```

`realtime_server` is the only required clause. Enable and disable a channel from the builder rather than in script.

### Channel settings

| Setting                     | Script key                       | Values                                                                  |
| --------------------------- | -------------------------------- | ----------------------------------------------------------------------- |
| Anonymous clients           | `access.anonymous`               | boolean                                                                 |
| Presence                    | `access.presence`                | boolean                                                                 |
| Who may publish             | `publish.who`                    | `nobody` · `anyone` · `authenticated`                                   |
| Client-to-client addressing | `publish.direct`                 | boolean                                                                 |
| Rate limit                  | `rate_limit.messages_per_minute` | number (`0` = unlimited)                                                |
| Transcript                  | `conversation.active`            | boolean                                                                 |
| Transcript size             | `conversation.limit`             | number                                                                  |
| Transcript expiry           | `conversation.ttl`               | number (seconds)                                                        |
| Delivery guarantee          | `delivery.guarantee`             | `at_most_once` · `at_least_once`                                        |
| Per-recipient tracking      | `delivery.per_recipient`         | boolean                                                                 |
| Request history retention   | `history`                        | `false` · `"inherit"` · `0` · `10` · `100` · `1000` · `10000` · `"all"` |

What these mean for security is covered in [Access Control](/realtime/access-control).

## Delivery guarantees

The guarantee is a **channel** setting — every delivery on the channel uses it. (Which *clients* receive a given message is chosen per message, with [`deliver_to`](/realtime/messages#delivery).)

| Guarantee       | Behavior                                                                                               |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `at_most_once`  | Delivered zero or one time. Cheaper; a delivery can be lost.                                           |
| `at_least_once` | Delivered one or more times. A recipient may see a duplicate, so client handlers should be idempotent. |

`delivery.per_recipient` tracks delivery for each recipient individually rather than treating the publish as a single fire-and-forget event.

<Note>
  Match the guarantee to the channel's traffic. A cursor position or a presence ping is fine at `at_most_once` — the next one is along shortly. Chat messages or order status changes usually want `at_least_once`.
</Note>

## Conversation transcript

A channel can keep a **client-visible conversation transcript** — recent messages available to a client that joins later. It's bounded two ways:

| Control              | Purpose                                                |
| -------------------- | ------------------------------------------------------ |
| `conversation.limit` | Maximum number of messages retained                    |
| `conversation.ttl`   | How long a message stays in the transcript, in seconds |

This is what makes "open the chat and see what was said before I got here" a channel setting rather than something you build from scratch. If you need durable history beyond the transcript window — a permanent record you can query, page through, and report on — write messages to a database table from the message's function stack, exactly as you would in an API endpoint.

## Next steps

<CardGroup cols={2}>
  <Card title="Messages" icon="message" href="/realtime/messages">
    Define the handlers clients invoke on a channel.
  </Card>

  <Card title="Access Control" icon="lock-keyhole" href="/realtime/access-control">
    Decide who can connect, join, publish, and receive.
  </Card>
</CardGroup>


## Related topics

- [Realtime Messages](/realtime/messages.md)
- [Realtime Triggers](/realtime/realtime-triggers.md)
- [Realtime Access Control](/realtime/access-control.md)
