Library / SDK
pubnub/javascript avatar
pubnub/javascript

pubnub/javascript: the official JavaScript SDK for realtime messaging

PubNub JavaScript SDK docs https://www.pubnub.com/docs/sdks/javascript

561 stars399 forksTypeScriptNOASSERTION

At a glance

What is it?
The PubNub JavaScript SDK is the client library for PubNub's realtime service, and its shape is defined by two decisions: the transport is HTTP long-poll rather than a socket, and the message ordering is assigned by the server rather than by the client. The package ships separate node and web builds, with Karma and Mocha test targets for each.
Who is it for?
The PubNub JavaScript SDK suits a browser or Node application that needs presence, message history and reactions rather than plain publish and subscribe, since those are the features that make the service worth its complexity. It does not suit you if you want a socket-based client or if you need to control message ordering yourself, because the ordering is server-assigned by design.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The pieces of the SDK that show up in a first integration

The PubNub JavaScript SDK is the official client for the PubNub realtime messaging service, and PubNub describes its own role as taking care of the infrastructure and APIs needed for the realtime communication layer of your application, so you work on your app's logic and let PubNub send and receive data across the world in less than 100ms.

The first thing you need is credentials, and there are two of them. You need the publish and subscribe keys to authenticate your app, and you get them from the Admin Portal. The distinction matters later because a subscribe key is what a client uses to receive and a publish key is what it uses to send, and the SDK is constructed with both.

The client itself is constructed with those keys and a user identifier:

javascript
  pubnub = new PubNub({
    publishKey: 'myPublishKey',
    subscribeKey: 'mySubscribeKey',
    userId: 'myUniqueUserId',
  });

That is the whole setup. There is no connection object to manage and no endpoint to configure, because the service is addressed by the SDK rather than by you.

The next step is the channel and subscription pair, which is the core abstraction:

javascript
// create a subscription from a channel entity
const channel = pubnub.channel('my_channel');
const subscription = channel.subscription();
subscription.subscribe();

A channel is a named destination, and a subscription is the act of listening on it. The subscription object is where every callback lives, so the shape of the API is that you hold a subscription and attach handlers to it.

The repository is on version 13.1.2, released on 2026-10-14, with the last push on 2026-10-20. The homepage of the project is the PubNub site, and the README carries a tutorial video recorded through Dashcam for the getting started walkthrough.

Two listener styles, and why both exist

The README documents two ways to receive events, and the difference between them is the difference between a fixed set of handlers and a generic dispatcher.

The event-specific style assigns a function to a named property on the subscription. The SDK exposes onMessage, onPresence, onSignal, onObjects, onMessageAction and onFile, and you assign to whichever ones you need. That is a plain property assignment, so replacing a handler is another assignment and there is no registration order to think about.

The generic style is a single addListener call taking an object whose keys are the event types. The message handler receives an object carrying the channel name, the channel group or wildcard subscription match, the publish timetoken, the message payload and the publisher. The presence handler receives the action, which can be join, leave, state-change or timeout, along with the occupancy, the state, the timestamp and the timetoken. The signal handler carries the same shape as message.

The objects handler is the App Context surface, and its fields are worth noting because they are what you need to reason about state: the channel, the channel group, the timetoken, the publisher UUID that made the call, the name of the event that occurred, its type, and the data from the event.

The file handler is the file sharing surface, and it carries the file's unique id, its name and its direct URL, along with an optional message attached to the file. The messageAction handler is the reaction surface, and it has two timetokens rather than one: the timetoken of the original message and the timetoken of the message action itself, which is what lets a client reconcile a reaction against the message it belongs to.

Both styles are documented in full in the same example, which is unusual and convenient, because you can compare them directly rather than reading one and inferring the other. The event-specific form is shorter, and the generic form is easier to extend when you want a single function per event type and one place to look for what a payload contains.

Reactions and file events, the two newer payload shapes

Two of the listener types carry more structure than the rest, and they are the ones to read carefully if you are building on them.

