Open-source project
postcss/postcss-load-config avatar
postcss/postcss-load-config

postcss-load-config: finding a PostCSS config wherever you put it

Autoload Config for PostCSS

662 stars78 forksJavaScriptMIT

At a glance

What is it?
postcss-load-config is the module that locates and evaluates a PostCSS configuration, supporting a postcss section in package.json, .postcssrc files in JSON or YAML, and JavaScript or TypeScript configs that can export an object or a function. It suits a team with several build tools sharing one stylesheet pipeline, and it is a poor fit if you want a single fixed config file for every project.
Who is it for?
postcss-load-config fits a team whose PostCSS setup differs between tools, or that wants the same pipeline configured in several supported formats, since the discovery and evaluation logic is the same regardless of whether the config lives in package.json, a .postcssrc file or a TypeScript module. It is the wrong choice if a project wants one canonical config file and a predictable lookup, since the search order is a discovery mechanism rather than a fixed path.
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 3 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The module that sits between a tool and a config file

postcss-load-config is described as Autoload Config for PostCSS, and it is the piece of plumbing that lets a tool find out how a project wants PostCSS to run.

The problem it solves is that PostCSS itself does not define where a configuration lives. Different integrations have historically looked in different places, and a project that used PostCSS through a CLI, through gulp and through webpack at once could end up with several configuration files that disagreed. A loader that searches for a config in a defined set of locations and formats gives one answer for all of them.

The supported formats are the substance of the module. A project can put a postcss section inside its package.json, which keeps the configuration next to the dependency list. It can use a .postcssrc file in JSON or YAML format, with the documentation recommending an explicit extension such as .postcssrc.json or .postcssrc.yml rather than the bare .postcssrc. Or it can use a JavaScript or TypeScript module, with a wide set of accepted file names: .postcssrc.js, .postcssrc.mjs, .postcssrc.cjs, .postcssrc.ts, .postcssrc.mts, .postcssrc.cts, postcss.config.js, postcss.config.mjs, postcss.config.cjs, postcss.config.ts, postcss.config.mts and postcss.config.cts.

That last group is the important one for a modern project. A JavaScript or TypeScript config can be a function, which means the configuration can depend on the environment rather than being a static object, and the file naming convention covers every module system a project might use, from CommonJS through ESM to the TypeScript variants.

The repository is JavaScript, MIT licensed, with a src/ directory, a test/ directory, a CHANGELOG.md, a CONTRIBUTING.md, a DOCS.md generated by jsdoc2md, and an eslint config in the modern flat form.

A function config, and the context it receives

The JavaScript config can export an object, which is the simple case:

js
module.exports = {
  parser: 'sugarss',
  map: false,
  plugins: {
    'postcss-plugin': {}
  }
}

Or it can export a function that returns the config, which is where the environment comes in:

js
module.exports = (ctx) => ({
  parser: ctx.parser ? 'sugarss' : false,
  map: ctx.env === 'development' ? ctx.map : false,
  plugins: {
    'postcss-plugin': ctx.options.plugin
  }
})

The context object is evaluated while the config is being loaded. By default it carries ctx.env, which is process.env.NODE_ENV, and ctx.cwd, which is process.cwd(). Most third-party integrations add their own properties, and the documentation is explicit that you need to check the specific module's readme for what is available, naming postcss-loader as an example.

The fuller example in the documentation shows the array form of plugins, which is the more capable of the two:

js
module.exports = (ctx) => ({
  parser: ctx.parser ? 'sugarss' : false,
  map: ctx.env === 'development' ? ctx.map : false,
  plugins: [
    require('postcss-import')(),
    require('postcss-nested')(),
    ctx.env === 'production' ? require('cssnano')() : false
  ]
})

That example is also the clearest statement of the practical value: the same config file gives you a different plugin set in development and in production, with cssnano minifying only when the environment says so. The plugin array supports a false entry to disable a plugin conditionally, which is how the cssnano line works.

Plugin forms, and the deprecation that pushes you to arrays

Plugins can be declared as an object or as an array, and the difference between them has a wrinkle worth knowing about.

In the object form, the key is the plugin name and the value is the options object. A key can be a Node.js module name, a path to a JavaScript file relative to the directory of the PostCSS config file, or an absolute path to a JavaScript file. The value can be an empty object, meaning load the plugin with its defaults, an options object, or false, meaning do not load the plugin at all. The documentation is careful that the empty object must be an empty object literal, which is the sort of detail that produces a confusing error if you get it wrong.

The array form requires each plugin to be required, so the entry is the plugin instance rather than a name:

js
module.exports = ({ env }) => ({
  ...options,
  plugins: [
    env === 'production' ? require('postcss-plugin')() : false
  ]
})

The wrinkle is the deprecation notice. postcss-loader for webpack marks the object form as deprecated, so the documentation advises using the array form in JavaScript configs that postcss-loader loads. If your project is built with webpack and postcss-loader, that is the form to write, and the reason is not aesthetic.

Ordering is straightforward and worth stating because it is easy to get wrong: plugin execution order is determined by declaration in the plugins section, top down. In the object form that means the key order of the object literal, which is why the documentation shows the same plugin listed three times with index comments to make the point.

The options table lists the top-level keys, and each has a default. to is the destination file path, undefined by default. from is the source file path, undefined by default. map is a boolean or an object, false by default, and it takes the source map options. parser, syntax and stringifier are each a string or a function, false by default, and they are the three hooks for changing how PostCSS reads and writes.

