11 PubNub Alternatives in 2026, by How Much Chat You Have to Build

If you use PubNub for chat, the best alternatives are Stream, Sendbird, CometChat, and TalkJS, as these platforms come with the chat features that PubNub leaves you to build. If you use it as real-time messaging infrastructure, the closest replacements are Ably, Pusher Channels, Firebase, AWS AppSync, and self-hosted Socket.IO or Centrifugo.
That split is also a core reason users leave PubNub. Beyond pricing, building a chat app on top of PubNub means teams end up owning a message list, a moderation Function, a read-state model, and a join policy that chat platforms like Stream ship as settings.
We make Stream, so we're one of the PubNub competitors on this list. To give you something to check, we built one community chat three times, on PubNub, on Stream, and on Pusher Channels, and counted how much each one made us write.
Which PubNub Are You Replacing?
PubNub sells low-latency publish/subscribe messaging, presence, message storage, Functions, and a Chat SDK on top of them, and what you use determines what can replace it.
The vendors on this list fall into three groups by how much of a chat product comes with the SDK:
| Group | What comes with the SDK | What you write | Vendors |
|---|---|---|---|
| Chat platforms | Conversations, members, history, unread counts, typing, receipts, reactions, uploads, moderation, push notifications, and UI components | The setup, a theme, and whatever your product adds on top | Stream, Sendbird, CometChat, TalkJS |
| Pub/sub with a chat SDK | Rooms or channels, typing, reactions, and history; the rest varies by vendor | The UI, moderation, and whatever the SDK lacks or needs switched on | PubNub, Ably |
| Pub/sub only | Channels and fan-out, and on some of them presence | Everything else, from the message table up | Pusher Channels, Firebase, AWS AppSync, Socket.IO, Centrifugo |
PubNub is a pub/sub platform with a Chat SDK on top, which puts it in the middle group. However, the Chat SDK is deprecated for PubNub’s new DataSync method, which PubNub's docs call App Context's successor, and the same docs say Chat SDK apps should use App Context.
Why Look for a PubNub Alternative
Price is a reason. PubNub pricing starts at 98 USD a month for 1,000 MAU on the Starter plan. At 10,000 MAU, the pricing page's calculator estimates Pro at 550 USD a month, and the Pro card itself says "Custom", with volume discounts from 10,000 MAU up. Beyond Starter, the price comes from the calculator's estimate or from sales.
The bigger reason is how much of the chat you write. PubNub's React chat components are archived, and the Chat SDK is headless. Moderation is a Function you write and deploy from the Admin Portal. A mute, a user report, and an unread count each need either App Context or your own code.
PubNub’s publish-subscribe handles real-time data streaming, a live location, a price, or a device reading as readily as a chat message, so if those are at the center of your app, you can keep PubNub for them and move only the chat to a platform like Stream.
A third reason teams give is reliability as usage grows. Dating app Jigsaw migrated to Stream, saying, “While PubNub served us initially, scalability issues became apparent as our user base grew. Connectivity problems were affecting user experience, necessitating a change.” Additionally, MyOutDesk, which moved its chat from PubNub to Stream, reported zero Stream support tickets after the switch, against 10 to 15 on PubNub. In Stream’s benchmark test, 5 million users were connected to a single channel while message send latency stayed under 40 milliseconds. Stream also offers enterprise customers a 99.999% uptime SLA.
PubNub Alternatives at a Glance
Only PubNub and the four chat platforms sell by monthly active users (MAU). Other vendors charge for messages, connection time, or storage (or nothing at all if you host them yourself), so the price at 10,000 MAU depends on how much your users talk.
| Alternative | Group | Best for, compared with PubNub | What you write | At 10,000 MAU |
|---|---|---|---|---|
| Stream | Chat platform | Chat with the SDK, UI components, and moderation from one vendor | Channel types, permissions, block lists, and a theme | Start 399 USD, Elevate 599 USD |
| Sendbird | Chat platform | UI kits on the most platforms, with reporting and operator mutes | The regex for a contact filter | Starter 429 USD, Pro 579 USD |
| CometChat | Chat platform | UI kits with calling built in | A theme; email notifications aren't on Basic | Basic 299 USD, Advanced 399 USD |
| TalkJS | Chat platform | A web inbox on TalkJS's own UI | Threads, and your own report button | Basic 279 USD, with 10,000 MAU included |
| Twilio Conversations | Chat platform, no UI | SMS and WhatsApp replies in the same thread | The UI, the filter, and reporting | 477.50 USD, estimated from the per-user tiers |
| Ably | Pub/sub with a chat SDK | Rooms, typing, and reactions on a pub/sub network | Membership, read receipts, threads, file storage | 29 USD or 399 USD (Not priced on MAUs) |
| Pusher Channels | Pub/sub only | Fan-out for a web app that already has a backend | The whole chat | No MAU price; paid plans by messages a day and concurrent connections, from 49 USD |
| Firebase | Pub/sub only | Teams on Google Cloud that want the database and the sync in one place | The whole chat, as documents and security rules | Usage: storage, reads, and writes |
| AWS AppSync | Pub/sub only | AWS-native teams | The whole chat, on DynamoDB and S3 | Usage: 1 USD per million events plus connection minutes |
| Socket.IO or Centrifugo | Pub/sub only, self-hosted | The open-source PubNub alternative, on servers you run, or plain WebSockets with your own server | The whole chat, plus the hosting | No license fee; Centrifugo PRO is priced on request |
Prices checked October 2026.
If you're assessing PubNub alternatives, every build here ran on a free plan: the Stream Build plan, PubNub's Free plan, and Pusher's Sandbox plan. Firebase and AWS AppSync also have no-cost tiers, and Socket.IO and Centrifugo have no license fee.
Quick Picks
- Pick Stream if you use PubNub mainly for real-time chat and want the SDK, the UI components, and moderation from one vendor. MyOutDesk’s Director of IT and Chief Architect called the move “the decision we should have made the first time.”
- Pick Ably if you want a pub/sub vendor whose chat SDK has rooms, typing, and reactions, and you'll write membership, read receipts, and threads.
- Pick Twilio Conversations if users have to reply by SMS or WhatsApp in the same thread, and you’ll build the UI and the filter.
- Pick Pusher Channels if you already have a backend and only need the fan-out.
- Pick Firebase or AWS AppSync if you're on that cloud and will build chat as data with rules on top.
- Pick Socket.IO or Centrifugo if the messages have to stay on servers you run.
Those picks come from the vendors' docs and pricing pages, and from building the same chat on one platform from each group.
The Build: Same Chat on PubNub, Stream & Pusher
We built on one vendor from each group to show what “how much chat you write” means in practice.
The build is a player community for Lantern Forge, a fictional game studio, on the night its game Tidewreck ships. It has eleven users: admins, community leads, players, and, unfortunately, a spam bot.
The chat has fifteen features:
- Public, read-only, private, and direct channels; joining a public channel; roles; and who's online.
- History, unread and mention counts, a new-messages divider, typing indicators, and read receipts.
- Threads with a threads inbox, quoted replies, reactions, edit and delete, and @-mentions.
- Pinned messages that only staff can pin, polls with one vote per person, images, files, link previews, Giphy, and message search.
- Moderation, with a block list, flags and a review queue, bans, shadow bans, and slow mode.
We built it on Stream, PubNub, and Pusher Channels, representative of the chat platforms and pub/sub tools. We could build the same on any of the platforms, with different feature sets.
On Stream, the UI is Stream React components in the community's own colors. PubNub archived their React chat components this year, and Pusher retired Chatkit in 2020, so the PubNub and Pusher apps share one React UI we wrote, with the same layout and palette as the Stream app.