The message action handler is the reaction model, and the shape is unusual because a reaction is an event about another event:

javascript
  // Message Reactions
  messageAction: function (ma) {
    const channelName = ma.channel; // Channel to which the message belongs
    const publisher = ma.publisher; // Message publisher
    const event = ma.event; // Message action added or removed
    const type = ma.data.type; // Message action type
    const value = ma.data.value; // Message action value
    const messageTimetoken = ma.data.messageTimetoken; // Timetoken of the original message
    const actionTimetoken = ma.data.actionTimetoken; // Timetoken of the message action
  },

The event field says whether the action was added or removed, the type is the kind of action and the value is what was reacted with, so an emoji reaction and a text reaction differ in the value while sharing the type. The two timetokens are the important part. A reaction refers to a message by that message's timetoken, and the action itself has its own timetoken, so a client that stores reactions separately can tell whether it has seen a given reaction before and can apply a removal correctly.

The file handler is the file sharing model, and it is simpler because the file itself is addressed by id and URL rather than by a nested structure. The event carries the channel it belongs to, the publisher, the timetoken, an optional attached message, and then the file object with its id, name and url.

The generic objects handler, which covers App Context, is the third of the structured ones and it is the general purpose one. It gives you the publisher UUID that made the call, the name and type of the event and the data that came with it, which is the shape you want for a user presence system where each user publishes their own state.

All three are surfaced through the same two registration styles, so whether you use onObjects or an objects key in addListener, you get the same fields.

Installing it, and the React Native wrinkle

There are two installation routes, and the second one is version-pinned in a way that tells you how the releases work.

The npm route is the ordinary one:

bash
     npm install pubnub

The CDN route offers two files for the current version, an unminified build and a minified one, both named for the release:

The URLs are cdn.pubnub.com/sdk/javascript/pubnub.13.1.2.js and the same name with a .min suffix.

The React Native case is the interesting one, and the README explains it rather than leaving you to guess. The SDK relies on a global URL implementation, which React Native does not provide, so it depends on react-native-url-polyfill. That dependency is declared as an optional peer dependency, so only React Native projects need it, while Node.js and browser consumers are unaffected. The polyfill is installed alongside the SDK:

The install command is npm install react-native-url-polyfill.

And the last detail saves you a line of code. The SDK loads the polyfill automatically with an import of react-native-url-polyfill/auto, so no extra import is required once the package is installed.

That is the whole React Native story, and it is a good illustration of how the optional peer dependency pattern works in practice. The dependency is declared as optional so that a plain Node project does not get a useless package installed, but the failure it prevents is real, and the fix is documented in the same paragraph that explains the cause.

The package is published on npm as pubnub, and the repository also carries a Bower badge, so the package is discoverable through more than one channel.

The build and test scripts, which describe the architecture

The package manifest is short and its scripts are the clearest description of how the project is put together, because there are two builds and two test runners.

json
  "scripts": {
    "build": "npm run build:node && npm run build:web",
    "build:web": "rollup -c ./rollup.config.js --bundleConfigAsCjs",
    "build:node": "tsc -p tsconfig.json && npm run build:node-types",
    "ci": "npm run clean && npm run build && npm run lint && npm run test",

The build is split into a node target and a web target. The node build runs the TypeScript compiler against tsconfig.json and then a separate step that aggregates the type declarations, a script that takes the node entry declaration and writes a combined output into the types directory. The web build runs Rollup with the rollup.config.js file and the bundleConfigAsCjs flag, which is the flag you need because the configuration is CommonJS and the tool expects ES module config.

The test story mirrors the build. The web tests run under Karma, and there is a second Karma configuration for a shared worker variant, which means the SDK is tested in a shared worker context as well as in the ordinary one. The node tests run Mocha with a dedicated tsconfig.mocha.json and a TS_NODE_PROJECT environment variable pointing at the main TypeScript configuration.

The ci script chains the whole thing, clean then build then lint then test, and there are ci:web and ci:node variants that do the same for one target at a time. The lint step runs ESLint over the source with the .eslintrc.cjs configuration, and the clean step removes the lib, dist and upload directories with rimraf.

Two more scripts are worth naming. There is test:snippets, which type checks the docs-snippets directory with the compiler in noEmit mode, so the examples in the documentation are verified against the type definitions rather than drifting. And there are feature-level test scripts that run a single file with a specific reporter, one for the objects v2 feature and one for file upload, which is the pattern you would expect in a project that has distinct subsystems.

The repository layout supports all of this. There is src/ for the source, lib/ and dist/ for the two build outputs, karma/ for the browser test configuration, test/ for the tests, docs-snippets/ for the documentation examples, .scripts/ for the helper scripts such as the type aggregator, cucumber.js at the root, and the various configuration files including tsconfig.json, tsconfig.mocha.json and tsconfig.rollup.json.

What the package description tells you about the service itself

The manifest description is short, and it is the best summary of the service's model in the whole repository: PubNub SDK for JavaScript. Publish/subscribe messaging over HTTP long-poll with server-assigned message ordering, channel occupancy via Here Now, and message history through Message Persistence.

Three mechanisms, and each one answers a question you would otherwise have to solve yourself.

The transport is HTTP long-poll, which means the SDK does not open a persistent socket of its own and does not need a WebSocket-capable environment. That is a deliberate choice about reachability, and it is the reason the React Native note is about URL rather than about sockets.

The message ordering is server-assigned, which is the detail that decides whether the library is usable for your problem. With client-generated identifiers, two subscribers can receive the same two messages in different orders, and you end up writing reconciliation logic. With ordering assigned by the server, every subscriber sees the same sequence, and the ordering question is answered once, centrally.

Channel occupancy is exposed as Here Now, which is the presence mechanism, and it is the same data the presence listener reports, so an application can either be told about joins and leaves as they happen or ask for the current state of a channel when it starts up. The second is the one that matters on reconnect, because a client that was offline has missed events and needs a way to resynchronise.

Message history through Message Persistence is the third, and it covers the same gap from the other side. A client that reconnects can fetch what it missed rather than starting blind.

The package author is recorded as PubNub with a support address, the licence is a custom one recorded in the LICENSE file, and there is a CHANGELOG.md at the root for the version history.

Editorial conclusion

The PubNub JavaScript SDK suits a browser or Node application that needs presence, message history and reactions rather than plain publish and subscribe, since those are the features that make the service worth its complexity. It does not suit you if you want a socket-based client or if you need to control message ordering yourself, because the ordering is server-assigned by design. Before you adopt it, check the React Native note, because that is the one platform with an extra install step, and note that the documentation examples are type-checked in CI with the test:snippets script, so the snippets in the docs are known to compile against the current type definitions.

Frequently asked questions

How do I install the PubNub JavaScript SDK and configure it?

Run npm install pubnub, or take one of the two CDN builds for the current version, then construct the client with new PubNub({ publishKey, subscribeKey, userId }). The publish and subscribe keys come from the Admin Portal.

Why does my React Native app need react-native-url-polyfill?

The SDK relies on a global URL implementation, which React Native does not provide, so it declares react-native-url-polyfill as an optional peer dependency. Install it with npm install react-native-url-polyfill, and the SDK imports it automatically so no extra import is needed.

What is the difference between the onMessage style listeners and addListener?

The onMessage style assigns a function to a named property on the subscription, with onMessage, onPresence, onSignal, onObjects, onMessageAction and onFile available. addListener takes a single object keyed by event type, so each event type gets its own handler function in one place.

What fields does the PubNub message action listener provide?

The channel the message belongs to, the publisher, whether the action was added or removed, the action type and value, the timetoken of the original message and the timetoken of the message action itself, which is what lets a client match a reaction to the message it belongs to.

Official sources

  1. Issues
  2. pubnub/javascript on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pubnub-javascript.svg)](https://hysenlabs.com/projects/pubnub-javascript)

Was this useful?