TypeScript

Fix: TypeError: Invalid PostCSS Plugin found at: plugins[0]

A postcss.config.js that lists plugin names as plain strings compiles nothing — the array syntax needs each entry already resolved, not a package name.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

A postcss.config.js whose plugins is an array of plain strings ("postcss-import", "tailwindcss", "autoprefixer") throws before any CSS is touched, as long as that file is read by postcss-load-config — the loader used by the standalone postcss CLI, Vite, webpack and Storybook. In array form, postcss-load-config uses each entry exactly as written; it does not resolve package names for you. The fix is to require() each entry yourself, or switch to the object-keyed syntax, which postcss-load-config resolves internally. Next.js reads postcss.config.js with a different, internal mechanism that works the opposite way — see the dedicated section below if that is your stack.

text
TypeError: Invalid PostCSS Plugin found at: plugins[0]
 
(@/app/postcss.config.js)
    at /app/node_modules/postcss-load-config/src/plugins.js:77:15
    at Array.forEach (<anonymous>)
    at plugins (/app/node_modules/postcss-load-config/src/plugins.js:58:13)
    at processResult (/app/node_modules/postcss-load-config/src/index.js:38:14)
    at /app/node_modules/postcss-load-config/src/index.js:168:14
    at async Promise.all (index 0)

Why it happens#

This is the error behind a 13-vote Stack Overflow question with 24,762 views: a developer cloned Tailwind's own setup-examples repository, edited tailwind.config.js, then ran postcss css/tailwind.css -o generated.css directly with the standalone postcss CLI — no Next.js, no bundler. The build never got as far as reading the CSS file — it failed while PostCSS was still loading its own config.

A minimal version of the broken config looks like this:

postcss.config.js (broken)
js
module.exports = {
  plugins: [
    "postcss-import",
    "tailwindcss",
    "autoprefixer",
  ]
};

Reproduced in a node:20-alpine container (Node 20.20.2), with postcss@8, postcss-cli@10 (which installed postcss-load-config@4.0.2), tailwindcss@3 and autoprefixer@10, 17 October 2026: running npx postcss css/tailwind.css -o generated.css against the config above throws the exact TypeError: Invalid PostCSS Plugin found at: plugins[0] above, pointing at postcss-load-config/src/plugins.js.

Reading that file directly (it ships inside node_modules) shows the mechanism in full. For an array plugins, the loader only filters out falsy entries — config.plugins.filter(Boolean) — it never calls require() on anything. It then walks the resulting list and, for each entry, throws unless that entry is already a function, or an object with a postcssPlugin property, or an object whose own plugins is an array:

node_modules/postcss-load-config/src/plugins.js (installed: 4.0.2)
js
if (
  !(
    (typeof plugin === 'object' && Array.isArray(plugin.plugins)) ||
    (typeof plugin === 'object' && plugin.postcssPlugin) ||
    (typeof plugin === 'function')
  )
) {
  throw new TypeError(`Invalid PostCSS Plugin found at: plugins[${i}]\n\n(@${file})`)
}

For an object plugins, the same file does the opposite: it calls Object.keys(config.plugins) and require()s each key through an internal load() helper before running that same check — which is why the object syntax resolves package names and the array syntax does not. The current release on the npm registry, postcss-load-config@6.0.1 (checked 17 October 2026), still has the identical array-vs-object split in its own src/plugins.js — this is not a defect fixed in a later version.

The traceback also shows how far the loader gets: it fails inside processResult, before Promise.all resolves a config object, so nothing downstream — including Tailwind's own class scanning — ever runs. Reproducing the same config with only the first entry fixed (require() on postcss-import, the other two still bare strings) confirms the loader stops at the first bad entry rather than collecting every broken one: the error becomes TypeError: Invalid PostCSS Plugin found at: plugins[1], matching the position of the next string in the array. Fixing one entry at a time and re-running tells you exactly which index is still wrong.

Fix#

1. Require each plugin by hand, if you keep the array syntax. This applies to the standalone postcss CLI, Vite, webpack and Storybook — any tool whose config is read by postcss-load-config.

postcss.config.js (fixed)
js
module.exports = {
  plugins: [
    require("postcss-import"),
    require("tailwindcss"),
    require("autoprefixer"),
  ]
};

