recurly-js: the browser side of Recurly subscription billing
Simple subscription billing in the browser
At a glance
- What is it?
- recurly.js is the MIT licensed browser library that tokenizes payment details for Recurly, and it deliberately refuses to be self hosted or bundled so that a checkout page always runs the vendor's current, reviewed build. It suits teams selling subscriptions through Recurly, and it is useless if you are not already on Recurly.
- Who is it for?
- recurly-js fits a product team that sells subscriptions through Recurly and wants card capture that never touches raw card data in their own code, since the library's whole job is turning form fields into a token the Recurly API accepts. It is the wrong choice if you are not on Recurly, if your security policy forbids loading third party scripts on a payment page, or if you need to pin the exact build you tested.
- 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 received new commits within the last day.
- 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 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A CDN-only script, and why that is the point
The installation section is a single script tag:
<script src="https://js.recurly.com/v4/recurly.js"></script>And then comes the sentence that decides whether you can use this project. Do not host recurly.js or bundle it using a package manager. In order to ensure you always run the most stable and secure version possible, you must load recurly.js from the vendor CDN.
That instruction is unusual and it is the defining constraint of the library. The project ships an npm package, the manifest names the package recurly.js and lists it on npm with a version, and it is a MIT licensed open source repository. Yet the supported integration path is the CDN, and bundling your own copy is explicitly discouraged.
The reasoning is a supply chain one, and it is a good one. recurly.js sits on a payment page. If every merchant pinned their own copy, then a security fix in the library would only reach sites that chose to update, and a compromised release would sit in thousands of build pipelines and node_modules trees indefinitely. Loading from the origin means there is exactly one build in production, and the vendor can rotate it.
The cost of that choice is real and should be weighed. Your checkout page now depends on a third party origin being reachable, your page loads a script you do not control, and your Content Security Policy has to allow that origin. A team with a strict policy about third party scripts on payment pages will not be able to use this, and that is a legitimate disqualification rather than a detail.
The project homepage is js.recurly.com, and the documentation is on the Recurly developer site, with a separate examples repository for client-side and server-side implementations.
What the library is actually for
The description is simple subscription billing in the browser. What that means in practice is payment tokenization, and it is worth being precise about the boundary because that boundary is the security model.
The library runs in the customer's browser and reads the payment fields. What it sends back to your server is a token, not a card number, and the server exchanges that token for a charge through the Recurly API. Raw card details never travel to your server and never land in your logs.
The dependency list in the manifest shows the shape of the client side work. bowser and component-find are for browser detection and feature checks. tabbable and string-squish are accessibility and text handling helpers, and tabbable in particular is what a payment form needs so that keyboard users can move through the fields in a sensible order. currency-symbol-map and to-slug-case handle display formatting, deep-filter and intersect are data utilities, and load-script is the loader for the script tag itself. jsonp is present, which is how the older integrations talk to the Recurly API directly from the browser.
The stack is deliberately small and old in places. component-clone, component-each and component-emitter are the small component primitives, and the promise package is a polyfill. The engines field requires Node 18 or newer for the build toolchain, while the browserslist field targets the last one version and more than one percent, which is a much older browser policy than a modern library would set.
The types entry in the manifest points at a types directory, and the repository has a types folder alongside a test/types folder, with a dedicated make target that runs dtslint against them. So the library is written in JavaScript but ships TypeScript definitions, and those definitions are checked in CI.
A repository built around two test runners
The Makefile is the best description of how this project works, and it shows a codebase that is simultaneously a library, a browser application and a type definition package.
The build is esbuild driven, through scripts/esbuild/build.js, producing build/recurly.js and then build/recurly.min.js by running the same script with --minify. The src variable collects index.js plus every .js and .css file outside build and node_modules, so the library is assembled from the whole tree rather than a single file.
The test story is layered. The test target runs test-unit and test-e2e. The unit tests use the web test runner, with @web/test-runner, and there are separate configuration files for local use and for CI. The end to end tests use WebdriverIO, again with separate local and CI configuration files, and there is a visual-service package in the dev dependencies, which points to screenshot comparison in the browser tests.
The type tests are separate again, with dtslint run over test/types and over the types directory itself. The manifest also lists @definitelytyped/dtslint, which is the DefinitelyTyped linting tool, so the type definitions are held to the same standard as a community DefinitelyTyped package.
There is a BrowserStack integration for the end to end tests, with @wdio/browserstack-service and @web/test-runner-browserstack, plus a public build badge. The visual service and the BrowserStack combination tells you the project checks that the rendered checkout looks right, not only that it behaves right, which for a payment UI is a meaningful part of the test surface.
Running the dev server in Docker, with Chromium and a legacy OpenSSL flag
The development build server is started with a single command:
docker compose upAnd if you have trouble with the build, the README suggests trying make clean.
The compose file builds from the repository root, maps port 8020 on the host to 8020 in the container, bind mounts the working directory into /usr/src/app, and runs make as the command. The Dockerfile is based on node:lts-alpine and installs a long list of system packages: make, chromium, python3, build-base, g++, openssl, cairo-dev, jpeg-dev, pango-dev and giflib-dev. The graphics libraries are there for image processing in the browser tests, and the compiler toolchain is for native modules.
The environment variables are the interesting part. NODE_OPTIONS is set to --openssl-legacy-provider, PUPPETEER_SKIP_CHROMIUM_DOWNLOAD is true, and PUPPETEER_EXECUTABLE_PATH, CHROME_BIN and CHROME_PATH all point at the system chromium-browser. In other words, the container uses the distribution's Chromium rather than downloading a browser, and the openssl legacy provider flag is there to let an older crypto path work under a modern Node.
That flag is a smell worth naming. It exists because something in the dependency tree still asks Node for a legacy crypto algorithm, and on a modern Node without the flag the build fails. It is a pragmatic fix rather than a fix of the underlying cause, and it is the kind of detail that breaks when the toolchain moves on.
The Makefile has a second server target, server-http, which runs the same serve script with the certificate and key environment variables cleared, RECURLY_JS_CERT and RECURLY_JS_KEY. So the dev server serves over HTTPS by default and you have to opt out explicitly.
Version 4.45 and the patch release cadence
The version in the manifest is 4.45.0, and the release tags in the repository are v4.45.0 from 2026-09-25, v4.44.4 from 2026-09-15 and v4.44.3 from 2026-09-15.
Look at the middle tag. v4.44.3 and v4.44.4 were both published on the same day, September 15, 2026, and the gap between them is a patch increment. That pattern of two releases a day apart, followed by a minor bump two weeks later, is the shape of a client library that has to respond to a fast moving API and to browser compatibility issues, and it has a direct consequence for anyone pinning a version.
A patch release should not break you. Semver says so, and the project is published on npm with a normal version field, so the intent is there. But a checkout library that runs in the browser and talks to a payments API has more surface for a patch to touch than a pure utility library, and the .changelog_config.yaml file at the root of the repository is a hint that the changelog generation is customised, which usually means the notes are filtered or grouped for a particular audience.
The repository also carries a CHANGELOG.md and a CONTRIBUTING.md, and the last push was on 2026-10-16, with the repository not archived. So the library is being worked on, roughly three weeks after the 4.45.0 tag, which means whatever you pin is already behind what is in the tree.
There is also a .serena/ directory in the repository root, which is an unusual entry and suggests some tooling is configured for the project beyond the standard lint and test setup.
Where recurly.js is the wrong tool
The first case is a team that is not on Recurly. This is a client for one specific billing vendor's API, and there is no abstraction layer that would make it work with another provider. If you have not already chosen Recurly, this library is not a place to start.
The second case is a page that cannot load third party scripts. Recurly.js is loaded from js.recurly.com by design, which means it is a third party script executing in the context of your payment page, with access to the DOM of that page. Many security teams have a policy that payment pages may not load scripts from origins outside a short allowlist, and a policy that forbids self hosting the file means you cannot satisfy that policy by vendoring a copy. The README is explicit that you must not do that, so there is no compliant fallback inside this project.
The third case is a checkout that needs to be reproducible. Because the file is served from the CDN, two developers on two days may be running two different builds, and a bug that appears on one machine may be a version difference rather than anything in your code. That is manageable for most teams and genuinely painful for a team doing visual regression testing on a checkout flow.
The alternative worth comparing is not another Recurly library, it is a different integration strategy. A server-side checkout, where your own backend talks to the Recurly API and the browser never touches payment fields, removes the third party script from the page entirely and puts the tokenization boundary on your server. Recurly's own documentation links to server-side examples in the examples repository, so both routes are supported. The difference in approach is where the sensitive handling lives, and the browser library route trades a stricter threat model for a checkout that can be embedded more easily in existing pages.
Editorial conclusion
recurly-js fits a product team that sells subscriptions through Recurly and wants card capture that never touches raw card data in their own code, since the library's whole job is turning form fields into a token the Recurly API accepts. It is the wrong choice if you are not on Recurly, if your security policy forbids loading third party scripts on a payment page, or if you need to pin the exact build you tested. Before adopting it, read the note about not hosting or bundling the file yourself, confirm your integration keeps the token handling on the server rather than posting card fields anywhere, and check the CHANGELOG.md and the .changelog_config.yaml so a breaking change in a patch release does not surprise you at checkout.
Frequently asked questions
How do I install recurly.js?
Load it from the vendor CDN with a script tag pointing at https://js.recurly.com/v4/recurly.js. The documentation explicitly says not to host recurly.js yourself or bundle it with a package manager, so that you always run the most stable and secure version.
Can I install recurly-js from npm or bundle it?
The project states you should not host recurly.js or bundle it using a package manager, and that you must load it from the CDN to ensure you always run the most stable and secure version possible.
How do I run the recurly-js development server?
Run docker compose up, which builds the container, maps port 8020 and runs make in the mounted working directory. If the build misbehaves, the README suggests trying make clean.
What tests does recurly-js run?
There are unit tests using the web test runner with separate local and CI configurations, end to end tests using WebdriverIO with a visual service for screenshot comparison, and dtslint type tests over the types directory and the test/types directory.
What licence is recurly-js released under?
It is MIT licensed, with the licence text in LICENSE.md at the repository root. The current version in the manifest is 4.45.0 and the last push was on 2026-10-16.
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/recurly-recurly-js)Was this useful?