# find-up turns a directory walk into one function call

> find-up is an MIT licensed Node package that walks up parent directories looking for a file or directory, and its design is a small set of decisions, such as returning undefined rather than throwing, matching symlinks by default and offering a symbol that lets a matcher abort the search, that make it a default dependency rather than a niche utility.

**sindresorhus/find-up** — Find a file or directory by walking up parent directories

- Repository: https://github.com/sindresorhus/find-up
- Stars: 642 · Forks: 48
- Language: JavaScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/sindresorhus-find-up

## The problem is finding the file above you

find-up finds a file or directory by walking up parent directories, and the illustration in the README makes the shape of the problem obvious. The tree has a user directory containing unicorn.png and a nested foo/bar directory holding example.js, and the question find-up answers is where the nearest file of a given name is, starting from where you are.

That question comes up whenever a tool has to work from a directory it does not control. A linter that has to find a configuration file, a build tool that has to find the project root, a test runner that has to find a fixture, all of them need the same upward search, and all of them tend to start with a hand-rolled loop over path.dirname.

The installation is one command:

```sh
npm install find-up
```

The package is written by Sindre Sorhus, and the metadata shows the shape of a modern small dependency: the type field is module, so it is ESM only, the exports map points at the types and the default entry, sideEffects is false so a bundler can drop it when unused, and the engines field requires Node 20 or newer. The runtime dependencies are two, locate-path and unicorn-magic, so the walking itself is handled by the former while the latter provides the entry point detection.

## Four ways to call it, and the same four in sync

The API surface is larger than it first appears, and it is easier to read as four ideas each available in two forms.

The first idea is findUp with a name, which returns a Promise for the path or undefined if the file could not be found. The second is findUp with a matcher, a function called for each directory in the search, which returns a path when it matches. The third is findUp with an array of names, which returns the first path found while respecting the order of the names, so you can express a preference list. The fourth is findUpMultiple, which returns an array of all paths found, with a limit option to cap the number of matches.

And then there are the sync variants of each: findUpSync and findUpMultipleSync, which return the path or undefined and the array respectively, without a Promise.

That symmetry is the point of the design. A package that only offers a Promise forces a synchronous caller to restructure, and a package that only offers a synchronous function cannot be used at the top of an async module without a blocking call. Here both exist, with the same options and the same return shape, so the choice is a matter of the call site rather than of the package.

The undefined rather than throw decision is the other one that matters. A missing configuration file is a normal outcome, not an error condition, and returning undefined lets a caller write a fallback instead of a try and catch.

## The options that change what counts as a match

Five options are documented, and each one exists because a real case broke the default.

cwd is the directory to start from, and it accepts a URL or a string, defaulting to process.cwd(). type selects what counts as a match, with the values file, directory and both, defaulting to file. allowSymlinks, defaulting to true, allows symbolic links to match if they point to the chosen path type.

That last default is worth understanding, because the common case is a symlink. A .git entry in a submodule is a file rather than a directory, and the README's own example uses findUp('.git', {type: 'both'}) with the comment that .git could be a file or a directory and is common in submodules.

stopAt applies only to the findUp functions, takes a URL or a string, and defaults to the root directory. It is a directory path where the search halts if no match is found before reaching that point, which is the escape hatch for a project that must not escape a particular tree.

limit applies only to the multiple variants, is a number, and defaults to Infinity. It caps how many matches are returned, which matters for the findDown case where the answer is usually a list and the list can be long.

## findUpStop lets the matcher end the search early

The matcher function is called for each directory in the search and can do more than test for a file. It can return a path to match, and it can return findUpStop, a Symbol, which stops the search and makes findUp return undefined immediately.

```js
import path from 'node:path';
import {findUp, findUpStop} from 'find-up';

await findUp(directory => {
	// Stop searching if we've reached a 'work' directory
	if (path.basename(directory) === 'work') {
		return findUpStop;
	}

	// Look for package.json in this directory
	return 'package.json';
});
```

The documentation explains the motivation as a performance optimisation in case the current working directory is deeply nested in the filesystem. That is the honest reason for it, and it is a good one. An upward walk from a deeply nested directory can visit hundreds of directories before it reaches the filesystem root, and a matcher that knows the answer cannot be above a certain point should not pay for the rest of the walk.

Returning undefined from a matcher means keep going, returning a path means match, and returning the symbol means stop with no match. Those three states are enough to express a policy, and the README uses the matcher form to show the other interesting capability too, which is matching on something other than a name, such as a set of permissions or a pattern.

## findDown is the same idea pointing the other way

findDown searches down descendant directories from cwd rather than up, and it exists because the upward search has a common cousin that is not a variant of it.

The motivating case, from the README, is finding the nearest parent directory that contains a specific file in its direct children, which the documentation calls useful for monorepo roots. The example composes the two functions:

