matcher gives you wildcard matching without the regex or glob machinery
Simple wildcard matching
At a glance
- What is it?
- matcher is a small ES module that matches strings against wildcard patterns with a star and a leading exclamation mark, and it does the case folding in a way that is more careful than a naive regular expression. It is MIT licensed, has no runtime dependencies, and its one real subtlety is the interaction between case sensitivity and non-ASCII characters.
- Who is it for?
- Adopt matcher if you need to accept loose user input as a pattern, such as a filter box or a saved view, and you want a matching language small enough that a user can guess. Do not reach for it if you need glob semantics, path matching or anything a regular expression expresses better.
- 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 27 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 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The matching language, which is two characters
matcher is described as simple wildcard matching, and the use case is given in one line: it is useful when you want to accept loose string input and regexes or globs are too convoluted.
The pattern language is deliberately tiny. Use * to match zero or more characters. A leading ! negates the pattern. A backslash escapes the character after it, so \* matches a literal asterisk and \\ matches a literal backslash, and a backslash at the very end of a pattern matches itself.
There are two exported functions. matcher takes inputs and patterns and returns an array of the inputs filtered by the patterns. isMatch takes the same arguments and returns a boolean for whether any of the inputs matches the patterns.
The filtering rule is stated precisely, and it is worth reading rather than inferring. An input string is omitted if there is no pattern at all, if it matches a negated pattern, or if there are non-negated patterns and it fails to match them, all of them when allPatterns is set and at least one otherwise. When every pattern is negated, an input is kept as long as it matches none of them.
The pattern semantics are also unusual in one respect: it matches even across newlines. The documentation gives the example that foo*r will match foo followed by a newline and bar, which is a difference from a regular expression where the dot does not match a newline.
Negation, and what allPatterns actually changes
The default behaviour of matcher is any pattern matches. The allPatterns option flips that to every non-negated pattern must match the same input.
import {matcher} from 'matcher';
// Find text strings containing both "edge" and "tiger" in arbitrary order, but not "stunt".
const demo = (strings) => matcher(strings, ['*edge*', '*tiger*', '!*stunt*'], {allPatterns: true});
demo(['Hey, tiger!', 'tiger has edge over hyenas', 'pushing a tiger over the edge is a stunt']);
//=> ['tiger has edge over hyenas']And the effect on negation is the part that surprises people. A negated pattern always excludes the inputs it matches, whether or not allPatterns is set. So the option only changes the requirement for the positive patterns.
import {matcher} from 'matcher';
matcher(['foo', 'for', 'bar'], ['f*', 'b*', '!x*'], {allPatterns: true});
//=> []
matcher(['foo', 'for', 'bar'], ['f*'], {allPatterns: true});
//=> ['foo', 'for']The first result is empty because foo and for match f* but not b*, and bar matches b* but not f*, so no single input satisfies both positive patterns, and nothing is excluded by the negated pattern either, so the exclusion is not what empties it. The second shows the same list when only one positive pattern is present.
The isMatch variant has the same rule, described as an input having to match every non-negated pattern, not just one.
Case folding, and the characters it will not fold
The caseSensitive option defaults to false, and the description of what that means is the most careful paragraph in the documentation.
When false, it treats uppercase and lowercase characters as being the same, the way a case-insensitive regular expression without the u flag does. So a character whose uppercase form is more than one character, such as the German sharp s, or is ASCII while the character itself is not, such as the dotless i or the long s, and every character outside the Basic Multilingual Plane, only matches itself.
That is a precise statement of JavaScript's own toUpperCase behaviour being unreliable, and it has a concrete consequence. In most locales a case-insensitive regular expression folds a character to its uppercase form and compares, so a character that uppercases to two characters can never match, and a character that uppercases to an ASCII character folds onto the wrong letter. matcher does the same thing, which means the behaviour is predictable and matches what developers already expect from regular expressions, without the surprise of a different library inventing its own rules.
The advice that follows is the part to take seriously: ensure you use this correctly. Files and directories should be matched case-insensitively, while most often, object keys should be matched case-sensitively.
import {isMatch} from 'matcher';
isMatch('UNICORN', 'UNI*', {caseSensitive: true});
//=> true
isMatch('UNICORN', 'unicorn', {caseSensitive: true});
//=> false
isMatch('unicorn', ['tri*', 'UNI*'], {caseSensitive: true});
//=> falseBackslashes, and the Windows path trap
The escaping rules are simple in principle and easy to get wrong in practice, because a backslash in a pattern is an escape character.
import {isMatch} from 'matcher';
isMatch('unicorn', 'uni\\*');
//=> false
isMatch('uni*', 'uni\\*');
//=> true
// Every backslash in a Windows path has to be doubled, otherwise the first one escapes the second.
isMatch('C:\\temp\\x', 'C:\\\\temp\\\\*');
//=> true
isMatch('C:\\temp\\x', 'C:\\temp\\*');
//=> falseThe first two show the escape working: a pattern of uni followed by an escaped asterisk matches the literal text uni with an asterisk in it, and does not match unicorn.
The last two are the trap, and the comment in the documentation says it plainly: every backslash in a Windows path has to be doubled, otherwise the first one escapes the second. A single backslash in the pattern is consumed as an escape for the character after it, so the pattern stops matching what you meant.
This is the cost of having an escape character at all, and it is a real cost when patterns come from users, who will type a Windows path without doubling anything. The documentation does not offer a raw or literal mode, so a caller who wants to match a literal backslash has to construct the pattern carefully.
An ES module with no dependencies and a Node floor of 22
The package metadata is minimal in a way that reflects how small this is.
The package is matcher at version 7.0.0, it is MIT licensed, the author is Sindre Sorhus, and the type is module, so it is an ES module only with no CommonJS build. The exports map has a types entry and a default entry pointing at index.js and index.d.ts, and sideEffects is false so a bundler can drop it entirely if nothing is imported.
The engines field requires node 22 or newer, which is a notable floor for a library with no dependencies. The dev dependencies are just matcha for the benchmark and xo for the linting, and the test script runs xo and then node --test, so the tests use the Node built-in test runner rather than a separate framework. The published files are only index.js and index.d.ts.
The repository is correspondingly small. There is index.js, index.d.ts, test.js, bench.js, a fixture.txt, a readme.md, a license file and the usual configuration. The type definitions are shipped, so the option names and the union types for inputs and patterns are available to TypeScript users.
The benchmark is run with npm run bench.
The neighbours, and where matcher fits
The documentation lists two related projects, and they mark the boundaries of what matcher is for.
matcher-cli is a command line interface for this module, so the same matching language is available as a program for shell scripts. multimatch extends minimatch.match() with support for multiple patterns, which is the glob-family approach to the same problem.
That contrast is the useful one. minimatch implements glob semantics, with brace expansion, extglob patterns, character classes and the rest of the shell convention, and it is the right tool when you are matching file paths. matcher implements a two-character pattern language, and it is the right tool when a person is typing a filter.
The keyword list in the manifest is a fair summary of the intended use: matching, match, regex, regexp, regular, expression, wildcard, pattern, string, filter, glob, globber, globbing and minimatch. The glob and minimatch keywords are there to catch people looking for that, and the filter and wildcard ones to catch people looking for this.
So the decision is mostly about who writes the pattern. A pattern written by a developer against a known set of paths belongs in a glob library. A pattern typed by a user into a search box belongs here, because a star and an exclamation mark is something you can guess.
Editorial conclusion
Adopt matcher if you need to accept loose user input as a pattern, such as a filter box or a saved view, and you want a matching language small enough that a user can guess. Do not reach for it if you need glob semantics, path matching or anything a regular expression expresses better. Before you use it, check three things: whether the patterns come from users who might use a backslash, since a backslash escapes the next character and every backslash in a Windows path has to be doubled, whether you want case-insensitive matching, which is the default and is not the same as a case-insensitive regular expression, and how you want negated patterns to combine with the allPatterns option.
Frequently asked questions
What pattern syntax does matcher support?
Use * to match zero or more characters, a leading ! to negate a pattern, and a backslash to escape the character after it, so \* matches a literal asterisk. A backslash at the very end of a pattern matches itself, and matching works even across newlines.
What is the difference between matcher and isMatch?
matcher takes inputs and patterns and returns an array of the inputs filtered by the patterns. isMatch takes the same arguments and returns a boolean for whether any of the inputs matches the patterns, and with the allPatterns option enabled an input has to match every non-negated pattern rather than just one.
How does the caseSensitive option work in matcher?
It defaults to false, and case-insensitive matching treats uppercase and lowercase as the same, the way a case-insensitive regular expression without the u flag does. So a character whose uppercase form is more than one character, or is ASCII while the character is not, and every character outside the Basic Multilingual Plane, only matches itself.
Why do backslashes in matcher patterns need doubling?
A backslash escapes the character after it, so every backslash in a Windows path has to be doubled, otherwise the first one escapes the second. The pattern isMatch('C:\\temp\\x', 'C:\\temp\\*') is false for that reason, while the doubled version matches.
Is matcher still maintained and what does it depend on?
The last push was on 2026-09-26 with v7.0.0 released the same day, and v6.1.0 the day before. It has no runtime dependencies, ships type definitions, is MIT licensed, and requires node 22 or newer since it is an ES module only.
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/sindresorhus-matcher)Was this useful?