This is the Stream app as Theo, the community lead, sees #general, with unread badges, read ticks, Juno’s Giphy, and the spam bot’s message that slipped past the block list. The build uses stream-chat for the client and server and stream-chat-react for the UI.
The PubNub build runs on the core SDK with DataSync, Access Manager, Message Persistence, message actions, signals, Presence, File Sharing, and one Function, and it uses PubNub’s own pieces wherever one exists.
The Pusher build uses Channels for what it does, which is fanning events out: one presence channel per chat channel, a lobby presence channel for who’s online, and client events for typing. Everything else, including history, is our Express server and a Postgres database.
Where a Chat Feature Comes From
What's different about building chat on these three platforms is where each feature comes from:
- On Stream, a feature is a switch on a channel type, and the React components draw it.
- On PubNub, a feature is a design you build from primitives.
- On Pusher, a feature is a table and a route on your own server, and Pusher delivers the events.
Threads show the difference. On Stream, replies are one setting on the channel type, and the thread panel, the reply count on the parent, and the threads inbox with an unread count per thread all come with the components:
import { ChatView, Thread, ThreadList } from 'stream-chat-react';
export function ThreadsView() {
return (
<ChatView.Threads>
<div className="tw-layout">
<div className="tw-sidebar">
<div className="tw-sidebar-title">Threads</div>
<ThreadList />
</div>
<ChatView.ThreadAdapter>
<Thread />
</ChatView.ThreadAdapter>
</div>
</ChatView.Threads>
);
}
That's #bug-reports on Stream, with Priya's pinned known issues, Oskar's screenshot with four 👍, and the thread open beside it. Unread and mention counts, typing, read receipts, quotes, reactions, edits, mentions, pins, polls, uploads, link previews, Giphy, and search all work the same way: as a type switch and a component in the app. The server-side file for unread counts is only this:
import type { TypeFeature } from './grants.ts';
export const unread: TypeFeature = { config: { read_events: true } };PubNub has no threads. It has messages, message actions, and history. Hence, a thread is a design: a reply is a message on the same channel with its parent's timetoken in the payload, plus a reply message action on the parent so the parent knows it has replies. The threads inbox is computed on the client from the history it has loaded:
export async function linkReply(ctx: Ctx, cid: string, parentId: string, replyId: string) {
await ctx.pn.addMessageAction({ channel: ch.chat(cid), messageTimetoken: parentId, action: { type: 'reply', value: replyId } });
}Every feature on PubNub runs the same way:
- Reactions, edits, deletes, and poll votes are message actions.
- Quotes, mentions, and the poll itself are in the message payload
- Typing and read receipts are signals with a payload we defined.
- Pins are a list on the channel's DataSync record.
None of those designs is wrong, but each one is a decision that every client of yours has to agree on.
On Pusher, there was nothing to design around, because Channels only delivers events. A reply is a row with a parent_id, and the thread is a query:
r.get('/channels/:id/threads/:parent', wrap(async (req) => {
const me = userOf(req);
await requireVisible(String(req.params.id), me);
return messages('m.parent_id = \u00241', [req.params.parent], me, 'order by m.id');
}));The whole Pusher build is like that: a messages table, a route per feature, and Pusher “pushes” live updates to the browser when something changes. It's the most code of the three, but the fewest surprises.
Who Keeps the State
Not everything in a chat app is a message. Who has read what, which messages are pinned, what's in the search index, or who voted are all pieces of state that need to be kept.
Stream keeps all of it on the server. Unread counts, mention counts, the new-messages divider, and read receipts are server facts the components show. Search is an API over everything stored. A poll accepts one vote per person because Stream counts the votes.
PubNub stores the messages themselves, through Message Persistence. It doesn't store whether anyone has read them, so we keep that record. We store the timetoken of the last message each user read on their DataSync membership record. To get an unread count, the client loads the channel's history, takes every message newer than that timetoken, counts them, and counts the ones that mention the user.
When the user reads the channel, the client moves the timetoken forward and sends a signal so everyone else's read receipts update:
export async function unread(ctx: Ctx): Promise<Record<string, Unread>> {
const out: Record<string, Unread> = {};
for (const [cid, info] of ctx.infos) {
if (!info.isMember) continue;
await ensureHistory(ctx, cid);
const since = (await lastRead(ctx, cid)) ?? '0';
const fresh = (await history(ctx, cid)).filter((m) => idCmp(m.id, since) > 0 && m.userId !== ctx.userId && !m.deleted);
out[cid] = { count: fresh.length, mentions: fresh.filter((m) => m.mentions.includes(ctx.userId)).length, lastReadId: since };
}
return out;
}
The unread badges in the channel list, the “Seen by” row under the poll, the vote counts, and the “(edited)” mark all come from our code, which derives them from the history, message actions, and DataSync records. PubNub didn't tell us any of them.
Working state out on the client has limits. Three things in the PubNub build do less than the same features on Stream and Pusher:
- Search covers only the messages the client has already loaded, the newest 100 in each channel. PubNub has no API to search messages on the server.
- A poll allows only one vote per person because of client behavior. When you change your vote, your client removes your old vote before adding the new one. Nothing on PubNub would stop a client that didn't.
- Editing a message doesn't change the stored message. The edit is a message action attached to the original with the new text, and a delete is another action that marks it deleted. Every client has to know to show the edit instead of the original.
They aren't bugs. Each one is as far as PubNub's building blocks go.
On Pusher, none of this comes up, because all the state is in Postgres. Who has read what is a column. Whether you can vote twice is decided by a primary key on the votes table, so the database refuses the second vote. An edit updates the row. What Pusher Channels stores for you is nothing at all. It keeps no history, so a message sent while you were offline reaches you only because our server saved it and hands it over when you come back.
Who Enforces the Rules
This chat has rules. Only staff can post in #announcements. Anyone can join a public channel. Only staff can pin. Five words are blocked. Any player can flag a message, and a moderator can ban someone, shadow ban them so only they see their own posts, or put a channel into slow mode.
The spam scene tests them. A bot posts “🔥 FREE Tidewreck keys 🔥 message me on telegram,” and the block list stops it. Then it posts “cheap doubloons 💰💰 instant delivery, no ban guaranteed → doubloonz dot shop,” which has no blocked words, so it gets through, and Kenji and Amara flag it.
This shows what Theo, the moderator, sees on Stream. The telegram message never appears. The doubloon message does; Amara pings @theo, and Theo opens the moderation queue, finds both messages listed, and bans the bot.
On Stream, all of those rules are enforced by Stream's servers, which we configured. Who can post, join, and pin depends on the channel type permissions. Our app reads those permissions, which is how a player gets a Join button on #bug-reports and a staff-only notice instead of a composer on #announcements.
The block list is a list of five words attached to a moderation policy. Flagging is a built-in message action, and the review queue is built in. Banning, shadow banning, and slow mode are each a single call:
const act = async (userId: string, shadow: boolean) => {
await (shadow ? client.shadowBan(userId, { reason: 'spam' }) : client.banUser(userId, { reason: 'spam' }));
setDone((d) => ({ ...d, [userId]: shadow ? 'shadow banned' : 'banned' }));
};When a rule is broken, Stream sends the refusal. The bot is told, “Message was blocked by moderation policies.” A banned user is told, “Your account is currently banned from chat.” A player who posted twice in slow mode got an error that named the cool-down period. We changed one default. The moderator role can read the review queue but can't ban across the app, so we added the ban-user permission.
On PubNub, Access Manager controls access, and it only has read and write permissions on a channel. Two of our rules don't fit that.
- The first is “players can react in #announcements but can't post,” because on PubNub a reaction is a message action, and adding a message action needs the same write permission as posting.
- The second is joining a channel. A token can only limit which membership IDs a client can create, and the client chooses the ID, so a client could create a membership pointing to any channel. Joins go through our server instead.
Every rule that needs more than read or write went into a Before Publish Function. We wrote that code, and PubNub runs it on its servers before each message is published.
A flag is a message our client publishes to a mod.flags channel that only moderators can read, and the history of that channel is the review queue. A ban or a slow-mode setting is a message a moderator publishes to a chat.control channel, which the Function saves in its key-value store. Then the Function checks every message against what it has saved:
export default (request) => {
const channel = request.channels[0];
// The publisher's user id is the request's uuid parameter, bound to the Access Manager token.
const publisher = request.params.uuid;
const m = request.message || {};
if (channel === 'chat.control') {
if (!MODERATORS.includes(publisher)) return refuse(request, 403, 'moderators only');
if (m.type === 'ban') return db.set('ban:' + m.userId, { shadow: !!m.shadow, by: publisher }, 525600).then(() => request.ok());
if (m.type === 'unban') return db.removeItem('ban:' + m.userId).then(() => request.ok());
if (m.type === 'slowmode') return db.set('slow:' + m.channelId, m.seconds, 525600).then(() => request.ok());
return request.ok();
}
const cid = channel.slice(5);
if (READONLY.some((id) => cid === id || cid.startsWith(id + '-')) && !STAFF.includes(publisher)) return refuse(request, 403, 'Only the Lantern Forge team posts here');
return db.get('ban:' + publisher).then((ban) => {
if (ban && !ban.shadow) return refuse(request, 403, 'banned by ' + ban.by);
const words = String(m.text || '').toLowerCase().split(/[^a-z0-9]+/);
const hit = BLOCKED.find((w) => words.includes(w));
if (hit) {
const flag = { type: 'flag', reason: 'block list: ' + hit, by: 'block list', message: { channelId: channel.slice(5), userId: publisher, text: m.text } };
return pubnub.publish({ channel: 'mod.flags', message: flag }).then(() => refuse(request, 403, 'Message was blocked by moderation: ' + hit));
}
if (ban && ban.shadow) request.message.shadow = true;
if (MODERATORS.includes(publisher) || m.type !== 'text') return request.ok();
return db.get('slow:' + channel.slice(5)).then((seconds) => {
if (!seconds) return request.ok();
const key = 'last:' + channel + ':' + publisher;
return db.get(key).then((last) => {
const wait = last ? seconds * 1000 - (Date.now() - last) : 0;
if (wait > 0) return refuse(request, 429, 'Slow mode: wait ' + Math.ceil(wait / 1000) + 's');
return db.set(key, Date.now(), 60).then(() => request.ok());
});
});
});
};Two things to know about Functions.
- They only see publishes, so message actions, signals, and file uploads go past them unchecked.
- Only one Before Publish Function can cover a channel pattern, so all of these rules have to live in the same one. We wrote the rules, so the refusal messages here are ours.
On Pusher, our server sees every message before it stores it and sends it out, so it checks the rules there:
export async function checkSend(channelId: string, userId: string, text: string): Promise<boolean> {
const ban = (await db.query('select * from bans where user_id = \u00241', [userId])).rows[0];
if (ban && !ban.shadow) throw httpError(403, 'banned by ' + nameOf(ban.banned_by));
const hit = blockedWord(text);
if (hit) {
await db.query("insert into flags (flagged_by, reason, text, author, channel_id) values ('block list', \u00241, \u00242, \u00243, \u00244)", ['block list: ' + hit, text, userId, channelId]);
throw httpError(403, 'Message was blocked by moderation: ' + hit);
}
const { slow_mode_sec: seconds } = (await db.query('select slow_mode_sec from channels where id = \u00241', [channelId])).rows[0];
if (seconds && !MODERATORS.includes(userId)) {
const last = (await db.query('select at from last_posts where channel_id = \u00241 and user_id = \u00242', [channelId, userId])).rows[0]?.at as Date | undefined;
const wait = last ? seconds * 1000 - (Date.now() - last.getTime()) : 0;
if (wait > 0) throw httpError(429, 'Slow mode: wait ' + Math.ceil(wait / 1000) + 's');
await db.query('insert into last_posts values (\u00241, \u00242, now()) on conflict (channel_id, user_id) do update set at = now()', [channelId, userId]);
}
return !!ban?.shadow;
}The PubNub Function and the Pusher route are the same logic in two places. On PubNub, it runs on PubNub's servers against PubNub's key-value store. On Pusher, it runs on our server against Postgres. On Stream, there was nothing like it to write because the block list, flags, bans, and slow mode are settings and calls, and Stream's servers apply them to every message.
The UI and the Setup
Everything in the Stream videos and screenshots was drawn by Stream's React components. That includes:
- The channel list with its search box and unread badges
- The channel header with the online count
- The message list with read ticks
- Typing indicators
- Reactions
- Quotes
- Polls
- Link previews
- Giphy
- The thread panel and the threads list
- The composer with @-mention autocomplete
- Attachments
We set the community's colors on the components' theme variables and added two reaction types, 🎉 and 🐙.
The video is the Stream app while a script plays the other users. Kenji and Rafa type and post, reactions land on the fan art, Felix and Theo vote in the poll, Amara replies in the lore thread, Juno posts a Giphy, and Felix and Kenji @-mention each other. Every on-screen change arrived as a Stream event and was drawn by a Stream component.
We wrote five small things for the Stream app:
- The Join button and the staff-only notice
- A panel listing a channel's pinned messages
- The slow-mode dropdown in the header
- The moderation queue panel
- A search result row
PubNub and Pusher gave us no UI. PubNub's React chat components are archived, and Pusher retired Chatkit in 2020. So we wrote one React UI and used it for both, with the same layout and colors as the Stream app. Every badge, tick, reaction, and thread panel in it is our code reading our own data.

