# eslint-plugin-functional: a linter that treats mutation as a defect

> eslint-plugin-functional is an ESLint plugin that disables mutation and promotes functional programming in JavaScript and TypeScript, shipped as a family of rulesets from lite to strict. The rules, the generated documentation and the type-aware subset are all part of what you are buying, and the choice of ruleset is the decision that matters.

**eslint-functional/eslint-plugin-functional** — ESLint rules to disable mutation and promote fp in JavaScript and TypeScript.

- Repository: https://github.com/eslint-functional/eslint-plugin-functional
- Stars: 1,005 · Forks: 35
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/eslint-functional-eslint-plugin-functional

## The plugin's job is to make mutation a lint error

eslint-plugin-functional is an ESLint plugin to disable mutation and promote functional programming in JavaScript and TypeScript. The description in the repository metadata is more compact: ESLint rules to disable mutation and promote fp.

That is a much stronger position than a style preference, and it is worth being clear about what it means in practice. A linter that prefers const over let is a suggestion. A linter whose no-mutations ruleset rejects the assignment of a property is a gate. This plugin is the second kind.

The audience is a team that has already made the decision. There is no point installing a plugin that bans mutation in a codebase where mutating a local variable is normal and uncontroversial, because the plugin does not distinguish between the mutation that causes a bug and the mutation that is fine. It bans the category.

That makes it a policy tool as much as a code tool, and it changes how you should evaluate it. The question is not whether the rules are correct in the abstract, but whether the category is one your team agrees with, and whether you can adopt it in a codebase that was not written that way. The plugin's own rulesets are structured to help with exactly that second question.

The project is MIT licensed, and the author in the package metadata is Rebecca Stevens, with Jonas Kello listed as a contributor. It is published on npm as eslint-plugin-functional, and the README points at a getting started guide in the repository for the setup steps.

## The rulesets are the interface, not the individual rules

The plugin exposes configurations rather than asking you to assemble rules one by one, and the README's ruleset list is long enough to be worth reading closely.

There are three presets. The Strict preset, configs.strict, enforces recommended rules designed to strictly enforce functional programming. The Recommended preset, configs.recommended, has the same goal but is a little more lenient, allowing for functional-like coding styles and nicer integration with non-functional third-party libraries. The Lite preset, configs.lite, is described as good if you're new to functional programming or are converting a large codebase.

Then there are the categorised rulesets, which are the interesting part because they let you take a position on one axis at a time. Currying, configs.currying, is for the syntax that JavaScript has that is not compatible with curried functions, and preventing that syntax is how currying is enforced. No Exceptions, configs.noExceptions, encodes the idea that functional programming style does not use run-time exceptions, and that expressions produce values to indicate errors instead. No Mutations, configs.noMutations, prevents mutating any data. No Other Paradigms, configs.noOtherParadigms, blocks the object-oriented and other styles that JavaScript being multi-paradigm permits. No Statements, configs.noStatements, enforces the idea that in functional programming everything is an expression that produces a value, and blocks the JavaScript syntax that is just a statement. Stylistic, configs.stylistic, enforces code styles that can be considered to be more functional.

The final group are utility configurations. All enables every rule, Off disables every rule, Disable Type Checked disables all the rules that require type information, and the two external presets configure recommended vanilla ESLint rules and recommended TypeScript ESLint rules, with the TypeScript one also enabling the vanilla one.

The composition is the point. You can take no-mutations today, add no-statements next quarter, and leave the object-oriented rules off entirely if your team still writes classes.

## Some rules need types, and that changes where you can run them

The rule tables in the README carry a set of markers, and one of them decides whether a rule can run in your setup at all.

The legend defines a marker for configurations enabled in, configurations set to warn in, configurations disabled in, a marker for each of the five categorised configurations, a marker for the recommended, strict and stylistic configurations, a marker for rules automatically fixable by the --fix CLI option, a marker for rules manually fixable by editor suggestions, a marker for rules that require type information, and a marker for deprecated rules.

The type information marker is the one that catches teams out. A rule marked as requiring type information cannot be evaluated by ESLint alone; it needs the TypeScript program to be built and the types resolved. That is why the Disable Type Checked configuration exists, and why the type aware rules are useful but not universal.

The rule table shows this in practice. The functional-parameters rule, which is the currying rule and is present in the currying, lite, recommended and strict configurations, carries the type information marker, so the strictest currying enforcement is also a typed rule.

In a monorepo this is where the friction appears. Typed linting needs a resolvable program for every file being linted, which means a build step before linting and a configuration that knows which project each file belongs to. A project that lints on every push without a type build will find that some rules silently cannot be evaluated.

The deprecated marker matters for a different reason. A rule that has been deprecated still exists in the plugin, and a strict preset that includes it can keep a codebase on a rule the maintainers no longer recommend. The README's table is the place to check which ones are which before you adopt a preset.

## The rule documentation is generated, and the build checks it

The repository is arranged so that the documentation cannot drift from the code, and the tooling makes that visible.

The README's rules list sits between comment markers for an auto-generated rules list, and each rule links to a document under docs/rules, for example docs/rules/functional-parameters.md and docs/rules/no-promise-reject.md. The generator is eslint-doc-generator, and it appears twice in the package scripts: build:docs runs eslint-doc-generator, and lint:eslint-docs runs eslint-doc-generator --check.

