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.
Repository Health
Technical Analysis
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, orundefinedif none is foundpackageDirectorySync(options?)— synchronous equivalent for code paths (like CLI startup or config loaders) that can’t await- A
cwdoption to start the search from any directory instead ofprocess.cwd() - An
ignoreTypeOnlyPackageJsonoption 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.
Used by 2 apps in this directory
Craft CMS
CMS
A developer-first PHP CMS with clean-slate content modeling, auto-generated GraphQL API, and a four-tier edition system that scales from solo projects to enterprise deployments.
Logto
Authentication
Open-source auth infrastructure for SaaS and AI apps with OIDC, SAML, and RBAC