TL;DR
picomatch is the fast, lightweight glob matcher — parses glob patterns into regexes, zero dependencies, powers micromatch and many other tools. micromatch is the feature-rich glob library — builds on picomatch, adds brace expansion, negation, filtering, and matching utilities. minimatch is the original npm glob matcher — used by npm internally, widely adopted, slower but battle-tested. In 2026: picomatch for the lowest-level regex conversion, micromatch for full-featured glob matching, minimatch if you need npm-compatible behavior.
Key Takeaways
- picomatch: ~60M weekly downloads — fast regex conversion, zero deps, foundation for micromatch
- micromatch: ~65M weekly downloads — full glob library, brace expansion, filtering, negation
- minimatch: ~40M weekly downloads — npm's glob matcher, oldest, most compatible
- picomatch converts glob → regex:
*.js→/^[^/]*\.js$/ - micromatch adds utilities:
mm.match(files, "*.js"),mm.isMatch(),mm.filter() - minimatch is what npm uses internally for package file matching
Glob Syntax Quick Reference
Pattern Matches
* Any file (no path separator)
** Any path (including nested directories)
*.js All .js files in current directory
**/*.js All .js files in any directory
src/** Everything under src/
{a,b}.js a.js or b.js
[abc].js a.js, b.js, or c.js
!(*.test).js All .js except .test.js
?(a|b).js Optional: .js, a.js, or b.js
+(a|b).js One or more: a.js, ab.js, ba.js, etc.
picomatch
picomatch — fast glob-to-regex:
Basic matching
import picomatch from "picomatch"
// Create a matcher function:
const isMatch = picomatch("*.js")
isMatch("app.js") // true
isMatch("app.ts") // false
isMatch("src/app.js") // false (* doesn't match /)
// Globstar (**):
const isDeep = picomatch("src/**/*.ts")
isDeep("src/index.ts") // true
isDeep("src/utils/helpers.ts") // true
isDeep("lib/index.ts") // false
Pattern options
import picomatch from "picomatch"
// Brace expansion (picomatch supports it):
const isAsset = picomatch("*.{js,ts,css}")
isAsset("app.js") // true
isAsset("app.css") // true
isAsset("app.html") // false
// Negation:
const notTest = picomatch("!*.test.js")
notTest("app.js") // true
notTest("app.test.js") // false
// Case insensitive:
const matcher = picomatch("*.JS", { nocase: true })
matcher("app.js") // true
matcher("app.JS") // true
// Dot files:
const withDots = picomatch("*", { dot: true })
withDots(".gitignore") // true (default: false)
Convert to regex
import picomatch from "picomatch"
// Get the compiled regex:
const { regex } = picomatch.makeRe("src/**/*.{ts,tsx}")
console.log(regex)
// → /^src\/(?:[^/]*\/)*[^/]*\.(?:ts|tsx)$/
// Use regex directly:
regex.test("src/index.ts") // true
regex.test("src/components/App.tsx") // true
// Scan pattern for metadata:
const info = picomatch.scan("src/**/*.ts")
// → { prefix: "src/", base: "src", glob: "**/*.ts", ... }
Performance
picomatch performance:
- Compiles glob → regex once, then reuses
- ~3x faster than minimatch for repeated matching
- Zero dependencies — no overhead
- Used internally by: micromatch, chokidar, fast-glob, anymatch
Tip: Create the matcher once, reuse for many files:
const isMatch = picomatch("**/*.ts") // compile once
files.filter(isMatch) // reuse many times
micromatch
micromatch — full glob matching library:
Matching utilities
import micromatch from "micromatch"
const files = [
"src/index.ts",
"src/utils/helpers.ts",
"src/components/App.tsx",
"tests/index.test.ts",
"README.md",
"package.json",
]
// match() — filter files by pattern:
micromatch.match(files, "src/**/*.ts")
// → ["src/index.ts", "src/utils/helpers.ts"]
micromatch.match(files, "**/*.{ts,tsx}")
// → ["src/index.ts", "src/utils/helpers.ts", "src/components/App.tsx", "tests/index.test.ts"]
// isMatch() — check single file:
micromatch.isMatch("src/index.ts", "src/**/*.ts") // true
micromatch.isMatch("README.md", "**/*.ts") // false
// not() — exclude matching files:
micromatch.not(files, "**/*.test.ts")
// → all files except tests/index.test.ts
Multiple patterns
import micromatch from "micromatch"
// Array of patterns — union:
micromatch.match(files, ["**/*.ts", "**/*.tsx"])
// → all TypeScript files
// Negation patterns — exclude:
micromatch.match(files, ["**/*.ts", "!**/*.test.ts"])
// → .ts files excluding tests
// Filter function:
const isSource = micromatch.matcher(["src/**/*.{ts,tsx}", "!**/*.test.*"])
files.filter(isSource)
// → ["src/index.ts", "src/utils/helpers.ts", "src/components/App.tsx"]
Brace expansion
import micromatch from "micromatch"
// Brace expansion:
micromatch.braces("src/{components,utils}/*.ts")
// → ["src/components/*.ts", "src/utils/*.ts"]
// Numeric ranges:
micromatch.braces("file{1..5}.txt")
// → ["file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"]
// Nested braces:
micromatch.braces("{src,lib}/{**/*.ts,**/*.js}")
// → ["src/**/*.ts", "src/**/*.js", "lib/**/*.ts", "lib/**/*.js"]
Advanced patterns
import micromatch from "micromatch"
// Extglob patterns:
micromatch.match(files, "src/!(tests)/**/*.ts") // Not in tests/
micromatch.match(files, "src/?(utils|lib)/*.ts") // Optional utils or lib
micromatch.match(files, "src/+(components)/*.tsx") // One or more components
// POSIX character classes:
micromatch.isMatch("a1.ts", "[[:alpha:]][[:digit:]].ts") // true
// Regex-like patterns:
micromatch.match(files, /\.tsx?$/) // Can also accept RegExp
minimatch
minimatch — npm's glob matcher:
Basic usage
import { minimatch } from "minimatch"
// Single file check:
minimatch("src/index.ts", "src/**/*.ts") // true
minimatch("README.md", "**/*.ts") // false
// With options:
minimatch("SRC/Index.ts", "src/**/*.ts", { nocase: true }) // true
minimatch(".gitignore", "*", { dot: true }) // true
Filter files
import { minimatch } from "minimatch"
const files = [
"src/index.ts",
"src/utils/helpers.ts",
"tests/index.test.ts",
"README.md",
]
// Filter:
files.filter((f) => minimatch(f, "src/**/*.ts"))
// → ["src/index.ts", "src/utils/helpers.ts"]
// Filter with Minimatch class (reusable):
import { Minimatch } from "minimatch"
const mm = new Minimatch("**/*.ts")
files.filter((f) => mm.match(f))
// → ["src/index.ts", "src/utils/helpers.ts", "tests/index.test.ts"]
Negation and options
import { minimatch } from "minimatch"
// Negation with !:
minimatch("test.js", "!*.test.js") // true
minimatch("app.test.js", "!*.test.js") // false
// Options:
minimatch("file.ts", "*.ts", {
nocase: true, // Case insensitive
dot: true, // Match dot files
noglobstar: false, // Allow **
nonegate: false, // Allow ! negation
matchBase: true, // Match basename only (like find)
})
// matchBase — match filename part only:
minimatch("src/deep/nested/app.js", "app.js", { matchBase: true })
// → true (matches basename "app.js")
npm compatibility
minimatch is what npm uses for:
- "files" field in package.json
- .npmignore patterns
- workspace patterns
If you're building npm-related tooling, minimatch ensures
exact behavioral parity with npm's own pattern matching.
Feature Comparison
| Feature | picomatch | micromatch | minimatch |
|---|---|---|---|
| Glob → regex | ✅ | ✅ (via picomatch) | ✅ |
| match(files) | ❌ (single) | ✅ | ❌ (single) |
| Multiple patterns | ❌ | ✅ | ❌ |
| Brace expansion | ✅ | ✅ (+ braces()) | ✅ |
| Negation | ✅ | ✅ (in arrays) | ✅ |
| Extglob | ✅ | ✅ | ✅ |
| POSIX classes | ✅ | ✅ | ❌ |
| Pattern scanning | ✅ (scan) | ✅ | ❌ |
| Dot files | Option | Option | Option |
| Case insensitive | Option | Option | Option |
| Dependencies | 0 | 1 (picomatch) | 1 (brace-expansion) |
| Performance | ⚡ Fastest | ⚡ Fast | Slower |
| Weekly downloads | ~60M | ~65M | ~40M |
When to Use Each
Use picomatch if:
- Need the fastest glob-to-regex conversion
- Building your own matching utilities
- Want zero dependencies
- Only need single-pattern, single-file matching
Use micromatch if:
- Need to filter file arrays by glob patterns
- Want multiple pattern support (arrays with negation)
- Need brace expansion utilities
- Building file system tools, bundlers, or linters
Use minimatch if:
- Need npm-compatible pattern matching behavior
- Building npm/package.json tooling
- Want the most battle-tested glob implementation
- Don't need performance-critical matching
Production Performance and Optimization Patterns
Glob pattern matching is often on the hot path of build tools and file watchers, where the same patterns are applied to thousands of files repeatedly. The most important performance optimization in all three libraries is to compile the pattern once and reuse the resulting function or regex rather than calling the top-level match function with the same pattern string on every invocation. picomatch's design makes this explicit — the function returns a compiled matcher, and calling it repeatedly with different file paths reuses the compiled regex. micromatch's matcher() function similarly returns a compiled filter function. minimatch's Minimatch class allows reuse of the compiled pattern across multiple match() calls. For tools like Vite or webpack that watch directories containing tens of thousands of files, applying unoptimized glob matching on every file system event can measurably slow down the development experience. Benchmarks show picomatch running approximately 3-5x faster than minimatch for repeated single-file checks against the same pattern, which explains picomatch's adoption as the internal engine for fast-glob, chokidar, and micromatch.
TypeScript Integration and Type Definitions
All three libraries ship TypeScript type definitions as of 2026. picomatch's types cover the Options configuration object and the function signatures for the main export and helper methods like makeRe and scan. micromatch's types are more extensive, covering the full utility API including braces, expand, capture, and the various option combinations. minimatch's TypeScript types have improved significantly in recent major versions, with the Minimatch class now properly typed and the MinimatchOptions interface covering all configuration options. One practical consideration is that glob patterns themselves are typed as string in all three libraries, which means TypeScript cannot catch typos or invalid syntax in pattern strings at compile time — pattern validation at runtime (by attempting to compile the pattern and catching errors) is the only defense against invalid glob syntax in dynamically constructed patterns. The picomatch.scan() function is particularly useful for extracting the static prefix from a dynamic pattern, which enables optimization techniques like filtering by directory prefix before applying the more expensive glob match.
Integration with File System Tooling
Understanding how these libraries integrate with the broader Node.js file system tooling ecosystem helps with architecture decisions. chokidar, the most widely used file watcher for Node.js, uses picomatch internally to filter watch events against the patterns you configure — switching from minimatch to picomatch was part of chokidar's performance improvements in version 3. fast-glob, the standard library for high-performance file system globbing (finding files matching a pattern), also uses picomatch internally for pattern matching after it retrieves the directory listing. If you're building a tool that needs to both find files (using fast-glob) and then match new files against the same patterns (for a watch mode), using picomatch directly for the watch-mode matching ensures behavioral consistency with how fast-glob filters its results, since both use the same underlying regex compilation engine. minimatch remains the correct choice when your patterns need to match npm's behavior exactly, since npm itself uses minimatch for the files field and .npmignore processing.
Glob Pattern Security and Regex DoS
Glob patterns are compiled to regular expressions, which introduces the risk of Regular Expression Denial of Service (ReDoS) attacks when glob patterns are constructed from user-supplied input. A malicious pattern like **/a**/a**/a**/a**/a** can cause exponential backtracking in the compiled regex, consuming 100% CPU for seconds or minutes on a typical input. All three libraries have implemented mitigations for the most common ReDoS patterns, but the fundamental risk exists whenever user input is allowed to influence glob pattern construction. For applications that accept user-defined file patterns (IDEs, build configuration UIs, test explorers), glob patterns should be validated against a allowlist of safe syntax elements rather than allowing arbitrary pattern construction. picomatch's scan() function can be used to inspect a pattern's structure before compiling it, providing a limited form of pattern analysis that can detect excessively complex patterns before they're evaluated.
Choosing Between Libraries Based on Ecosystem Position
The decision between picomatch, micromatch, and minimatch should account for the dependency chains already in your project. Checking node_modules/.package-lock.json for which glob libraries are already present — installed as transitive dependencies of other tools — reveals which libraries are already available at zero additional bundle cost. Most projects that use Vite, Rollup, or Babel already have picomatch and micromatch installed transitively, making them the natural choice for any glob matching your own code needs to perform. Projects that use npm workspaces, yarn, or pnpm for monorepo management already have minimatch installed as a dependency of npm's internal resolution logic. Adding micromatch to a project that already has it as a transitive dependency adds no new code to the dependency tree, which is a non-trivial consideration for deployment size optimization in serverless or edge environments where cold start time correlates with bundle size.
Negation Patterns and Complex Filtering
All three libraries support negation patterns (prefixing a pattern with ! to exclude matching paths), but the behavior differs in subtle ways that affect complex filtering scenarios. In micromatch's filter() function, negation patterns are processed after positive patterns — so ['src/**', '!src/test/**'] first selects all files in src/, then removes those in src/test/. The order of negation evaluation matters: in some libraries, a negation pattern earlier in the array can be overridden by a positive pattern later. picomatch's some() and every() utility functions provide alternative approaches when you need to evaluate multiple patterns independently rather than combining them into a single filter pass. minimatch provides the ! prefix negation with the matchBase option for basename-only matching, which is useful for patterns like !*.test.ts that should exclude any test file regardless of directory depth. For build tool configuration where users write .gitignore-style patterns, micromatch's micromatch.isMatch() with negation support is the most directly compatible with how .gitignore processes include/exclude rules, because both use the same last-matching-rule-wins semantics.
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on picomatch v4.x, micromatch v4.x, and minimatch v10.x.
Compare file matching and developer tooling on PkgPulse →
See also: cac vs meow vs arg 2026 and cosmiconfig vs lilconfig vs conf, archiver vs adm-zip vs JSZip (2026).