# randexp.js generates matching strings, and quietly returns empty ones for regexes that cannot match

> randexp turns a regular expression into a random string that satisfies it, which makes it useful for property-based test data. Its documented edge cases are the interesting part: positionals are ignored, dangling back references return an empty string, and unbounded repetition silently becomes min plus 100.

**fent/randexp.js** — Create random strings that match a given regular expression.

- Repository: https://github.com/fent/randexp.js
- Website: http://fent.github.io/randexp.js/
- Stars: 1,868 · Forks: 95
- Language: JavaScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/fent-randexp-js

## One function, and it covers the constructs that usually break generators

The library does one thing: given a RegExp, produce a random string that matches it. The API is a constructor plus `gen()`.

```js
const RandExp = require('randexp');

// supports grouping and piping
new RandExp(/hello+ (world|to you)/).gen();
// => hellooooooooooooooooooo world

// sets and ranges and references
new RandExp(/<([a-z]\w{0,20})>foo<\1>/).gen();
// => <m5xhdg>foo<m5xhdg>

// wildcard
new RandExp(/random stuff: .+/).gen();
// => random stuff: l3m;Hf9XYbI [YPaxV>U*4-_F!WXQh9>;rH3i l!8.zoh?[utt1OWFQrE ^~8zEQm]~tK

// ignore case
new RandExp(/xxx xtreme dragon warrior xxx/i).gen();
// => xxx xtReME dRAGON warRiOR xXX
```

The second example is the one that proves the implementation is doing real work. A back reference `\1` inside the pattern means the generator has to remember what it emitted for group 1 and emit the same string again, which it does: `<m5xhdg>foo<m5xhdg>`.

For one-shot use there is a shorter form, since `new RandExp(...).gen()` gets noisy when you only need one string:

```js
const randexp = require('randexp').randexp;

randexp(/[1-6]/); // 4
randexp('great|good( job)?|excellent'); // great
```

That shortcut also accepts a string pattern with flags, which is the same as constructing the RegExp yourself:

```js
new RandExp('(sun|mon|tue|wednes|thurs|fri|satur)day', 'i');
// is the same as
new RandExp(new RegExp('(sun|mon|tue|wednes|thurs|fri|satur)day', 'i'));
```

And there is a compatibility shim for the older prototype-based syntax, which patches RegExp so you can call `.gen()` directly:

```js
require('randexp').sugar();

/yes|no|maybe|i don't know/.gen(); // maybe
```

That `sugar()` call mutates the global RegExp prototype, which is why it is opt-in rather than default.

## Printable ASCII by default, adjustable by subtracting and adding

The default character range for a wildcard or dot is printable ASCII, which the README shows as a chart. This is a sensible default rather than a technical necessity: generated test data full of control characters produces failure messages nobody can read.

The range is exposed as a `defaultRange` attribute on the instance, with `subtract(from, to)` and `add(from, to)` methods:

```js
const randexp = new RandExp(/random stuff: .+/);
randexp.defaultRange.subtract(32, 126);
randexp.defaultRange.add(0, 65535);
randexp.gen();
```

Subtracting 32 to 126 removes printable ASCII, then adding 0 to 65535 replaces it with the full BMP range, which is why the README's sample output is CJK characters. If you want that changed globally rather than per instance, you can modify `RandExp.prototype.defaultRange` instead.

This matters more than it looks in one direction: if your regex relies on a dot matching, or you use `[^\w]` style negated sets, the characters available determine what your test data covers. Printable ASCII never exercises multi-byte handling. Widening the range is how you would surface encoding bugs in code that consumes the generated string.

## randInt is the seam where you replace the entropy source

The default randomness is `Math.random()`. The README is direct that if you need a seedable or cryptographic PRNG you can override `RandExp.prototype.randInt`, or `randexp.randInt` on a specific instance.

The contract is one line: `randInt(from, to)` takes an inclusive range and returns a randomly selected number within it. That is the whole surface area. Because it is a single function with an inclusive range, a drop-in replacement using `crypto.randomInt` or a seeded generator is straightforward.

Two things follow from this that are worth stating explicitly. First, `Math.random()` is not a security boundary, so anything generated with defaults should be treated as test data and nothing else. Second, because `randInt` is called per character and per range selection, replacing it gives you reproducibility for free: a seeded generator passed through `randInt` makes the entire generated string reproducible, which is what you want when a test failure needs to be replayed.

The prototype-level override is global, so if you set `RandExp.prototype.randInt` for one test you have changed it for the rest of the process. Instance-level is the safer default for test suites that run in the same process.

## Unbounded repetition becomes min plus 100, silently

Repetition tokens with an open upper bound, meaning `*`, `+` and `{3,}`, have an infinite max range. Randexp resolves that by looking at the minimum and adding 100 to get a usable maximum.

