Skip to main content
Bun provides a fast, cross-platform glob matcher for finding files using wildcard patterns. It’s built in Zig and optimized for performance.

Basic Usage

import { Glob } from "bun";

// Create a glob pattern
const glob = new Glob("**/*.ts");

// Scan for matching files
for await (const file of glob.scan(".")) {
  console.log(file); // "src/index.ts", "test/util.ts", etc.
}

Glob Patterns

Wildcards

// * matches any characters except /
const glob = new Glob("*.txt");
// Matches: file.txt, data.txt
// Not: dir/file.txt

// ** matches any characters including /
const glob2 = new Glob("**/*.js");
// Matches: file.js, src/index.js, src/lib/util.js

// ? matches single character
const glob3 = new Glob("file?.txt");
// Matches: file1.txt, fileA.txt
// Not: file.txt, file10.txt

Character Classes

// [abc] matches any character in brackets
const glob = new Glob("file[123].txt");
// Matches: file1.txt, file2.txt, file3.txt

// [a-z] matches range
const glob2 = new Glob("[a-z]*.js");
// Matches: app.js, utils.js
// Not: App.js, 1.js

// [!abc] matches any character not in brackets
const glob3 = new Glob("*[!0-9].txt");
// Matches: data.txt, fileA.txt
// Not: file1.txt, data5.txt

Braces

// {a,b,c} matches alternatives
const glob = new Glob("*.{js,ts,jsx,tsx}");
// Matches: index.js, App.tsx, util.ts

// Can be nested
const glob2 = new Glob("{src,test}/**/*.{js,ts}");
// Matches: src/index.js, test/util.ts

// Can contain multiple patterns
const glob3 = new Glob("file.{txt,md,json}");
// Matches: file.txt, file.md, file.json

Negation

// ! at start negates pattern
const glob = new Glob("!*.test.js");
// Matches everything except test files

Scanning Files

Async Iteration

Most efficient for large directories:
const glob = new Glob("**/*.js");

for await (const file of glob.scan("./src")) {
  console.log(file);
}

Array Result

Collect all matches:
const glob = new Glob("**/*.ts");
const files = await Array.fromAsync(glob.scan("."));
console.log(files);
// ["src/index.ts", "src/util.ts", ...]

Sync Scanning

const glob = new Glob("*.json");
const files = glob.scanSync(".");
// Returns array of matches

Scan Options

Current Working Directory

const glob = new Glob("*.js");

// Scan from specific directory
for await (const file of glob.scan("/path/to/dir")) {
  console.log(file);
}

// Use options object
for await (const file of glob.scan({ cwd: "./src" })) {
  console.log(file);
}

Dot Files

Include hidden files:
const glob = new Glob("*");

// Exclude dot files (default)
for await (const file of glob.scan({
  cwd: ".",
  dot: false,
})) {
  console.log(file); // Won't include .gitignore, .env, etc.
}

// Include dot files
for await (const file of glob.scan({
  cwd: ".",
  dot: true,
})) {
  console.log(file); // Includes .gitignore, .env, etc.
}

Absolute Paths

Return full paths instead of relative:
const glob = new Glob("**/*.js");

for await (const file of glob.scan({
  cwd: "./src",
  absolute: true,
})) {
  console.log(file); // "/Users/me/project/src/index.js"
}

Only Files

Exclude directories from results:
const glob = new Glob("**/*");

for await (const file of glob.scan({
  onlyFiles: true, // default
})) {
  console.log(file); // Only files, no directories
}

for await (const path of glob.scan({
  onlyFiles: false,
})) {
  console.log(path); // Includes directories too
}
const glob = new Glob("**/*.js");

for await (const file of glob.scan({
  followSymlinks: true,
})) {
  console.log(file); // Follows symlinked directories
}
const glob = new Glob("**/*");

try {
  for await (const file of glob.scan({
    followSymlinks: true,
    throwErrorOnBrokenSymlink: true,
  })) {
    console.log(file);
  }
} catch (err) {
  console.error("Broken symlink:", err);
}

Pattern Matching

Match Method

Test if a path matches the pattern:
const glob = new Glob("**/*.ts");

console.log(glob.match("src/index.ts")); // true
console.log(glob.match("src/index.js")); // false
console.log(glob.match("README.md")); // false

Multiple Patterns

Match against multiple globs:
const patterns = [
  new Glob("**/*.ts"),
  new Glob("**/*.tsx"),
  new Glob("!**/*.test.*"),
];

function matches(path: string): boolean {
  let result = false;
  
  for (const glob of patterns) {
    if (glob.pattern.startsWith("!")) {
      // Negation
      if (glob.match(path.slice(1))) {
        result = false;
      }
    } else {
      // Inclusion
      if (glob.match(path)) {
        result = true;
      }
    }
  }
  
  return result;
}

console.log(matches("src/index.ts")); // true
console.log(matches("src/index.test.ts")); // false

Common Patterns

All TypeScript Files

const glob = new Glob("**/*.{ts,tsx}");

All JavaScript Variants

const glob = new Glob("**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}");

Exclude node_modules

const glob = new Glob("**/*.js");

for await (const file of glob.scan(".")) {
  if (file.includes("node_modules")) continue;
  console.log(file);
}
Or use a function to filter:
const files = [];
for await (const file of glob.scan(".")) {
  if (!file.includes("node_modules")) {
    files.push(file);
  }
}