The peer dependencies that decide which config formats work

The dependency list in package.json is short and tells you how the module works. The single runtime dependency is lilconfig, which is the configuration discovery library that does the searching across the supported file names and extensions. Everything else is a peer dependency, and the set of them is the real interface of the package.

postcss is a peer dependency at 8.0.9 or newer, and it is marked optional, which is odd but makes sense given the module is a loader rather than a processor. The more interesting peer dependencies are the three that gate config formats.

yaml is a peer dependency, so a YAML config only works if the yaml package is installed. The documentation states this as a requirement rather than a recommendation: for YAML configs you must have yaml installed as a peer dependency.

For TypeScript configs the same applies with tsx or jiti. The peer dependency list names both, jiti at 1.21.0 or newer and tsx at 4.8.1 or newer, and the documentation says you must have tsx or jiti installed as a peer dependency. That is a reasonable design, since loading a TypeScript file requires a runtime that can execute it, and requiring the consumer to choose between a transpiler and a loader keeps the package itself free of that weight. It also means a config that works on one machine can fail on another if the peer is missing, which is worth a lockfile.

The engines field requires Node 18 or newer, and the manifest version is 6.0.1, with the main entry at src/index.js and types at src/index.d.ts. The test setup is worth a note: the unit tests run with node --test, the built-in Node test runner, over test/*.test.js, and coverage is measured with c8. The docs target is generated with jsdoc2md, which is where DOCS.md comes from, and the funding block points at an OpenCollective page for PostCSS and a GitHub sponsor link.

Where postcss-load-config is the wrong choice

The first limitation is that discovery is not the same as a fixed path. A loader that searches several locations is convenient for a project that has one config, and it is a source of ambiguity for a project that has several. A monorepo with a config at the root and another in a package, or a repository where an old config file was never deleted, has two plausible answers, and the tie is broken by an order you did not choose. The documentation does not document a search order in the visible text, which means the behaviour you get is the implementation's rather than a contract you can rely on.

The second is the peer dependency friction. A YAML config needs the yaml package, a TypeScript config needs tsx or jiti, and none of them is bundled. That is the right call for a package that many tools load, since bundling a TypeScript runtime into every install would be worse, but it does mean the config format is not a free choice. A team that wants YAML has to add a dependency to package.json for a file that contains three lines, and a team that wants TypeScript has to pick between two transpilers.

The third is that a function config is evaluated at load time, which means a config that throws, or that imports something unavailable in the current environment, breaks the build rather than degrading. That is the same trade as any code-in-config approach and it is a feature when you want it, since the environment-dependent plugin set is genuinely useful.

The fourth is the version gap. The manifest says 6.0.1, the last push was on 2026-10-19, and the newest published GitHub tag is v2.1.0 from 2019-06-10, with v2.0.0 from 2018 and v1.2.0 from 2017. So the tags are not a usable version history, and a project that wants to know what changed between releases has to read the CHANGELOG.md instead. The current work is clearly happening, and the pnpm-lock.yaml in the tree shows the package manager in use, but the tag list reflects an older release practice.

The fifth is the scope. This module loads configuration. It does not run PostCSS, it does not resolve plugins by itself beyond what the config names, and it does not decide what a plugin does. A team expecting a build pipeline abstraction will find something much smaller here, and one that most of the time can be replaced by pointing a tool at a config file directly.

Editorial conclusion

postcss-load-config fits a team whose PostCSS setup differs between tools, or that wants the same pipeline configured in several supported formats, since the discovery and evaluation logic is the same regardless of whether the config lives in package.json, a .postcssrc file or a TypeScript module. It is the wrong choice if a project wants one canonical config file and a predictable lookup, since the search order is a discovery mechanism rather than a fixed path. Before adopting it, confirm the format you want to use is supported, install the peer dependencies it requires, because a YAML config needs the yaml package and a TypeScript config needs tsx or jiti, and note that the last push was on 2026-10-19 with the manifest at version 6.0.1 while the newest published tag is v2.1.0 from 2019.

Frequently asked questions

How do I install postcss-load-config?

Run npm i -D postcss-load-config. It is a development dependency, since it is the loader that a tool such as a CLI or a bundler uses to find your configuration.

What config file formats does postcss-load-config support?

A postcss section in package.json, a .postcssrc file in JSON or YAML, and JavaScript or TypeScript modules named .postcssrc.js, .postcssrc.mjs, .postcssrc.cjs, .postcssrc.ts, .postcssrc.mts, .postcssrc.cts or the corresponding postcss.config.* names.

What do I need to install to use a YAML or TypeScript PostCSS config?

A YAML config requires the yaml package as a peer dependency, and a TypeScript config requires tsx or jiti as a peer dependency. The loader itself only depends on lilconfig at run time.

How do I make a PostCSS config depend on the environment?

Export a function instead of an object. The function receives a context object with ctx.env, taken from process.env.NODE_ENV, and ctx.cwd by default, and third-party integrations add their own properties to that context.

Should I use the object or the array form for plugins?

The array form is recommended in JavaScript configs that postcss-loader for webpack loads, because the loader marks the object form as deprecated. In the array form each plugin must be required, and a false entry disables a plugin conditionally.

Official sources

  1. Issues
  2. License: MIT
  3. postcss/postcss-load-config on GitHub
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/postcss-postcss-load-config.svg)](https://hysenlabs.com/projects/postcss-postcss-load-config)

Was this useful?