using-ably
Using Ably
Use this skill when building, modifying, or generating code for an application that uses Ably for realtime messaging, chat, collaboration, or AI streaming.
What Ably Is
Ably is realtime infrastructure — the same way you wouldn't build your own database or CDN, you shouldn't build your own realtime messaging system. Ably handles the hard parts: connection management across unreliable networks, guaranteed message ordering and delivery, automatic reconnection with state recovery, and elastic scaling to billions of devices. It works across platforms (25+ SDKs) and protocols (WebSocket, MQTT, SSE, HTTP), so you can focus on your application logic rather than transport reliability.
Documentation
Always check the official docs at ably.com/docs for current API references. For LLM-optimized documentation, use ably.com/llms.txt. The guidance below covers architectural decisions and common mistakes that documentation alone doesn't prevent.
Before Writing Code
Always verify against current documentation before generating Ably code. Training data goes stale — methods get renamed, parameters change, new products launch. These three checks prevent the most common LLM code generation failures.
1. Fetch Current Docs First
Before generating code for any Ably feature:
- Fetch
https://ably.com/llms.txt?source=using-ablyto get the documentation URL index - Fetch the relevant product and platform documentation pages
- Verify method names, parameters, return types, and package versions against the fetched docs
- If a feature or method doesn't appear in the docs, it probably doesn't exist — don't fabricate it
Never guess or construct doc URLs. Only use URLs from llms.txt or web search results. If a URL 404s, go back to llms.txt. Web search is also allowed as a complementary approach. If llms.txt is unreachable, fall back to web search or browse https://ably.com/docs?source=using-ably directly.
2. Confirm SDK Availability
Not all products are available on all platforms. Before generating code, confirm from the fetched docs that the product has an SDK for the user's platform.
If no SDK exists:
- State clearly: "[Product] does not currently have an SDK for [platform]"
- Don't fabricate an API
- Offer alternatives: REST API directly, a different product that IS available, or MQTT/SSE protocol adapters for constrained devices
3. Post-Generation Check
After generating code, verify these common-mistake patterns:
- API key in client code? Must use
authUrl/authCallbackwith JWT or token auth (see Section 3) - Chat SDK:
attach()before subscribing? Subscribe first to avoid messages being silently lost (see Section 7) - React: Ably client created inside a component without cleanup? Create once outside and pass via provider, or use useEffect with proper cleanup (see Section 8)
- Server-side: Realtime SDK used when REST would suffice? Default to
Ably.Restfor publishing, token generation, history (see Section 2) - Connection cleanup on unmount/exit? Call
realtime.close()(see Section 5) - Reimplementing built-in behavior? Don't add custom debounce for typing indicators, custom throttling for cursors, or custom conflict resolution for LiveObjects (see Section 9)
1. Understand the Product and SDK Landscape
Ably is a multi-product platform, not a single SDK. Choosing the wrong product or SDK is the most common integration mistake.
Product Layer: What Are You Building?
| Product | Use Case | SDK |
|---|---|---|
| Pub/Sub | Core messaging: live dashboards, notifications, IoT, event streaming | ably (Realtime or REST) |
| AI Transport | Streaming LLM tokens, multi-agent coordination, resumable AI sessions | ably (Realtime) |
| LiveObjects | Shared mutable state: counters, key-value maps, collaborative data | ably + LiveObjects plugin |
| LiveSync | Database-to-frontend sync (PostgreSQL change streams) | @ably-labs/models + ADBC connector |
| Chat | Chat rooms, typing indicators, reactions, message history, moderation | @ably/chat |
| Spaces | Collaborative cursors, avatar stacks, member locations, component locking | @ably/spaces |
SDK Architecture: Two Layers
Layer 1 — Core SDKs (used directly for Pub/Sub, AI Transport, LiveObjects, LiveSync):
| SDK | Use When | Connection |
|---|---|---|
Ably.Realtime |
Client needs to subscribe to messages, presence, or state changes | Persistent WebSocket |
Ably.Rest |
Server-side publish only, token generation, history queries | Stateless HTTP |
These are the foundation. AI Transport, LiveObjects, and LiveSync all use the core Realtime SDK directly — they are patterns and plugins on top of Pub/Sub, not separate SDKs.
Layer 2 — Product SDKs (higher-level abstractions for specific use cases):
| SDK | Purpose |
|---|---|
@ably/chat |
Rooms, typing indicators, reactions, message history, moderation |
@ably/spaces |
Cursors, avatar stacks, member locations, component locking |
These product SDKs depend on the core Realtime SDK. You pass an Ably.Realtime instance when creating them. The underlying Realtime knowledge (auth, channels, presence) still applies.
Decision Rules
Q: Is this server-side or client-side?
├── Server-side:
│ ├── Publishing, token generation, history → Use Ably.Rest (stateless HTTP)
│ └── Need persistent connection (AI token streaming, subscribing) → Use Ably.Realtime
└── Client-side → Use Ably.Realtime, then:
├── Building chat? → Use @ably/chat (pass Realtime instance)
├── Building collaboration? → Use @ably/spaces (pass Realtime instance)
├── Need shared state? → Use LiveObjects plugin with Realtime
├── Syncing database? → Use @ably-labs/models with ADBC connector
└── Otherwise → Use Ably.Realtime directly (Pub/Sub, AI Transport)
Server-side rule of thumb: Default to Ably.Rest for server operations. Only use Ably.Realtime on the server when you genuinely need a persistent connection — for example, an AI agent streaming tokens to clients, or a backend service that subscribes to events.
Common Mistakes
// WRONG: Using REST SDK then polling for messages
const rest = new Ably.Rest({ key: '...' });
setInterval(async () => {
const history = await rest.channels.get('updates').history();
}, 1000);
// RIGHT: Using Realtime SDK to subscribe
const realtime = new Ably.Realtime({ key: '...' });
realtime.channels.get('updates').subscribe((msg) => {
console.log('Received:', msg.data);
});
// WRONG: Using raw Pub/Sub for chat when Chat SDK exists
const channel = realtime.channels.get('chat-room');
channel.subscribe((msg) => { /* manually handling typing, reactions, history... */ });
// RIGHT: Using Chat SDK which handles all chat patterns
import { ChatClient } from '@ably/chat';
const chat = new ChatClient(realtime);
const room = await chat.rooms.get('my-room');
room.messages.subscribe((msg) => console.log(msg.text));
await room.typing.keystroke(); // built-in typing indicators
2. Use Modern SDK Patterns (v2.x)
Ably's JavaScript SDK v2.x uses async/await. Never generate callback-style 1.x patterns.
// WRONG: Old 1.x callback pattern (deprecated)
const channel = realtime.channels.get('updates');
channel.subscribe('event', function(message) {
console.log(message.data);
});
channel.publish('event', { text: 'hello' }, function(err) {
if (err) console.error(err);
});
// RIGHT: Modern 2.x async/await pattern
const channel = realtime.channels.get('updates');
channel.subscribe('event', (message) => {
console.log(message.data);
});
await channel.publish('event', { text: 'hello' });
Imports and initialization — keep it simple:
// RIGHT: Standard ESM import
import Ably from 'ably';
const realtime = new Ably.Realtime({ key: 'your-key' });
const rest = new Ably.Rest({ key: 'your-key' });
// RIGHT: Chat SDK import
import { ChatClient } from '@ably/chat';
// WRONG: Don't invent type-based initialization
const options: Ably.Types.ClientOptions = { ... }; // unnecessary
If you hit ESM/CommonJS import errors, check that your tsconfig.json has "moduleResolution": "bundler" or "node16", and that you're importing from 'ably' (not subpaths). Don't try multiple import strategies — check the SDK README for your environment.
Server-side: Default to REST SDK. Only use Ably.Realtime on the server when you need a persistent connection.
// WRONG: Realtime SDK on server just to publish
const realtime = new Ably.Realtime({ key: '...' }); // opens WebSocket unnecessarily
await realtime.channels.get('events').publish('update', data);
// RIGHT: REST SDK for server-side publish
const rest = new Ably.Rest({ key: '...' }); // stateless HTTP
await rest.channels.get('events').publish('update', data);
// RIGHT: Realtime on server for AI streaming (message-per-response pattern)
// See ably.com/docs/ai-transport/token-streaming/message-per-response?source=using-ably
const channel = realtime.channels.get('conversation:123');
const { serials: [msgSerial] } = await channel.publish({ name: 'response', data: '' });
for await (const event of llmStream) {
if (event.type === 'token') {
channel.appendMessage({ serial: msgSerial, data: event.text });
}
}
3. Authentication
API keys are for server-side only. Never expose them to clients.
API keys don't expire. If leaked, attackers have indefinite access until you regenerate the key.
Recommended: JWT Authentication
JWT is the recommended authentication method for client-side applications.
Why JWT over Ably tokens:
- No round-trip to Ably servers — your backend creates and signs the JWT directly
- Integrates with existing auth systems — uses standard JWT libraries you already have
- Capabilities defined per-token — not limited to what's configured on the API key
- Works everywhere — including environments without an Ably SDK (MQTT, embedded devices)
JWT Claims:
kid(header): Your Ably API key name (e.g.,xVLyHw.abcdef)iat: Issued-at timestamp (seconds)exp: Expiration timestamp (seconds)x-ably-capability: JSON string of channel permissionsx-ably-clientId(optional): Client identity for presence
// Server-side: Create JWT for client
import jwt from 'jsonwebtoken';
app.post('/api/ably-auth', (req, res) => {
const apiKey = process.env.ABLY_API_KEY; // 'appId.keyId:keySecret'
const [keyName, keySecret] = apiKey.split(':');
const token = jwt.sign(
{
'x-ably-capability': JSON.stringify({
'room:*': ['subscribe', 'publish', 'presence'],
[`notifications:${req.user.id}`]: ['subscribe'],
}),
'x-ably-clientId': req.user.id,
},
keySecret,
{
expiresIn: '1h',
keyid: keyName,
}
);
res.json(token);
});
// Client-side: Use JWT with Ably
const realtime = new Ably.Realtime({
authUrl: '/api/ably-auth',
authMethod: 'POST',
});
Alternative: Ably Token Requests
JWT is recommended for most use cases, but Ably tokens may be preferred when your capability list is too large for JWT size limits (JWTs must fit within HTTP headers, ~8KB), or when you need to keep capabilities confidential since JWTs can be decoded by clients. The trade-off is that Ably token integration is more involved and doesn't support all JWT functionality like channel-scoped claims and per-connection rate limits.
// Ably's native token system (requires round-trip to Ably servers from backend)
app.post('/api/ably-token', (req, res) => {
const client = new Ably.Rest({ key: process.env.ABLY_API_KEY });
client.auth.createTokenRequest({
clientId: req.user.id,
capability: { 'room:*': ['subscribe', 'publish'] },
}).then(tokenRequest => res.json(tokenRequest));
});
Rules
- Server-side: API key is fine (
{ key: 'appId.keyId:keySecret' }) - Client-side: Always use
authUrlorauthCallback— never embed the API key or pass a statictokendirectly (it won't auto-refresh on expiry) - Set
clientIdif you need presence features — it's required for presence - Use capabilities to restrict what channels and operations each client can access
4. Channel Design
Channels separate messages into topics. Get the naming right early — it's hard to change later.
Rules:
- Use
:as a hierarchy separator:chat:room-123,orders:user-456 - Channel names are case-sensitive
- Don't create one channel per message — channels are long-lived topics
- Use rules in the dashboard to apply settings (e.g., persistence, push notifications) to groups of channels
Common patterns:
chat:room-{roomId} # Chat rooms
notifications:user-{userId} # Per-user notifications
cursors:doc-{docId} # Collaborative editing
events:{eventType} # Event streaming
conversation:{sessionId} # AI Transport sessions
5. Connection Management
The Realtime SDK manages reconnection automatically. Don't fight it.
Rules:
- Don't manually reconnect — the SDK handles transient failures with exponential backoff
- Listen to connection state changes to update UI:
realtime.connection.on('connected', () => { /* online */ });
realtime.connection.on('disconnected', () => { /* temporarily offline */ });
realtime.connection.on('suspended', () => { /* offline for extended period */ });
- Call
realtime.close()when done (component unmount, page unload, etc.) - Messages published while disconnected are received on reconnection (within the 2-minute recovery window)
- For AI Transport (client-side): use channel
rewindto hydrate returning clients with recent messages:
const channel = realtime.channels.get('conversation:123', {
params: { rewind: '2m' } // Replay last 2 minutes on attach
});
6. Presence
Presence tracks which clients are on a channel. Use it for "who's online" features.
Rules:
- Set
clientIdduring auth — it's required for presence - Call
channel.presence.enter()to join,channel.presence.update()to update data,channel.presence.leave()to depart - Use
channel.presence.get()for current members,channel.presence.subscribe()for changes - If a client disconnects ungracefully, Ably removes them after ~15 seconds (not instantly)
- Don't use presence for high-frequency data (cursor positions, typing coordinates) — use channels instead. Presence is for low-frequency state (online/offline, user status)
- For Chat: use
room.presenceinstead of raw channel presence - For Spaces: use
space.enter()/useMembers()hook. Spaces has dedicatedcursorsAPI for cursor positions
7. Chat SDK: Critical Lifecycle
If using @ably/chat, these are the most common mistakes:
Always subscribe before calling attach(). Attaching without subscribing first causes silent message loss — the worst kind of bug.
const room = await chat.rooms.get('my-room');
// WRONG: Attach without subscribing first — messages silently lost
await room.attach();
room.messages.subscribe((msg) => console.log(msg.text)); // may miss messages during attach
// RIGHT: Subscribe first, then attach
room.messages.subscribe((msg) => console.log(msg.text));
await room.attach();
Use the actual API. The Chat SDK does NOT have: threading, read receipts, file attachments, or room.messages.broadcast(). Use room.messages.send() to send messages.
Don't mix Chat and Pub/Sub patterns. If you're using @ably/chat, use room.messages, room.typing, room.reactions — not raw channel.subscribe() / channel.publish().
Built-in behavior you don't need to reimplement:
- Typing indicator frequency control is handled by the SDK automatically
- Room reactions delivery is handled by the SDK
- Message ordering and history pagination are built-in
8. React Integration
Ably provides React hooks for each product:
| Product | Package | Key Hooks |
|---|---|---|
| Pub/Sub | ably/react |
useChannel, usePresence, useConnectionStateListener |
| Chat | @ably/chat/react |
useMessages, useTyping, usePresence, useRoomReactions |
| Spaces | @ably/spaces/react |
useMembers, useCursors, useLocations, useLocks |
Critical rule: Don't create a new Ably client on every render. This creates a new WebSocket connection each time — a memory leak.
// WRONG: Creates new connection every render — memory leak
function Chat() {
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
// ...
}
// RIGHT: Create client once, pass via provider
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
function App() {
return (
<AblyProvider client={ably}>
<ChannelProvider channelName="chat:room-1">
<Chat />
</ChannelProvider>
</AblyProvider>
);
}
// ALSO RIGHT: Create in useEffect with proper cleanup
function App() {
const [client, setClient] = useState(null);
useEffect(() => {
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
setClient(ably);
return () => ably.close();
}, []);
// ...
}
9. Spaces and LiveObjects: Built-in Behavior
When using Spaces (@ably/spaces):
- Cursor position batching is built-in — don't implement custom throttling
- Member location tracking is built-in via
space.locations - Component locking handles contention automatically via
space.locks
When using LiveObjects:
- Available data structures are Maps and Counters only (no custom object types)
- Conflict resolution is built-in — don't implement your own
- State recovery after reconnection is automatic
- Prefer the path-based API over the instance API for simplicity
10. LiveSync: Database-to-Frontend
LiveSync connects your database to frontends: Database → ADBC Connector → Ably Channels → Models SDK → Frontend.
Key constraints: PostgreSQL requires logical replication (14+, WAL level change needs restart). MongoDB requires a replica set (no standalone). The Database Connector handles channel mapping automatically once you configure an ingress rule.
Always fetch current LiveSync docs from ably.com/docs/livesync — database permissions and connector configuration change across versions.
11. Production Checklist
Before going to production, verify:
- No API keys in client code — use JWT or token auth via
authUrl/authCallback - Capabilities are scoped — don't grant
{"*":["*"]}to clients; restrict to specific channels and operations - Connection cleanup — call
realtime.close()on unmount/unload to avoid connection leaks - Error handling — listen to
connection.on('failed')and handle auth failures gracefully - Channel detach — detach from channels you no longer need (
channel.detach()) - Message size — messages are limited to 64KB by default; if you're hitting this, split payloads or reconsider message design
- Idempotent publishing — set unique message IDs when exactly-once delivery matters
-
echoMessages: false— set this if publishers don't need to receive their own messages (saves bandwidth and cost) - Rate limits — Ably enforces rate limits (e.g., 50 messages/second per channel, 200 channels per connection). If you're hitting them, check your publish frequency and channel fan-out. Read the limits documentation
Error Handling
Ably error codes can be broad (e.g., 40000, 50000) — always read the error message text, not just the code. Every error code has a help page at https://help.ably.io/error/{code}.
12. Quick Reference
Product docs: Pub/Sub | Chat | Spaces | LiveObjects | LiveSync | AI Transport
Platform SDKs: JavaScript, React, Python, Ruby, Java, Kotlin, Swift, .NET, Go, PHP, Flutter — ably.com/docs/sdks
Ably CLI: Install with npm install -g @ably/cli to publish test messages, subscribe to channels, and verify setup. Run ably --help to discover commands.
For API references, start at ably.com/docs.