That second command is the interesting one. The check mode fails if the generated documentation in the README and under docs is out of date with the rule metadata, so a contributor who adds a rule and forgets to regenerate the docs gets a failing lint rather than a stale table. The same pattern is applied to the rule table itself, which lists for every rule which configurations enable it, whether it is fixable, whether it requires type information, and whether it is deprecated.

For a user, that table is the reference to work from when you adopt a preset, because it tells you exactly which rules will fire and which of them your editor can fix for you. The fixable marker in particular matters, since an automatically fixable rule can be adopted with --fix rather than a manual cleanup.

The rest of the repository follows the same discipline. There is a knip.jsonc for unused dependency checks, a cspell.config.yml with a project-dictionary.txt for spelling, a markdownlint configuration, an eslint.config.js for the project's own linting, a vitest.config.ts for the tests, a rollup.config.ts for the build, and a .releaserc.yml for the release process.

## The published package is a rollup bundle with type declarations

Building the plugin is a two step npm script, and the output is a single entry point with types beside it.

```json
    "build": "pnpm run build:node && pnpm run build:docs",
    "build:docs": "eslint-doc-generator",
    "build:node": "rimraf lib && rollup -c rollup.config.ts --configPlugin @rollup/plugin-typescript --configImportAttributesKey with",
```

The node build clears the lib directory and runs rollup with the TypeScript plugin, and the docs build regenerates the rule documentation. The published entry points are:

```json
  "exports": {
    "types": "./lib/index.d.ts",
    "default": "./lib/index.js"
  },
```

Two details in the package metadata are worth noting. The version field reads 0.0.0-development, which is the placeholder semantic-release writes before a release is cut, and the README carries a semantic-release badge, so the published version numbers are generated from commit messages rather than typed by hand. The other is sideEffects set to false, which tells a bundler that importing the plugin has no side effects and can be dropped if nothing is used.

The lint script is a chain of seven checks, eslint, then the markdown lint, then the eslint docs check, then spelling, then knip, then the package lint, then the package lint for the workspace packages. A plugin whose job is to police other people's code holds itself to a fairly wide standard, which is a reasonable proxy for how seriously the maintainers take the rules themselves.

## A major version every few months, and a funding model aimed at companies

The release history shows a project that ships releases regularly. Version 9.0.5 was published on 2026-05-20, 10.0.0 on 2026-06-03 and 10.0.1 on 2026-09-16, and the last push to the repository was on 2026-10-18.

Major version bumps are worth planning around. ESLint plugins are usually consumed without much ceremony, and a major version that changes the recommended ruleset can move a large amount of code from passing to failing without anything changing in the application. The README's rule tables, with their deprecated markers and per-configuration columns, are the reference to check when a major version lands.

The funding model has two halves, and both are visible. There is a donations link in the repository, and the package metadata lists a ko-fi funding entry and a Tidelift funding entry. The Tidelift arrangement is described in the README under Enterprise Users, where the plugin is available as part of the Tidelift Subscription, with Tidelift working with the maintainers and a network of other open source maintainers to ensure the open source software supply chain meets enterprise standards.

The licence is MIT, with the LICENSE file at the root, so a company that uses the plugin internally need only keep the copyright notice. The subscription is for the supply chain assurance around it, not for the code.

## Conclusion

eslint-plugin-functional fits a team that has decided mutation is a defect rather than a style, and that wants that decision enforced on every pull request instead of in a review checklist. It does not fit a codebase in transition, because a plugin that bans the assignment operator turns a large diff into an unreviewable one unless the ruleset is introduced incrementally. Before you turn it on, start from the lite preset, check which of your rules require type information and whether your lint setup provides it, and read the rules table in the README, because a rule that needs types behaves differently in a monorepo where the program is not always built.

## FAQ

### What does eslint-plugin-functional do?

It is an ESLint plugin to disable mutation and promote functional programming in JavaScript and TypeScript. It provides rulesets including strict, recommended and lite presets, plus categorised configurations such as currying, noExceptions, noMutations, noOtherParadigms and noStatements.

### Which eslint-plugin-functional ruleset should I start with?

The README describes the lite preset as good if you're new to functional programming or are converting a large codebase, while recommended is more lenient than strict and allows functional-like coding styles and integration with non-functional third-party libraries.

### Do some eslint-plugin-functional rules need TypeScript type information?

Yes. The rule tables mark rules that require type information, and the plugin ships a Disable Type Checked configuration that disables all rules requiring it. The functional-parameters rule in the currying configuration is one of the marked rules.

### How is the eslint-plugin-functional rule documentation kept in sync?

The rule list in the README and the pages under docs/rules are generated by eslint-doc-generator, which runs in the build:docs script, and the lint:eslint-docs script runs the same tool in check mode so stale documentation fails the lint.

### What licence is eslint-plugin-functional released under?

MIT, with the LICENSE file in the repository root and the license field in package.json set to MIT. The README also notes that the plugin is available through the Tidelift Subscription for enterprise users.

## Sources

- [eslint-functional/eslint-plugin-functional on GitHub](https://github.com/eslint-functional/eslint-plugin-functional)
- [Issues](https://github.com/eslint-functional/eslint-plugin-functional/issues)
- [License: MIT](https://github.com/eslint-functional/eslint-plugin-functional/blob/main/LICENSE)
- [README](https://github.com/eslint-functional/eslint-plugin-functional/blob/main/README.md)
- [Releases](https://github.com/eslint-functional/eslint-plugin-functional/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/eslint-functional-eslint-plugin-functional
