pusher-js and the four builds it ships
Pusher Javascript library
At a glance
- What is it?
- pusher-js is the client half of Pusher Channels, covering web browsers, web workers, service workers and Node.js. Its configuration is small, but its build matrix, its dropped default encryption support and its pinned CDN tag are where adopters get caught.
- Who is it for?
- This library suits browser, worker and Node.js applications that only consume Pusher Channels events, and suits nothing that must publish events, sign subscriptions or hold app credentials.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Star history
- pusher/pusher-js2,225 ★ · +2
| Repository | 2026-10-14 | 2026-10-24 |
|---|---|---|
| pusher/pusher-js | 2.2k | 2.2k |
2 Stars in the last 10 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
A consumer library that stops at the subscribe call
pusher-js is the client half of Pusher Channels. It opens a connection, subscribes to named channels, and dispatches the events that arrive to handlers you bind. Everything that sends events or signs a subscription is the server's job, and the project draws that line itself: developers looking for the server library for Node.js are pointed at pusher-http-node rather than trying to extend this one.
That split keeps the surface small. The constructor takes an app key and an options object, and the app key and cluster both come from the Pusher Channels dashboard.
const pusher = new Pusher(APP_KEY, {
cluster: APP_CLUSTER,
});What you get in exchange for that small surface is a runtime split, because the same package resolves to different files depending on where it runs. package.json points its main entry at dist/node/pusher.js, its browser entry at dist/web/pusher.js and its react-native entry at dist/react-native/pusher.js, so a bundler picks the build that matches the target and you never see the difference unless you go looking.
Encrypted channels left the default bundle behind
Encrypted channels are not a constructor option. The crypto primitives they need inflate the bundle, so the default web and worker builds dropped support for them, and getting them back means importing a different entry point or a different script tag.
import Pusher from 'pusher-js/with-encryption';The worker equivalent exists as well, and the CDN publishes a matching file for each variant, so the choice is made at build time rather than at runtime.
<script src="https://js.pusher.com/7.0/pusher-with-encryption.min.js"></script>Nothing about the ordinary import changes if you never touch encryption, which is the trade the project is making: smaller default builds for everyone, one more decision for the people who need end to end encryption inside the client. A bundler config has to be rewritten twice if both variants ship.
The CDN tag reads 7.0 while the package reads 8.6.0
Every script tag and worker import in the README points at version 7.0 on js.pusher.com, while package.json records version 8.6.0. Anyone copying the script tag by hand pins a build that is nine minor versions behind the one a package manager would resolve, and the page gives no hint that the two have drifted apart.
<script src="https://js.pusher.com/7.0/pusher.min.js"></script>The worker path has the same fixed tag.
importScripts('https://js.pusher.com/7.0/pusher.worker.min.js');The Bower route is older still and marked as discouraged, with a script path that reaches into bower_components for the minified web build. Prefer the package manager route for anything you intend to keep, and treat the 7.0 tag as documentation to be checked rather than a version to be pinned deliberately.
React Native is called deprecated yet still gets built
The README carries an explicit notice that React Native support has been deprecated and will soon be removed from the repository, with pusher-websocket-react-native offered as the replacement. The build system has not caught up with that notice. The top level of the repository still holds a react-native directory, package.json still publishes a react-native entry resolving to dist/react-native/pusher.js, and the aggregate make target still builds it.
build_all: web cjs react-native node workerThe package description in package.json still names React Native among the platforms, and the development dependencies still carry @react-native-community/netinfo, the connectivity module that only a mobile runtime would need. So the deprecation is real for consumers and unfinished for maintainers. A team already running React Native through this package can keep going for now, but the removal note means the migration target is the separate SDK rather than a future version of this one.
forceTLS and the auth endpoint shape the options
The options object is passed straight to the constructor, and the private and presence channel cases add an authorization endpoint to it.
const pusher = new Pusher(APP_KEY, {
cluster: APP_CLUSTER,
channelAuthorization: {
endpoint: 'http://example.com/pusher/auth'
},
});Two transport level settings are spelled out. forceTLS takes a Boolean and defaults to true, so the connection is TLS only unless you deliberately turn it off, in which case the library attempts a non-TLS connection first. userAuthentication is an object whose endpoint key points at a server route of your own that returns the authentication data.
The README is candid that most users have little need to change any of it and defers the full list to the client API guide. That is a reasonable default for a transport wrapper, and it also means the interesting knobs, reconnect policy and the rest of the connection state machine, live in documentation rather than in the repository. Read the channelAuthorization endpoint carefully: it is your server making the request on behalf of the browser, and it is where a private or presence channel decides whether a subscription is allowed.
Service workers have no HTTP fallback to fall back on
When a websocket cannot be established, the client can fall back to HTTP so a channel still receives events. That fallback works in standard web workers. It does not work in service workers, because the XMLHttpRequest API is unavailable there, and no other transport is wired in.
For an application deployed as a service worker this is not a tuning problem but a hard boundary: push events and long polling simply cannot be substituted for the missing websocket. The project names the missing piece, stating that fallbacks using fetch could be added if there is demand, which puts the decision in users' hands and leaves the gap open today.
A second boundary sits alongside it. The web worker implementation is not compatible with Internet Explorer, which narrows the platforms where the worker entry point can be used at all. Neither limitation is spelled out in the constructor options, so they only surface when a deployment target forces the issue.
Five webpack configs behind a small set of make targets
The Makefile is the clearest map of what this package actually is. Its aggregate target runs five separate builds in order, one per runtime: web, cjs, react-native, node and worker. Each build invokes webpack with its own config file, config.web.js for browsers, config.cjs.js for CommonJS, config.react-native.js, config.node.js and config.worker.js. Passing MINIMIZE=false produces an unminified build for inspection, and the web, CommonJS and worker targets each run a second pass with INCLUDE_TWEETNACL=true, which is the mechanism behind the encryption builds described earlier.
INCLUDE_TWEETNACL=true MINIMIZE=false node_modules/webpack/bin/webpack.js --config=webpack/config.web.jsA target named json2 copies src/runtimes/web/dom/json2.js into dist/web and runs uglifyjs over it, a reminder that this package is assembled by several pipelines. TypeScript declarations ship in the repository alongside index.d.ts and a types directory, and have been provided since v5.1.0, so editors and type checkers get channel types such as PresenceChannel without a separate types package.
Version 8.6.0 is what package.json records, dated 2026-07-23, and the preceding tags sit at 8.5.0 from 2026-04-01 and 8.4.3 from 2026-03-19. The tooling behind those builds is pinned in the same file, with webpack driven through the Makefile, prettier at ^3.4.2 for the format and check-format scripts, and jasmine at ^4.5.0 feeding karma at ^6.4.4.
npm test exits with an error before any test runs
package.json defines the test script as a placeholder that prints an error saying no test is specified and exits with status 1. Running npm test therefore fails immediately, and no spec executes.
That reads badly next to the development dependencies, which include jasmine, karma, karma-chrome-launcher, karma-firefox-launcher, karma-safari-launcher, karma-opera-launcher and karma-browserstack-launcher, along with several reporting plugins. The Makefile also carries a target that builds the CommonJS bundle and then runs jasmine against a config file under the spec directory.
node_modules/.bin/jasmine --config=spec/config/jasmine/cjs.jsonSo the test infrastructure exists, it is just wired to make targets and browser runners rather than to the npm script most contributors try first. A contributor who runs npm test sees a red result that says nothing about the code, and has to read the Makefile to find the entry point that actually starts the CommonJS suite.
Editorial conclusion
This library suits browser, worker and Node.js applications that only consume Pusher Channels events, and suits nothing that must publish events, sign subscriptions or hold app credentials. Before adopting it, check four things: whether your build needs encrypted channels, which now require the separate with-encryption entry points, whether you depend on the HTTP fallback inside a service worker, where XMLHttpRequest is unavailable, and whether a CDN tag pinned to 7.0 still matches the 8.6.0 recorded in package.json.
Frequently asked questions
Does pusher-js still support React Native?
The README states that React Native support is deprecated and will soon be removed, pointing users to pusher-websocket-react-native. The repository has not finished that removal: package.json still ships a react-native entry pointing at dist/react-native/pusher.js and the Makefile still has a react-native target.
How do I install pusher-js in a web application?
With any NPM-compatible package manager, run yarn add pusher-js and then import Pusher from 'pusher-js'. A script tag from js.pusher.com is also given, but that URL is pinned to version 7.0 while package.json records 8.6.0.
Why does my encrypted channel not work after upgrading?
The default web and worker builds no longer support encrypted channels, because the encryption primitives inflate the bundle size. Import from pusher-js/with-encryption, or use the pusher-with-encryption script and worker files on the CDN.
Official sources
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.
[](https://hysenlabs.com/projects/pusher-pusher-js)Star history chart for your README
[](https://hysenlabs.com/projects/pusher-pusher-js)Was this useful?