solhint: a Solidity linter with rule inheritance and a narrow fix set
Solhint is an open-source project to provide a linting utility for Solidity code.
At a glance
- What is it?
- solhint is an MIT-licensed JavaScript CLI that lints Solidity for security and style rules. Its useful parts are the layered .solhint.json hierarchy and the plugin path resolution; its weakest part is a --fix flag that covers only nine rules.
- Who is it for?
- Adopt solhint if you have more than one Solidity package in a single repository and want rules to differ per directory. Do not adopt it expecting an automatic formatter: --fix touches nine named rules and nothing else.
- 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 6 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 September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem solhint addresses, and the teams it fits
Solhint is a command line linter for Solidity. The README states that it provides both Security and Style Guide validations, which is the whole pitch: one tool that flags unsafe patterns and formatting drift in .sol files. It is written in JavaScript and ships on npm, so the install path is the Node toolchain rather than a Solidity-native binary. That matters for who can use it. A team already running npm scripts, ESLint and a JS-based CI pipeline can add solhint as one more binary. A team whose contracts are built entirely inside Foundry with no Node present is adding a runtime dependency just to lint.
The audience the README implies is contract repositories with a directory structure worth governing. The multiple config feature, the .solhintignore file and the plugin system all assume a project big enough to have more than one source root. A single-file example contract does not need any of that. The security rules are the reason most people arrive; the style rules are the reason they stay, because style violations are cheap to fix and security findings are not.
How rule resolution works when configs are nested
The core mechanism is a merge across .solhint.json files found on the path from the repository root down to the file being linted. The README is explicit that all config files must be named .solhint.json, otherwise the hierarchy silently stops working. Given a root config plus one in interfaces/, linting interfaces/InterfaceRules.sol uses the interfaces config for any rule it defines, and falls back to the root config for rules it does not. If the same rule appears in both, the file closest to the source file wins.
That is a real design choice with consequences. You can enforce a strict rule set at the root and loosen one rule for a legacy directory without duplicating the entire config. The cost is that a rule's effective value is not visible from any single file. To answer why a particular rule fired, you have to walk the directory tree upward. The README's own worked example is the clearest documentation of this, and it is worth reading before you build a hierarchy you cannot debug.
Plugins resolve differently. Solhint looks for them from process.cwd(), then from each entry in pluginPaths, then from each pluginPath's node_modules. The README says the purpose is environments where solhint runs outside the project folder, naming IDE and editor integrations. If a plugin fails to load, solhint warns and continues with core rules and any other valid plugins. That is a deliberate choice to keep linting rather than abort, and it means a broken plugin produces a warning rather than a failed run.
Getting it running: install, init, lint
The README gives a global install and a version check:
npm install -g solhint solhint --version
Then generate a config before linting anything:
solhint --init
This writes a .solhint.json containing "extends": "solhint:recommended". The README notes that the older solhint:default preset, which held only max-line-length and no-console, is deprecated as of version 5.1.0. New projects should start from recommended.
Linting takes globs as arguments. The README's examples are solhint 'contracts/**/*.sol' for a directory and solhint contracts/MyToken.sol for a single file. Running solhint with no arguments prints usage rather than linting.
Several flags are worth knowing before you wire this into CI. -f, --formatter accepts stylish, table, tap, unix, json, compact or sarif. -w, --max-warnings sets an allowed warning count and the README notes it works in quiet mode as well, which is the flag combination you want if the build should fail on errors but tolerate a warning budget. -q, --quiet reports errors only. -c, --config points at a specific rules file and the README states it is not compatible with multiple configs, so choosing it opts you out of the hierarchy described above. --cache lints only files changed since the last run, with --cache-location setting the cache file path. --save writes a report named YYYYMMDDHHMMSS_solhintReport.txt into the current folder. There is also a stdin command for piping source in, a list-rules command that prints the rules covered by the current .solhint.json, and --disc to skip the update check the tool performs by default.
What --fix actually repairs, and what it does not
The README lists the rules --fix currently works on, and the list is short: avoid-throw, avoid-sha3, no-console, explicit-types, private-vars-underscore, payable-fallback, quotes, contract-name-capwords and avoid-suicide. Nine rules. Everything else in the rule set is report-only.
This is the single most misunderstood part of the tool, and the README does not oversell it, but the name --fix invites the assumption that it is a formatter. It is not. If your team's goal is consistent indentation, line breaking and spacing across a large contracts directory, solhint will report what it dislikes and leave the rewriting to you. The fix set is closer to mechanical substitutions: rename a variable with a leading underscore, swap a deprecated function name, change quote style. Those are the changes where the correct edit is unambiguous.
The related flag --noPrompt suppresses the suggestion to back up files when a fix option is selected. That the prompt exists at all tells you the maintainers treat --fix as capable of destructive edits, which is the right posture given it rewrites source in place.
The limitations that decide whether solhint is the right tool
The config hierarchy is powerful and fragile at the same time. It depends on a filename convention. Rename a config to something else and the README's own description implies the hierarchy stops applying, with no error. Silent misconfiguration in a linter is worse than a crash, because the run still passes.
Plugin failure is similarly quiet. A plugin that fails to load produces a warning and linting continues. In an interactive session that is helpful. In CI, where warnings are easy to miss and often not fatal, a plugin that stopped resolving means a whole category of rules silently stopped running while the build stayed green. If you rely on plugins, the warning needs to be treated as an error in your pipeline, and the README does not describe a flag that does that for you.
The --config flag cannot be combined with multiple configs. Teams that want an explicit config path for reproducibility give up the per-directory inheritance. You have to pick one.
Finally, there is no indication in the supplied material that solhint compiles, type-checks or reasons about contract semantics. It is a linter over source. It will not tell you that a function is unreachable or that a state transition is impossible. Treating a clean solhint run as evidence of contract safety would be a category error.
Where solhint sits next to a compiler-based analyzer
The obvious comparison is a static analyzer that works from compiler output rather than from source text, such as Slither. The difference in approach is structural, not a matter of rule counts. Solhint parses Solidity into an AST, which is why the repository lists ast among its topics, and applies pattern rules to that tree plus style rules that are largely textual. Slither operates on the compiled representation and builds a model of the contract's state and call graph.
That distinction predicts where each one is useful. Solhint is good at the things a compiler does not care about: naming conventions, quote style, deprecated function names, a banned console call left in a production file. It is a fast, cheap gate that runs on every save. A compiler-based analyzer is where you look for reentrancy paths, unguarded state changes and cross-contract interactions. Running solhint alone covers hygiene. Running an analyzer alone leaves style drift unchecked. Most teams that care about both end up running two tools, and the honest framing is that solhint is the cheaper half.
The overlap is in the security-labelled rules. avoid-suicide, avoid-sha3 and avoid-throw are pattern matches on known-bad constructs. They catch the obvious instance of a deprecated or dangerous call. They do not catch a correctly-named function that is called in the wrong order.
Maintenance, releases and licence terms
The repository is not archived and the default branch is develop. Recent releases in the supplied material run v6.2.2 in June 2026, v6.2.3 in June 2026 and v6.2.4 in August 2026, with the last push to the repository dated September 2026. That is a cadence of small patch releases rather than long gaps, which suggests ongoing maintenance rather than a dormant project. The README names a tech lead, dbale-arg, and points to a Discord server, so there is a named contact path rather than an anonymous issue tracker.
The licence is MIT, stated in the README badge and in the repository metadata. MIT is permissive: it allows commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That matters if you fork the rule set for an internal standard or vendor a patched build. This is a description of what the licence text generally permits, not legal advice, and the LICENSE file in the repository is the authoritative document.
Upgrade cost is the part worth planning for. Solhint checks for newer versions on each run unless --disc is passed, so an air-gapped CI runner will either need that flag or will spend time on a network call that cannot succeed. The deprecated solhint:default preset is a concrete migration item: any config still extending it should move to solhint:recommended, since the README marks the old preset deprecated as of 5.1.0. Across the 6.x line the option surface has grown (pluginPaths, --noPrompt, --noPoster, cache controls) rather than changed shape, which is a lower-risk upgrade path than a config format rewrite would be.
Editorial conclusion
Adopt solhint if you have more than one Solidity package in a single repository and want rules to differ per directory. Do not adopt it expecting an automatic formatter: --fix touches nine named rules and nothing else. Before committing, run solhint --init, then solhint 'contracts/**/*.sol' with the generated solhint:recommended preset, and check which of your existing violations the fix list actually covers.
Community notes