```js
import {findUp, findDown} from 'find-up';

// Find the nearest parent directory that contains a specific file
// in its direct children (useful for monorepo roots)
console.log(await findUp(async directory => {
	return findDown('example.js', {cwd: directory, depth: 1});
}));
//=> '/Users/sindresorhus/foo'
```

The findDown options differ from the findUp ones in two ways. depth is a number, defaulting to 1, and is the maximum number of directory levels to traverse below cwd, so the default searches the direct children only. And strategy is a string, either breadth or depth, defaulting to breadth, where a breadth-first search finds shallower matches first and a depth-first search fully explores each branch before moving to the next.

The strategy option exists because a downward walk has a failure mode an upward walk does not. The first match found in a depth-first search can be several levels down while a shallower match exists in a sibling branch, so the default of breadth gives the more predictable answer. The cost is that a breadth-first walk touches more directories before it descends.

## The alternative, and the family it belongs to

The alternative to find-up is a loop, and the loop is four lines: start at process.cwd, join the file name to the current directory, check whether it exists, and if not move to path.dirname and repeat until the parent equals the current directory. Any competent developer can write that, and many do.

What the loop does not give you is the edge cases, and those are where a package earns its place. Symlink handling, with a documented default and an option to turn it off. The type option, so that a directory and a file of the same name are distinguished. The stopAt boundary, so a search cannot escape a subtree. The stop symbol, so a matcher can end the walk. The sync variants. The findDown counterpart with a depth limit and a traversal strategy. Each of those is a bug report that someone else already received.

The package also sits in a family of related tools, and the README links them because they are variations on the same idea. There is a command line wrapper, find-up-cli. There is package-up, for finding the closest package.json, and package-directory, for finding the root directory of an npm package. And there is resolve-from, which resolves the path of a module the way require.resolve does but from a given path.

That family is worth knowing about, because the specialised variants are often better than the general one. If you are looking for a package manifest, package-up does exactly that and nothing else.

## MIT terms, ESM only, and a version line that has jumped majors

The licence is MIT, with the license file at the repository root, and the author is Sindre Sorhus, with funding through GitHub Sponsors.

The two constraints worth knowing before you install are on the metadata. The package is ESM only, the type field is module and there is no CommonJS entry, so a require based project cannot consume it without dynamic import. And the engines field requires Node 20 or newer, which is the cost of the major version.

The release history shows those jumps. v8.0.0 was published on 2025-09-16, v7.0.0 on 2023-11-16 and v6.3.0 on 2022-02-08. The 7.0.0 release was the ESM switch, and the 8.0.0 release raised the Node floor. A project that has been on find-up for years will find that upgrading means changing its module system, not just its version number.

The repository is small and conventional for a package of this kind: index.js, index.d.ts, index.test-d.ts for the type tests, a fixture/ directory with the test tree, and a test/ directory. The test script is xo for linting, then ava over test/findup.js and test/finddown.js, then tsd for the type tests, and the xo configuration in the package metadata relaxes a few rules and ignores the fixture directory.

The last push was on 2026-09-18, and the repository is not archived, so the project is maintained even though the published version is from 2025.

## Conclusion

find-up fits any Node tool that needs to locate a project file from an arbitrary working directory, whether that is a config file, a package manifest or a lockfile, and it saves every project from reimplementing the loop. It does not fit a project that must stay on CommonJS or on Node below 20, since the package is ESM only with a node >=20 requirement. Before adopting it, check whether the file you are looking for might be a symlink, since symlinks are matched by default, and use findUpStop if the search runs from a deeply nested directory and you know where the answer cannot be.

## FAQ

### How do I install find-up and find a file in a parent directory?

Install it with npm install find-up, then call findUp with the file name. It returns a Promise for the path or undefined if the file could not be found, and there are sync variants, findUpSync and findUpMultipleSync, that return the value directly.

### How do I stop find-up from searching above a directory?

Use the stopAt option, which takes a URL or a string and defaults to the root directory. It is a path where the search halts if no match is found before reaching that point.

### What does the findUpStop symbol do?

It is a Symbol a matcher function can return to stop the search, causing findUp to return undefined immediately. The documentation presents it as a performance optimisation when the working directory is deeply nested.

### How is findDown different from findUp?

findDown walks down descendant directories from cwd instead of up. It has a depth option, defaulting to 1, and a strategy option of breadth or depth, with breadth as the default, so shallower matches are found first.

### Which Node versions and module format does find-up support?

The package is ESM only, with the type field set to module, and the engines field requires Node 20 or newer. The recent releases are v8.0.0 on 2025-09-16, v7.0.0 on 2023-11-16 and v6.3.0 on 2022-02-08, and the licence is MIT.

## Sources

- [Issues](https://github.com/sindresorhus/find-up/issues)
- [License: MIT](https://github.com/sindresorhus/find-up/blob/main/LICENSE)
- [README](https://github.com/sindresorhus/find-up/blob/main/README.md)
- [Releases](https://github.com/sindresorhus/find-up/releases)
- [sindresorhus/find-up on GitHub](https://github.com/sindresorhus/find-up)

---

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