That is a heuristic and it is the kind that produces surprising fixtures. A pattern like `/no{1,}/` will generate between 1 and 101 repetitions, which is fine. A pattern like `/(very long prefix){2,}/` multiplies that, because the group body is generated once per repetition. The output length is not what the pattern reads like it would be.

The override is the `max` property, on the prototype or on the instance:

```js
const randexp = new RandExp(/no{1,}/);
randexp.max = 1000000;
```

Under `sugar()` the same property is set on the RegExp object instead:

```js
const regexp = /(hi)*/;
regexp.max = 1000000;
```

If your test suite generates fixtures and then times out or runs out of memory, this heuristic is the first thing to check. Raising `max` makes it worse, so the useful move is usually to set `max` to something small and explicit rather than large.

## Regexes that cannot match produce empty strings, not errors

This is the behaviour to know about, because all three cases fail silently and all three produce plausible-looking output.

Badly placed positionals such as `/a^/` and `/$c/m` are regular expressions that can never match anything. Randexp ignores positional tokens, so the anchor simply does not appear in the output.

Back references to non-existing groups, like `/(a)\1\2/`, are ignored too, and return an empty string for the reference. The same happens when the group exists only after the reference is used, as in `/\1 (hey)/`, which is also ignored.

Custom negated character sets containing two sets that cancel each other out are the third case. The example given is `/[^\w\W]/`, which returns an empty string for the set because it cannot match anything.

None of these raise. That is a deliberate design choice, reasonable for a test-data generator where a strange pattern should still yield something, but it means you cannot rely on an exception to tell you a regex is wrong. If a generated fixture looks empty or suspiciously short, check the pattern against this list before you check your own code.

## MIT, two dependencies, and an eight year gap in the release history

The package is `randexp` at version 0.5.3 in package.json, MIT licensed, authored by fent, with its main entry at `./lib/randexp.js` and TypeScript definitions shipped at `./types/index.d.ts`. The published files list is just `lib` and that definition file, so the browser bundle and the tests are not in the npm tarball.

Two runtime dependencies are listed: `drange` and `ret`.

The release history deserves attention. The most recent release is v0.6.0 on 2026-10-15, and the one before it is v0.5.3 from 2018-07-21. So v0.6.0 is a fresh release on top of a long gap, and the package.json in the repository still reads 0.5.3. That means the version you install from npm and the version in the default branch may differ, and the README's documented behaviours, particularly the `randInt` seam and the `max` heuristic, are worth re-checking against whichever version you pin.

The engine floor is Node 8, which is conservative but harmless. Development tooling is minimal: mocha 7 with nyc for coverage, and dtslint for the TypeScript definitions:

```bash
npm test
```

Security reports go through the Tidelift security contact rather than a GitHub advisory, per the README.

## Conclusion

Adopt randexp if you want property-based test data without hand-writing generators, since it handles sets, ranges, back references and alternation in one call and defaults to printable ASCII so output stays readable in a failure message. Do not adopt it as a source of entropy for anything that needs unpredictability, because the default randomness comes from `Math.random()` and you must override `RandExp.prototype.randInt` yourself to get a seedable or cryptographic generator. Before relying on it, assert that your regex is matchable, since positionals like `/a^/` are ignored and sets that cancel out such as `/[^\w\W]/` return an empty string rather than an error, and set `randexp.max` explicitly if your pattern uses `*`, `+` or `{3,}` and the default of min plus 100 is not what you want.

## FAQ

### How do I install randexp.js?

Run `npm install randexp`. For a browser without a build step, the README points to the minified version on the releases page. TypeScript definitions are included and can be checked with dtslint.

### Why does randexp return an empty string for part of my regex?

Some patterns cannot match anything, and randexp handles them by returning an empty string instead of erroring. These include back references to groups that do not exist and negated sets that cancel out, such as `/[^\w\W]/`. Badly placed positionals like `/a^/` are ignored.

### How do I make randexp use a seeded or cryptographic PRNG?

Override `RandExp.prototype.randInt` or `randInt` on a single instance. The function takes an inclusive `from` and `to` range and returns a random number in it. The default randomness is `Math.random()`, which is not suitable where unpredictability matters.

### How do I limit the length of strings randexp generates for `*` and `+`?

Unbounded repetition has an infinite max, and randexp uses the minimum plus 100. Set `max` on the instance, for example `randexp.max = 1000000`, or change `RandExp.prototype.max` to change it globally.

### Can randexp generate back references?

Yes, for groups that exist before the reference. `/<([a-z]\w{0,20})>foo<\1>/` generates matching text in both positions. References to groups that do not exist, or that are defined only after the reference, are ignored.

## Sources

- [fent/randexp.js on GitHub](https://github.com/fent/randexp.js)
- [License: MIT](https://github.com/fent/randexp.js/blob/master/LICENSE)
- [Project website](http://fent.github.io/randexp.js/)
- [README](https://github.com/fent/randexp.js/blob/master/README.md)
- [Releases](https://github.com/fent/randexp.js/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fent-randexp-js