Specific Directories

// Only src and test directories
const glob = new Glob("{src,test}/**/*.ts");

// Exclude build directories
const glob2 = new Glob("**/*.js");
for await (const file of glob2.scan(".")) {
  if (file.match(/\/(dist|build|out)\//)) continue;
  console.log(file);
}

Test Files

// Files ending in .test or .spec
const glob = new Glob("**/*.{test,spec}.{ts,js}");

// Files in __tests__ directory
const glob2 = new Glob("**/__tests__/**/*.{ts,js}");

Config Files

const glob = new Glob("*.config.{js,ts,json}");
// Matches: vite.config.js, tsconfig.json, etc.

Performance

Fast Scanning

Bun’s glob is highly optimized:
const glob = new Glob("**/*.js");

const start = performance.now();
let count = 0;
for await (const _ of glob.scan(".")) {
  count++;
}
console.log(`Found ${count} files in ${performance.now() - start}ms`);
// Typically <50ms for 10,000 files

Comparison

  • Bun Glob: ~200,000 files/sec
  • fast-glob: ~100,000 files/sec
  • node-glob: ~50,000 files/sec

Optimization Tips

  1. Use specific patterns
    // Faster - specific directory
    const glob = new Glob("src/**/*.ts");
    
    // Slower - scans everything
    const glob2 = new Glob("**/*.ts");
    
  2. Filter early
    // Better - filter while scanning
    for await (const file of glob.scan(".")) {
      if (!file.includes("node_modules")) {
        process(file);
      }
    }
    
    // Worse - collect then filter
    const all = await Array.fromAsync(glob.scan("."));
    const filtered = all.filter(f => !f.includes("node_modules"));
    
  3. Use async iteration
    // Better - streams results
    for await (const file of glob.scan(".")) {
      process(file);
    }
    
    // Worse - buffers all in memory
    const files = await Array.fromAsync(glob.scan("."));
    files.forEach(process);
    

Use Cases

Build Tools

import { Glob } from "bun";
import { build } from "./builder";

const glob = new Glob("src/**/*.ts");

for await (const file of glob.scan(".")) {
  await build(file);
}

File Watching

import { watch } from "fs";
import { Glob } from "bun";

const glob = new Glob("src/**/*.ts");

const watcher = watch("src", { recursive: true });

for await (const event of watcher) {
  if (glob.match(event.filename)) {
    console.log(`Changed: ${event.filename}`);
    // Rebuild...
  }
}

Testing

import { Glob } from "bun";

// Find all test files
const glob = new Glob("**/*.test.{ts,tsx}");
const testFiles = await Array.fromAsync(glob.scan("."));

// Run tests
for (const file of testFiles) {
  await import(file);
}

Linting

import { Glob } from "bun";
import { lint } from "./linter";

const glob = new Glob("**/*.{ts,tsx}");

for await (const file of glob.scan("src")) {
  const issues = await lint(file);
  if (issues.length > 0) {
    console.error(`${file}: ${issues.length} issues`);
  }
}

Cleaning

import { Glob } from "bun";
import { unlink } from "fs/promises";

// Remove all .js files
const glob = new Glob("**/*.js");

for await (const file of glob.scan("dist")) {
  await unlink(file);
}

Advanced Usage

Multiple Include/Exclude

function createMatcher(include: string[], exclude: string[]) {
  const includeGlobs = include.map(p => new Glob(p));
  const excludeGlobs = exclude.map(p => new Glob(p));
  
  return (path: string) => {
    const included = includeGlobs.some(g => g.match(path));
    const excluded = excludeGlobs.some(g => g.match(path));
    return included && !excluded;
  };
}

const matches = createMatcher(
  ["**/*.ts", "**/*.tsx"],
  ["**/*.test.*", "**/node_modules/**"],
);

console.log(matches("src/index.ts")); // true
console.log(matches("src/index.test.ts")); // false

Glob Expansion

Expand pattern to list of files:
async function expand(pattern: string): Promise<string[]> {
  const glob = new Glob(pattern);
  return await Array.fromAsync(glob.scan("."));
}

const files = await expand("src/**/*.{ts,tsx}");
console.log(files);

Custom Scanner

import { Glob } from "bun";
import { stat } from "fs/promises";

class FilteredGlob {
  constructor(public pattern: string) {}
  
  async *scan(dir: string) {
    const glob = new Glob(this.pattern);
    
    for await (const file of glob.scan(dir)) {
      const stats = await stat(file);
      
      // Only files larger than 1KB
      if (stats.size > 1024) {
        yield file;
      }
    }
  }
}

const glob = new FilteredGlob("**/*.js");
for await (const file of glob.scan(".")) {
  console.log(file);
}

Error Handling

import { Glob } from "bun";

const glob = new Glob("**/*.ts");

try {
  for await (const file of glob.scan("/nonexistent")) {
    console.log(file);
  }
} catch (err) {
  if (err.code === "ENOENT") {
    console.error("Directory not found");
  } else {
    throw err;
  }
}

Platform Support

Glob patterns work identically on:
  • macOS
  • Linux
  • Windows (uses forward slashes internally)

Windows Paths

// Use forward slashes even on Windows
const glob = new Glob("src/**/*.js");

// Not backslashes
const wrong = new Glob("src\\**\\*.js"); // Wrong!

Build docs developers (and LLMs) love