package-directory

pkg-dir walks up from any directory to find the nearest package.json, returning the root directory of a Node.js project or npm package.

Library
npm
v9.0.0
254stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
38/100Needs Attention
Development Activity4
Maintenance20
Community48
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
59/100Fair
Architecture70
Code Quality85
Innovation55
Learning Curve25

pkg-dir (published to npm as package-directory, with the widely-known pkg-dir name kept as an alias by Sindre Sorhus) solves a small but recurring problem: given an arbitrary starting directory, find the nearest ancestor directory that contains a package.json and treat it as the project root. It does this by walking up the filesystem tree from a cwd (or process.cwd() by default) until it hits a package.json file, then returns that file’s containing directory.

The package exposes both packageDirectory() (Promise-based) and packageDirectorySync() (synchronous) so it can be used from either async code paths or synchronous tooling contexts like config loaders and CLI bootstrapping. It also supports an ignoreTypeOnlyPackageJson option: some monorepos or ESM-only setups place a minimal {"type": "module"} file partway up the tree purely as a module-scope marker rather than a real project root, and this option lets callers skip past those and keep searching for a package.json with real content.

Internally it delegates the directory-walking mechanics to find-up-simple, a sibling utility from the same author, rather than reimplementing traversal logic — keeping pkg-dir itself to a single small file focused only on the package.json-specific decision of what counts as a valid root.

It’s a foundational building block used transitively by a huge number of build tools, linters, and CLIs (via packages like pkg-conf, read-pkg-up, and various ESLint/Babel/Jest config resolvers) to answer “where does this project actually start?” without every consumer re-deriving that logic.

What You Get

  • packageDirectory(options?) — async lookup that returns a Promise resolving to the project root path, or undefined if none is found
  • packageDirectorySync(options?) — synchronous equivalent for code paths (like CLI startup or config loaders) that can’t await
  • A cwd option to start the search from any directory instead of process.cwd()
  • An ignoreTypeOnlyPackageJson option to skip over minimal {"type": "module"} marker files and keep searching for a real project root
  • Full TypeScript type definitions (index.d.ts) shipped alongside the implementation
  • A tiny, dependency-light implementation (a single runtime dependency, find-up-simple) suitable for use deep in other tools’ dependency trees

Common Use Cases

  • Build tools and bundlers locating the project root to resolve config files relative to it
  • Linters and formatters (ESLint, Babel, Jest resolvers) finding the nearest package.json to determine project boundaries
  • CLI tools bootstrapping from an arbitrary working directory and needing to find the enclosing project
  • Monorepo tooling that needs to distinguish a package’s own root from a parent workspace root
  • Any script or library that needs to read the consuming project’s package.json without knowing its exact location in advance

Under The Hood

Architecture pkg-dir is implemented as a single small ES module (index.js, under 3KB) exposing four internal helpers — a type-only package.json predicate, its async and sync file-reading variants, a shared “get next search directory” helper, and the async/sync findPackageDirectory/findPackageDirectorySync recursive walkers — behind two exported entry points, packageDirectory and packageDirectorySync. The sync and async code paths are structured as deliberate mirror images of each other rather than one being derived from the other, trading a small amount of duplication for straightforward, independently-readable control flow in each. Directory traversal itself is delegated entirely to the find-up-simple dependency, so this package’s own logic is scoped narrowly to package.json-root semantics (in particular, the type-only package.json skip-and-recurse behavior) rather than reimplementing filesystem walking.

Tech Stack The package targets modern Node.js (engines.node >= 18) and ships as pure ESM ("type": "module"), with zero CommonJS interop layer. Its only runtime dependency is find-up-simple (^1.0.0) from the same author, used for the underlying upward directory search. There is no build or transpilation step — the shipped index.js and hand-written index.d.ts are the source files themselves. Tooling is limited to xo for linting, ava for tests, and tsd for type-definition testing, wired together as a single npm test script with no bundler or compiler in the loop.

Code Quality The test suite (test.js) uses ava with tempy to create real temporary directories and exercises both the async and sync entry points against realistic fixture layouts, including the type-only package.json edge case in both its default and ignoreTypeOnlyPackageJson: true forms. Type correctness is checked separately via tsd against index.test-d.ts. Linting is enforced through xo (a strict ESLint preset), and CI (.github/workflows/main.yml) runs npm test across a Node 18/20 matrix on every push and pull request. Naming is consistent and minimal (packageDirectory/packageDirectorySync), and error handling favors returning undefined over throwing, keeping the API predictable for callers that just want a best-effort lookup.

API Design The public surface is intentionally tiny: two functions differing only in sync vs. async form, a single options object with two well-documented fields, and one return shape (string | undefined) in both cases. There’s no configuration to get wrong, no required arguments, and the default behavior (search from process.cwd()) covers the common case with zero setup. The ignoreTypeOnlyPackageJson option is the one piece of real judgment the API exposes, and it’s opt-in rather than a footgun enabled by default, which keeps existing consumers’ behavior stable across versions.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search