node-re2: swapping Node's backtracking regex engine for RE2 without changing the API
Bindings for RE2: fast, safe alternative to backtracking regular expression engines.
At a glance
- What is it?
- node-re2 is a native Node.js addon binding Google's RE2, a C++ engine that matches in linear time, while keeping the RegExp constructor, flags, properties and symbol methods that JavaScript code already uses. It suits a service that parses untrusted input and cannot afford a ReDoS stall, and it is a native module, so it does not run in a browser.
- Who is it for?
- node-re2 fits a Node service that validates or parses untrusted text, where a catastrophic backtracking pattern would be a denial of service, and it does not fit a browser bundle or a project whose patterns rely on backreferences or lookahead, since RE2 lacks both.
- Can I use it commercially?
- Yes. BSD-3-Clause 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 7 days ago.
- What is it written in?
- Mainly C++, 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
Why a linear-time engine changes the risk profile
node-re2 provides Node.js bindings for RE2, a fast, safe alternative to backtracking regular expression engines written by Russ Cox in C++.
The problem it addresses is the one Node's built-in engine has. The built-in regular expression engine can run in exponential time with a special combination: a vulnerable regular expression, and evil input. That is the ReDoS pattern, a Regular Expression Denial of Service, and the consequence is that a single request with a crafted payload can pin a CPU.
The readme suggests two tools for checking whether your patterns are vulnerable, rxxr2 and safe-regex, and then says plainly that neither project is perfect. The reason for that caveat is not that the checkers are wrong, it is that detecting exponential behaviour in a regular expression is undecidable in general, so a checker can only recognise known-bad patterns rather than prove a pattern is safe.
That is exactly why the fix is to change the engine rather than to lint the patterns. node-re2 protects against ReDoS by evaluating patterns in RE2 instead of the built-in regex engine. RE2's guarantee is that matching takes time proportional to the length of the input, not to the number of possible paths through the pattern, and that guarantee holds for every pattern the engine accepts.
The engine itself is Russ Cox's, and the readme points to Regular Expression Matching in the Wild as the place to start learning about it, with more resources on his Implementing Regular Expressions page.
The primary language is C++, the licence is BSD-3-Clause, the default branch is master, and the recent releases are 1.27.0 on 2026-09-26, 1.26.1 on 2026-07-21 and 1.26.0 on 2026-07-08. The last push was on 2026-10-16, so the project is actively moving and the version moves with it.
The RegExp surface, and the two features that are not there
The API compatibility is the point of the package, and it is close to complete with one significant caveat.
RE2 objects are created just like RegExp, with new RE2(pattern[, flags]). The supported flags are g for global, i for ignoreCase, m for multiline, s for dotAll, u for unicode which is always on, y for sticky, and d for hasIndices. The supported properties cover lastIndex, global, ignoreCase, multiline, dotAll, unicode, sticky, hasIndices, source and flags, and the supported methods are exec, test and toString.
The well-known symbol-based methods are supported too, and this is what makes RE2 usable without rewriting call sites.
const re = new RE2('1');
'213'.match(re); // [ '1', index: 1, input: '213' ]
'213'.search(re); // 1
'213'.replace(re, '+'); // 2+3
'213'.split(re); // [ '2', '3' ]
Array.from('2131'.matchAll(new RE2('1', 'g'))); // matchAll requires the g flag
// [['1', index: 1, input: '2131'], ['1', index: 3, input: '2131']]So match, matchAll, search, replace and split all work, and the results are the same shape as the built-in engine produces. Named groups are supported as well.
The caveat is the feature gap. RE2's regular expression language is almost a superset of what RegExp provides, but it lacks backreferences and lookahead assertions. That is not a minor omission. A pattern with a backreference such as (a+)\1, or a lookahead such as (?=foo), will not compile under RE2, and the difference in expressiveness is real: backreferences match non-regular languages, which is precisely what makes RE2 fast in the first place, and lookaheads are how many people write overlapping-match logic.
There is also an extension in the other direction. RE2 provides String-based regular expression methods with swapped receiver and argument, so re2.match(str) works where str.match(re2) would be the built-in form. The constructor also accepts a RegExp directly, honoring all properties, and an RE2 object can be constructed from another RE2 object. That shortcut construction is what lets you drop RE2 into an existing codebase by wrapping the pattern objects rather than rewriting every call.
Unicode mode, binary data, and why browsers are out
Two behaviours differ from the built-in engine in ways that can change results rather than just performance.
The first is that RE2 always works in Unicode mode. Character codes are interpreted as Unicode code points, not as the binary values of UTF-16, and the readme points at the MDN Unicode mode documentation for the semantics. The unicode property is always true, and the readme refers to RE2.unicodeWarningLevel for details.
This matters for anyone whose patterns assume UTF-16 code units. A pattern written with a surrogate-pair assumption, or one relying on the . character matching a single code unit rather than a code point, behaves differently under RE2. It also means the u flag is effectively always on, so a pattern that the built-in engine would reject as malformed under u may be accepted here.
The second is binary data support. RE2 can work with binary data directly, with Node.js Buffers, typed arrays, DataView, ArrayBuffer and SharedArrayBuffer. The stated benefit is reducing overhead and making processing of long files fast, which is the case where avoiding a string conversion per chunk is the difference between a scan that keeps up with a file and one that does not.
The limitation is architectural rather than behavioural. The project is a C++ addon built with nan, and it cannot be used in web browsers. The nan dependency is worth noting for anyone tracking the ecosystem, since nan is the older native addon interface and node-gyp with it means the addon builds against Node's own headers rather than against the newer Node-API. The package metadata confirms this with nan as a runtime dependency alongside node-gyp.
So the audience is server-side Node. If your regex work happens in a browser bundle, this package is not for you, and no amount of configuration will change that.
Installing it, and the prebuilt binary cache
The install path is npm, and the important detail is that most users never compile anything.
The install script in the package metadata tries the cache first and only falls back to a local build. It runs install-from-cache with the artifact build/Release/re2.node, a host variable RE2_DOWNLOAD_MIRROR, a skip-path variable RE2_DOWNLOAD_SKIP_PATH, a skip-ver variable RE2_DOWNLOAD_SKIP_VER and a force-build variable RE2_DOWNLOAD_FORCE_BUILD, and if that fails the script runs node-gyp -j max rebuild.
So there are two practical routes to a working install. The default is to download a prebuilt binary from the GitHub release cache, and the fallback is compiling the addon locally with node-gyp. The three environment variables give you the controls: force a build instead of downloading, skip the download for a specific path, or skip a specific version.
The build scripts themselves are the standard node-gyp set, with build and rebuild for release and build:dev and rebuild:dev for a debug build, plus clean and clean-build. The build uses -j max, so it saturates the machine's cores.
Two supporting scripts are worth knowing about. The verify-build script is node scripts/verify-build.js, which is presumably a check that the built artifact matches expectations. And there is a save-to-github script that publishes build/Release/re2.node to the GitHub cache, which is how the prebuilt binaries get there in the first place, with the prepublishOnly script running hash-github-cache --write to record the hash.
The bundled benchmark is the way to see the difference yourself, and the readme notes that node-re2 has to be built first.
npx nano-bench bench/bad-pattern.mjsThe benchmark uses nano-benchmark, which is in the devDependencies alongside tape-six for testing, prettier for formatting and typescript for the type checks. The script name says bad-pattern, so it is presumably measuring the case where the built-in engine degrades and RE2 does not.
The repository, the type definitions and the documentation files
The top-level entries show a project that has grown past a single binding file.
There is re2.js as the main entry point and re2.d.ts as the type definitions, with the package metadata declaring type as commonjs, main as re2.js and types as re2.d.ts. The native side is in lib/, the build configuration is binding.gyp, and vendor/ holds the vendored RE2 sources, with .gitmodules present to manage them as a submodule.
The test suite is split by how it runs. The test script is tape6 --flags FO, with test:seq running tape6-seq and test:proc running tape6-proc. There are separate tests/ and ts-tests/ directories, the latter checked by the ts-check script which runs tsc --noEmit, and the tsconfig.json at the top level. The lint script runs prettier over the JavaScript, TypeScript, tests and bench directories.
Two entries stand out as unusual. There is a wiki/ directory in the repository, and the readme points to it as the place where all documentation lives alongside the README itself, with an index and a separate search page hosted at uhop.github.io.
And there are llms.txt and llms-full.txt, both of which are listed in the published files array alongside the binding, the lib directory, the type definitions and the scripts directory. So the package ships a machine-readable documentation index to npm along with the code. That is a deliberate distribution decision: an installing tool gets the documentation in a form it can read, without a separate fetch.
The documentation arrangement also explains why the repository contains ARCHITECTURE.md, AGENTS.md and CLAUDE.md. AGENTS.md and CLAUDE.md are the instruction files for coding agents, and llms.txt is the curated index of the same content, so the project is treating its documentation as something to be consumed by several kinds of reader at once.
For a project that has been stable for years, the version numbering tells a useful story: 1.26.0 and 1.26.1 in July 2026, then 1.27.0 in September. Regular patch releases inside a minor, and a minor when something changes that callers need to know about.
Editorial conclusion
node-re2 fits a Node service that validates or parses untrusted text, where a catastrophic backtracking pattern would be a denial of service, and it does not fit a browser bundle or a project whose patterns rely on backreferences or lookahead, since RE2 lacks both. Before switching, audit your existing patterns for those two features and check the binary cache, because the install script downloads a prebuilt artifact by default and falls back to node-gyp rebuild only when that download is unavailable or the platform is not covered.
Frequently asked questions
What problem does node-re2 solve?
The built-in Node.js regular expression engine can run in exponential time with a vulnerable pattern and crafted input, which is a Regular Expression Denial of Service. node-re2 protects against that by evaluating patterns in RE2, which matches in time proportional to the input length, instead of the built-in backtracking engine.
Which regular expression features does node-re2 not support?
RE2's language is almost a superset of RegExp's but lacks backreferences and lookahead assertions, so patterns using either will not work. RE2 also always runs in Unicode mode, treating character codes as code points rather than UTF-16 code units.
Can I use node-re2 with Buffers and typed arrays?
Yes. RE2 can work with binary data directly, including Node.js Buffers, typed arrays, DataView, ArrayBuffer and SharedArrayBuffer, which the readme says reduces overhead and makes processing of long files fast.
Does installing node-re2 require compiling the addon?
Usually not. The install script tries to download a prebuilt artifact with install-from-cache first and only falls back to node-gyp -j max rebuild if that fails. The environment variables RE2_DOWNLOAD_FORCE_BUILD, RE2_DOWNLOAD_SKIP_PATH and RE2_DOWNLOAD_SKIP_VER control that behaviour.
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/uhop-node-re2)Was this useful?