cosmiconfig's default search stays in one directory, and the published package has no source maps
Find and load configuration from a package.json property, rc file, TypeScript module, and more!
At a glance
- What is it?
- cosmiconfig finds and loads configuration for JavaScript tools by checking a handful of conventional filenames, walking up the directory tree only if you configure it to. Version 10 shipped with its patch release half a minute later, and the package manifest excludes source maps from what gets published, so anyone debugging a tool that depends on it is left without a stack trace.
- Who is it for?
- cosmiconfig is a good fit for a command line tool that needs to accept configuration in whatever form its users already have, since the default search places cover the conventions people actually use and every one of them is overridable. Two things to know before you rely on it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 35 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The default search stays in one directory
The opening of the readme is easy to skim and easy to misremember. The library installs like any other package:
npm install cosmiconfigIn the current directory, the loader checks a fixed list of conventional places. Above it, there is a search strategy option that walks up the tree, checking each of those places in each directory until it finds something acceptable or hits the home directory.
The detail that matters is in the description of the search function: the strategy that does nothing is described as the default when no stopping directory is configured. So a tool that installs cosmiconfig and calls search with no options looks in exactly one directory and returns nothing if the file is not there.
That is a defensible design. Walking upwards without a boundary means reading files in directories a user may not have intended, and stopping at the home directory is a small protection rather than a large one. It is also a change in behaviour from the versions many tools still depend on, which is why the option list spends a section on it.
The rest of the option surface is short and specific: the search strategy, the search places, custom loaders, the package property name, the directory to stop at, whether to cache, a transform hook, and whether empty search places should be ignored.
Dotted rc files get a leading dot; the ones in .config do not
The search places follow a naming convention, and the convention has one asymmetry that will cost you an afternoon if nobody explains it.
For a module called `myapp`, the loader looks for a property of that name in `package.json`. Then for a file it looks for a dotted name: `.myapprc` with no extension, and the same base name with any of nine extensions. Inside a `.config` subdirectory, it looks for the same set of names without the leading dot. Finally it looks for a config-style file named after the module with a `.config` suffix.
So the dot marks the rc form and its absence marks the subdirectory form. That is not arbitrary; it follows the older convention where rc files are dotfiles and therefore hidden, while files in a dot-directory are already visually recessed. But nothing in the readme states the rule, and the two lists are presented as parallel bullets, so it reads like an oversight until you notice the pattern.
The nine extensions are worth knowing as a set, because they tell you which module formats are supported: JSON and its two YAML spellings, then four JavaScript forms and two TypeScript forms, covering both module and script resolution in both languages.
moduleName must be a valid filename, so scoped names break it
The one required argument is the module name, and the readme is unusually direct about a constraint that catches people out. The explorer is created like this:
const { cosmiconfig } = require('cosmiconfig');
const explorer = cosmiconfig(moduleName, /* optional */ cosmiconfigOptions)The name is used to build the default search places and the default package property. If the search places include files, which they do by default, then the module name has to consist of characters that are allowed in a filename. The consequence is spelled out: you should not copy a scoped package name such as an organisation-and-package pair straight into this argument, because the slash in it is not a filename character and the constructed filenames will not match anything.
That is a small piece of advice with a large effect on a monorepo, where a tool's package name is very often scoped. The fix is a separate short name for the explorer, which then no longer matches the package property, so the property name has to be configured too.
The rest of the API is symmetric and easy to read. There is an asynchronous entry point that returns an explorer, and a synchronous one beside it, and each explorer offers a search that walks, a load that takes a path you already know, and three methods for clearing the load cache, the search cache, or both.
isEmpty is absent rather than false when a file has content
The result object has three properties, and one of them has an unusual shape.
The parsed configuration is undefined when the file was empty. The path to the file that was found is always there. And the emptiness flag is true when the configuration file is empty, with the explicit note that the property will not be present at all if the file is not empty.
So this is not a boolean that is sometimes true and sometimes false. It is a property that exists only in one case. Code that reads it as `result.isEmpty` will get undefined rather than false, which is falsy and therefore works in a conditional, but which will surprise anyone who logs the object, serialises it, or spreads it into a wider structure.
The flag exists because an empty configuration file is a real situation with two reasonable interpretations. Either the user created the file and left it empty, which is a mistake worth surfacing, or the tool created the file as a starting point, which is not. Returning null when nothing is found covers the third case, and the emptiness flag covers the first.
The published package excludes source maps
The manifest is short, and two of its fields are worth stopping on.
The first is the file list. The package publishes the compiled output directory and then explicitly excludes source maps from it with a negated pattern. Everything in the published artifact is minified or compiled output, and the mapping back to the original TypeScript is not there.
That is a defensible choice for a small loader with a large install graph, since source maps add weight to every install and the code is short. It is also a choice that transfers cost to consumers. When a tool using cosmiconfig throws inside the loader, the stack trace points at compiled line numbers in the dependency rather than at anything the consumer can read, and a debugger cannot step into the source without going to the repository separately.
The second field is the entry point, which is a CommonJS file with a sibling type declaration file. Nothing in the manifest declares a module type field, so the package resolves as CommonJS, which is what the readme's examples show as well, using a destructured require of both the asynchronous and synchronous entry points.
The clean script deletes ignored files across the tree
The scripts section is where a small library can still surprise you, and the clean script is the one to read before running it.
It is a single git clean invocation with the flags for removing ignored files, removing nested repositories and directories not managed by git, and not asking for confirmation, with one exclusion pattern protecting the dependency directory. In effect it deletes everything in the working tree that git is not tracking, apart from installed dependencies.
That is the right tool for the job, since a build directory, coverage output and compiled output are all ignored files, and there is no portable cross-platform equivalent. It is also a command that will remove a local environment file, an editor's scratch file, or anything else you have ignored on purpose, and the exclusion list currently protects only the dependency directory.
The rest of the script set is more conventional. Building runs the TypeScript compiler in project mode with the production environment set, development runs the same in watch mode, tests run under a coverage flag, and a combined check runs the tests, the linter and a formatting check in sequence. Publishing runs that combined check and then the build, and a prepare hook installs the git hooks.
Version 10 and its patch release went out 32 seconds apart
The release record is short and says something about how this project ships. Version 10.0.0 and version 10.0.1 were both published on the same day, and the timestamps put them 32 seconds apart. Before that, the previous release line had a patch in March.
A major version and its first patch landing together usually means the major bump was preceded by enough testing that the follow-up fixes were already known, and the maintainer chose not to leave a window where people installed 10.0.0 and hit a problem that was fixed before most of them finished downloading. It is a considerate choice and a slightly unusual release pattern.
What it means for you is that the major boundary is recent relative to the last push, and that the readme's compatibility statement should be read closely against it. The installation section gives one line about the runtime floor, saying it is tested in Node 14 and newer, which is a considerably older floor than most packages claiming a version 10 in 2026.
The tooling around the source is small and current: git hooks managed by a husky directory, an ESLint configuration in the older format that the lint script's file-extension flag matches, a Prettier configuration written as CommonJS, a coverage configuration, two TypeScript configuration files where one is the shared base, and a Vite configuration that exists for the test runner rather than for a build.
Editorial conclusion
cosmiconfig is a good fit for a command line tool that needs to accept configuration in whatever form its users already have, since the default search places cover the conventions people actually use and every one of them is overridable. Two things to know before you rely on it. The search does not walk up the directory tree by default in this version, so a tool that expects to find a config in a parent directory has to say so. And the published package ships without source maps, which matters the first time a consumer needs to debug inside the loader rather than around it.
Frequently asked questions
What is config in coding?
In this repository it means the external settings a tool reads at startup, and the whole library exists to find and load those settings in the forms the JavaScript ecosystem already uses: a property in package.json, an rc file in JSON or YAML, a module with a JavaScript or TypeScript extension, or any of those inside a .config subdirectory. It is aimed at tool authors rather than end users, and the readme says so.
How do I install cosmiconfig?
With a single npm install of the package name. The readme states it is tested in Node 14 and newer, and it is MIT licensed. Tool authors then create an explorer with the module name and either search for a configuration or load a known path directly, using either the asynchronous or the synchronous entry point.
Does cosmiconfig search parent directories by default?
Not in the current version. It checks a fixed list of places in the current directory, and walking up the tree is a search strategy you opt into, with the readme describing the strategy that does nothing as the default when no stopping directory is configured. Once a strategy is set, each directory in the walk is checked against the same list of places until something acceptable is found or the home directory is reached.
What does the cosmiconfig result object contain?
Three properties: the parsed configuration, which is undefined when the file was empty; the path to the file that was found; and an emptiness flag that is true for an empty file and is not present at all when the file is not empty. That last point is worth remembering, since the flag is absent rather than false in the common case.
Can I use a scoped package name with cosmiconfig?
Not directly. The module name is used to build the default search places and the package property, and because the search places include files the name must consist of characters allowed in a filename. The readme therefore advises against copying a scoped name such as an organisation and package pair into that argument, which means a scoped tool needs a separate short name and a separately configured property.
What are the cosmiconfig alternatives named in the repository?
None. The readme does not mention competing libraries. What it does document is its own extension points: custom search places, custom loaders, a package property override, a stopping directory, a cache toggle, a transform hook, and an option for ignoring empty search places.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cosmiconfig-cosmiconfig)