This is the same bug thread in the PubNub app. Reactions, reply count, and the thread come from message actions and the message payload. The pin comes from the channel's DataSync record.
The three platforms also took different amounts of console work before the first message could be sent. On Stream it was two steps: create the app, then copy the key and secret. The channel types, permissions, and block list were all set from code. On Pusher it was three: create the app, copy the keys, and tick "Enable client events" so typing indicators can go browser to browser. Then run a Postgres database.
On PubNub it was eight. Create the app and keyset, copy the keys, then turn on five features one at a time: DataSync, Access Manager, Message Persistence, File Sharing, and Presence. Then deploy the Function package from the portal. Even then, nobody showed as online until we added a Presence Management rule naming the lobby channel, which PubNub's docs say is the default behavior.

This is the same thread in the Pusher app. Oskar's image is stored in a Postgres row, because a Pusher event can't be bigger than 10 KB and Channels stores nothing. The 👍 count, reply count, and pin are also rows.
Where Each Feature Came From
| Feature | Stream | PubNub | Pusher Channels |
|---|---|---|---|
| Channels, roles, and joining | Built in | You build it: DataSync records, tokens from our server, joins through our server, read-only enforced in a Function | You build it: tables, with our server deciding who may subscribe |
| Who's online | Built in | Built in, once a Presence rule names the channel | Built in: the members of a presence channel |
| History and unread counts | Built in | History built in; the read position and the counts are ours | You build it: tables and queries |
| Typing and read receipts | Built in | You build it: signals, plus the read position | You build it: a client event and a table |
| Threads and a threads inbox | Built in | You build it: a parent timetoken in the payload and a message action | You build it: a column and queries |
| Quoted replies | Built in | You build it: in the payload | You build it: in the payload |
| Reactions | Built in | You build it: message actions | You build it: a table |
| Edit and delete | Built in | You build it: message actions, with the stored message unchanged | You build it: row updates |
| Mentions | Built in | You build it: ids in the payload | You build it: ids in the payload |
| Pinned messages | Built in | You build it: a list on the channel's DataSync record | You build it: a table |
| Polls | Built in | You build it: message actions, with one vote per person not enforced | You build it: a table that enforces one vote |
| Images, files, link previews | Built in | File Sharing built in; link previews from our server | You build it: files in Postgres, previews from our server |
| Giphy | Built in | You build it: our server calls GIPHY | You build it: our server calls GIPHY |
| Message search | Built in | You build it: over-loaded history only, no search API | You build it: Postgres |
| Moderation | Built in | You build it: a Function, with flags and bans as messages | You build it: our server checks every send |
The Other 9 PubNub Alternatives
We just wanted to show you the difference between building on a full chat platform like Stream and using pub/sub options like PubNub and Pusher. But other PubNub alternatives might work for your specific use case.
We built our Sendbird comparison on Ably, Sendbird, CometChat, TalkJS, and Twilio, so check out that post to see how they fare.
PubNub vs Ably
Ably is the other pub/sub vendor that also sells a chat SDK on top of its messaging API. Ably Chat gives you rooms, typing indicators, reactions, 30 days of message history on the Standard plan, and a React UI kit that its own docs mark as experimental.
Rooms have no membership, so there is no list of who belongs to one. Read receipts and threads are marked as coming soon. Images and files go to storage you provide. Pricing is a package fee of 29 USD or 399 USD a month, plus 2.50 USD per million messages and 1 USD per million connection minutes and per million channel minutes.
PubNub vs Firebase
Firebase is Google Cloud's option. You store messages in Firestore or the Realtime Database, use Security Rules to decide who can read them, put images in Cloud Storage, and send push notifications with FCM at no charge.
There is no chat SDK and no UI. Conversations, unread counts, typing, threads, and moderation are all documents and functions you design. You pay for usage: 5 USD per GB stored and 1 USD per GB downloaded on the Realtime Database, or per 100,000 reads and writes on Firestore.
PubNub vs AWS
On AWS, we looked at AppSync Events, which AWS lists for live chat and messaging. It gives you channels and an OnPublish hook to filter messages, and nothing else on our list. History is a DynamoDB table you set up, images go in S3, and push is a separate service.
We found no chat guide for AWS IoT Core. Amazon Chime SDK messaging has channels, memberships, and read markers, and sits outside this comparison. AppSync events cost 1 USD per million, plus 0.08 USD per million connection minutes.
PubNub vs Socket.IO
Socket.IO is the open source route for teams that will run their own server. It's a library for sending events between a browser and a server, with rooms and binary payloads, under the MIT license, with clients for the browser, React Native, Java, Swift, Dart, and Kotlin.
Rooms exist only on the server, and a client that disconnects misses whatever was sent in the meantime. So you can build membership, history, and everything else on our list, and you can host it too. Our Pusher build is a fair preview of what that looks like, with your own process replacing Pusher's fan-out.
PubNub vs Centrifugo
Centrifugo is a real-time server you host yourself. It includes a history cache, presence, join and leave events, and client SDKs for the browser, iOS, Android, Flutter, and React Native, more than Socket.IO has built in.
Chat still means your own backend, and Centrifugo's official tutorial builds one with Django and Postgres. Centrifugo PRO includes blocking users, revoking tokens, and push notifications, and it's priced by email. The open source version is Apache 2.0.
PubNub vs Sendbird
Sendbird is a chat platform with UIKits for React, iOS, SwiftUI, Android, Jetpack Compose, React Native, and Flutter, the widest set on this list. Its docs cover group channels, unread counts, read receipts, typing indicators, threads, reactions, and file messages, plus a profanity filter that takes regular expressions, user reporting, and operator mutes.
Browser push notifications aren't covered, and Sendbird's docs say to build them with a webhook. At 10,000 MAU, Starter is 429 USD/month billed annually, and Pro is 579 USD/month.
PubNub vs CometChat
CometChat has UI kits for React, Angular, React Native, iOS, Android, and Flutter, with voice and video calling in the same product. Its rules engine includes phone number and email filters. Push notifications cover Android, iOS, Flutter, React Native, and the web.
At 10,000 MAU, Basic is 299 USD/month billed annually, and Advanced is 399 USD, with overage at 0.10 USD per MAU.
PubNub vs TalkJS
TalkJS is built around its own UI components for React, Web Components, Flutter, and React Native, with no native iOS or Android SDK. Conversations, read receipts, typing indicators, reactions, file sharing, and contact masking are settings you turn on.
Threads aren't built in, and you add reporting as a custom action button. When we built on it, contact masking applied only to messages rendered by TalkJS's components. Basic is 279 USD/month and includes 10,000 MAU. The page's source data also lists an annual price of 239 USD, which we couldn't confirm on the page itself.
PubNub vs Twilio
Twilio Conversations is a chat API with conversations, participants, media, typing, and a read horizon that the client advances itself. A phone number can participate over SMS or WhatsApp, so a text reply lands in the same thread. There is no UI kit. Twilio now calls the product Conversations (classic), with no end-of-life notice on the page.
Moderation is a pre-event webhook that calls your server before a message is published, where you reject or rewrite it. Twilio offers no content filter, report API, or translation. Pricing is per active user. The first 200 are free, then 0.05 USD each up to 5,000, 0.0475 USD up to 10,000, and 0.045 USD up to 20,000; we estimate 477.50 USD a month at 10,000 MAU. SMS and WhatsApp fees are extra, and pricing above 20,000 is custom.
Choosing Your PubNub Alternative
Moving a chat off PubNub requires a plan:
- List what your chat does, then split it into what PubNub provides and what you built on top. The second list is what you want to stop maintaining, and its length decides the group. Long means a chat platform. Short, with non-chat data in the mix, means Ably, or keeping PubNub for that data and moving only the chat.
- Map each thing you wrote onto the new platform's feature. Your read positions become its read state, your reply convention becomes its threads, your Function becomes a block list and a ban call.
- Decide what happens to your UI. On a chat platform, it's the vendor's components in your colors. On a pub/sub platform, you keep the one you have.
- Try the shortlist on a fresh account. Count what has to be switched on before the first message, and test the one or two features you had to design yourself on PubNub, since those are where a platform either has the feature or leaves you building it again.
- Plan the data. You need to export messages in Message Persistence and records in DataSync, and your read positions and pins only exist in the format you chose.
- Price it at your own numbers. PubNub and the chat platforms charge by MAU, and Ably, Pusher, Firebase, and AWS charge by messages, connection time, or storage, so if your users send a lot, those bills grow faster.
Ready to migrate from Pubnub? If Stream is on your shortlist, the Build plan is free, and the chat in this article is a reasonable place to start. It includes four channel types, a block list, a few permission changes, and React components. Everything you designed yourself on PubNub, the read positions, the threads, the moderation, is already there waiting for a channel type to switch it on.