2. Or switch to the object syntax, which npx tailwindcss init -p generates by default (reproduced in the same container: a clean tailwindcss@3 + postcss + autoprefixer install, then npx tailwindcss init -p, wrote exactly this file):

postcss.config.js (object syntax)
js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

npx tailwindcss init -p does not add postcss-import on its own — if your build also needs it (as the broken example above does), add it as its own key by hand: "postcss-import": {}.

3. Re-run the build.

bash
npx postcss css/tailwind.css -o generated.css

Both fixes were reproduced against the same broken input: the require() array and the object syntax each produced a generated.css with Tailwind's base layer compiled in and exit code 0. The object syntax is not a workaround — src/plugins.js takes a genuinely different branch for it, the one that calls require() internally, which is also why reordering the array entries does not help: the check in the code above runs on every entry regardless of position.

Next.js is a different case — do not use this fix there#

If your project builds with Next.js's own CSS support (no custom bundler reading the file), do not add require() calls. Next.js's official PostCSS guide reads postcss.config.js itself — it does not go through postcss-load-config — and documents the opposite rule:

Do not use require() to import the PostCSS Plugins. Plugins must be provided as strings.

The same page adds that if your postcss.config.js must also support "other non-Next.js tools in the same project" (its own example is adding a tool like Storybook, which does read the file with postcss-load-config), you switch to the same object-keyed format shown above — because that format is the one shape both readers accept. In other words: a bare string works inside Next.js's own CSS pipeline by design, and only breaks once something else (postcss-load-config, via a CLI, Vite, webpack or Storybook) reads the same file. If your traceback names postcss-load-config/src/plugins.js, you are not inside Next.js's own PostCSS reader when the error happens — either you ran a standalone tool against the file, or another part of your toolchain (Storybook, Vite, a lint step) is loading it independently.

Verify the fix#

bash
npx postcss css/tailwind.css -o generated.css
echo $?

A clean run prints only Tailwind's own No utility classes were detected warning (if your content globs do not yet match any file) and exits 0. In this reproduction, that run wrote a 501-line generated.css starting with Tailwind's universal reset (*, ::before, ::after { --tw-border-spacing-x: 0; … }), confirming the pipeline reached PostCSS, ran postcss-import and tailwindcss, and wrote real output — not just that the process exited without an error.

Any remaining TypeError: Invalid PostCSS Plugin found at: plugins[N] means another entry in the array is still a bare string — fix them one at a time, since this error always reports a single index, not every broken entry at once. If the build runs through a bundler rather than the postcss CLI directly, run whatever script invokes PostCSS in your project (npm run build, or the dev server) and check whether the stack names postcss-load-config/src/plugins.js. A Next.js build that throws this same message is loading the config through something other than Next.js's own reader (see the section above) — the text of the error alone does not tell you which reader is involved.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

The same Stack Overflow thread's third-highest answer (score 5), posted on the same question, warns about a separate trap: installing autoprefixer@10 moved postcss to a peer dependency and required PostCSS 8, breaking setups still pinned to PostCSS 7. Its symptom is a different message, Error: PostCSS plugin autoprefixer requires PostCSS 8, not the plugins[N] TypeError this article covers — if your traceback names autoprefixer directly instead of postcss-load-config/src/plugins.js, that second thread is the one to follow, and the fix is to align your postcss and autoprefixer major versions, not to change the shape of the plugins array.

If you are on Next.js, the plugin shape is already correct, and Tailwind classes still render nothing, that is a separate issue again: see Tailwindcss not working with Next.js: fix the config — in that case PostCSS compiles without error, but Tailwind's content globs never match your component files.

Readers on Tailwind CSS v4 who hit a plugin error with this same postcss.config.js shape are usually facing a different, newer issue: v4 moved the PostCSS integration to a separate @tailwindcss/postcss package, and using the old tailwindcss plugin directly throws its own error. That case, including the Vite-specific instructions, is covered in Fix: Tailwind PostCSS Plugin Has Moved (Vite + React). If instead a clean npm install on an unpinned tailwindcss dependency is what broke your build, start from Fix "Missing specifier" & npx tailwindcss init Errors, which reproduces that failure one step at a time.

Either way, check which reader loads your postcss.config.js before picking a fix: postcss-load-config (the CLI, Vite, webpack, Storybook) wants already-resolved plugins in array form, or package names in object form; Next.js's own reader wants string names either way, and never a require() call.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote