For years, matching src/**/*.js meant installing a package. Node added fs.glob in 22.0.0 and marked it stable in 24.0.0, so a dependency-free build script can now find its own inputs.
import { glob } from 'node:fs/promises';
import { globSync } from 'node:fs';
import path from 'node:path';
for await (const hit of glob('src/**/*.js')) console.log('glob :', hit);
console.log('exclude :', globSync('**/*.js', { exclude: ['src/routes/**'] }));
const dirents = globSync('*', { cwd: 'public', withFileTypes: true });
console.log('types :', dirents.map((d) => `${d.name}:${d.isFile()}`).join(' '));
console.log('matches :', path.matchesGlob('src/routes/users.js', 'src/**/*.js'));glob : src\app.js glob : src\routes\posts.js glob : src\routes\users.js glob : src\models\User.js exclude : [ 'src\\app.js', 'src\\models\\User.js' ] types : img:false index.html:true matches : true
Patterns are always written with forward slashes, but results come back in native form — backslashes on Windows, as above. The async form is an async iterator, not a promise for an array, so a huge tree costs one entry of memory at a time. exclude takes a predicate or an array of patterns (patterns since Node 23.7.0), cwd sets the base, and withFileTypes yields Dirents. Node 26 added followSymlinks (26.1.0) and maxDepth (26.9.0). path.matchesGlob, stable since Node 24.8.0, tests a string against a pattern with no disk access — right for filtering a list you already hold, such as ignore rules applied to watcher events.
| Package | Version | Notes |
|---|---|---|
| node:fs glob | built in | stable, streams results, no dependencies |
| fast-glob 2,827 | 3.3.3 | fastest on large trees, many options |
| tinyglobby | 0.2.17 | small fast-glob replacement, few deps |
| globby 2,649 | 16.2.4 | fast-glob plus .gitignore support |
| picomatch | 4.0.7 | matching only, the engine under the others |
Reach for a package when you need what the built-in lacks. globby (github.com/sindresorhus/globby (https://github.com/sindresorhus/globby 2,649 ), npm i globby) honors .gitignore files, which is what a linter or formatter wants. fast-glob (github.com/mrmlnc/fast-glob (https://github.com/mrmlnc/fast-glob 2,827 )) still wins on very large trees: it walks directories concurrently and prunes branches that cannot match. All of them, Node's included, compile the pattern to a regular expression first; picomatch is that compiler, published on its own.