CLI tool
Shopify/erb_lint avatar
Shopify/erb_lint

erb_lint checks ERB templates for the rules a Ruby linter cannot see

Lint your ERB or HTML files

749 stars141 forksRubyMIT

At a glance

What is it?
erb_lint is a Shopify Ruby gem that lints ERB and HTML files, running whitespace and tag style checks on templates and, optionally, RuboCop against the Ruby inside them. The trade is a config file that must reference real RuboCop and better_html files or the tool errors out.
Who is it for?
erb_lint suits a Rails or Sinatra shop that has decided template markup is code and wants whitespace, tag style and security rules enforced in CI rather than left to review, and it suits teams that already run RuboCop and want the same rules applied inside templates.
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?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Ruby, 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

Templates are markup with Ruby in them, and RuboCop only sees half

The problem erb_lint addresses is that an ERB template is two things at once, a piece of HTML and a piece of Ruby, and ordinary tooling handles them separately. RuboCop reads your .rb files and knows nothing about whether the ERB tag spacing in a view is consistent. A browser or an HTML validator reads the output and never sees the Ruby that produced it. The gem is described as a tool to help lint your ERB or HTML files using the included linters or by writing your own, which is the framing to keep in mind. The intended user is a team with a large view layer and a codebase where a whitespace or script-tag mistake is cheap to make and annoying to review. The other stated use is writing your own linter, which means the gem is meant to be extended rather than only configured. Installation is either a gem install or a Gemfile entry:

bash
gem install erb_lint
ruby
gem 'erb_lint', require: false

Fourteen linters run by default, six more are opt-in

The linter table is split into two groups, and the split is the most useful thing in the README. Fourteen are enabled by default. AllowedScriptType prevents script tags whose type attribute is not on a whitelist. NoJavascriptTagHelper prevents the use of Rails' javascript_tag. RequireInputAutocomplete warns about input tags missing autocomplete attributes. FinalNewline warns when a template lacks a trailing newline. RightTrim enforces trimming at the right of an ERB tag, SpaceAroundErbTag enforces a single space after the opening and before the closing delimiter, and SelfClosingTag enforces self-closing style for void elements. The remaining set covers whitespace and structure: ClosingErbTagIndent, CommentSyntax for bad ERB comment syntax, ExtraNewline, ParserErrors, SpaceIndentation, SpaceInHtmlTag and TrailingWhitespace. That is a lot of opinionated defaults shipped on.

The opt-in linters are the interesting half

Six linters are off unless you ask for them, and they cover the failure modes a Ruby linter cannot reach. ErbSafety detects unsafe interpolation of Ruby data into JavaScript contexts and pushes you toward safe helpers like .to_json, and it depends on a better_html config file. RequireScriptNonce warns about missing Content Security Policy nonces in script tags. HardCodedString warns about visible hardcoded strings in the DOM, though it does not check for a hardcoded string nested inside a JavaScript tag. PartialInstanceVariable detects instance variables in partials, which is the class of bug that breaks when a partial is rendered from somewhere you did not expect. StrictLocals enforces strict locals in Rails view partial templates. DeprecatedClasses reports classes on HTML elements that violate a rule set you provide. Rubocop runs RuboCop rules on the Ruby statements found inside ERB templates, which is the bridge between the two halves of the file.

The config file references other config files, and they have to exist

Configuration lives in a .erb_lint.yml file in the project, and the shape of it is worth reading carefully because of what it implies. A minimal file turns on the default linters, and the README notes that creating it is not mandatory since the tool works out of the box with default settings. But the example shown points at two external files, .better-html.yml for the ErbSafety linter and .rubocop.yml inherited by the Rubocop linter, and the README warns that both must exist or you get an error, though you can omit the references. Here is the documented structure:

yaml
---
EnableDefaultLinters: true
linters:
  ErbSafety:
    enabled: true
    better_html_config: .better-html.yml
  Rubocop:
    enabled: true
    rubocop_config:
      inherit_from:
        - .rubocop.yml

The result is that the tool is configurable but the default example is not self-contained. A team that copies it and does not add a .better-html.yml will get a failure rather than a silently weaker check, which is the safer of the two failure modes but worth knowing before the first CI run.

Globs and excludes, and the prefix that catches people out

The set of files checked is controlled by a glob and an exclude list, and the README calls out one detail explicitly. By default the tool runs on all ERB files in the current directory or its descendants, matching `**/*.html{+*,}.erb`, and you can change the pattern, for example to cover other extensions, and add excludes. The warning is about the excludes: you must add `**/` to exclude patterns, because that is what matches the target files' absolute paths. Without it an exclude written as `vendor/**/*` will not behave as expected, which is a quiet misconfiguration rather than a loud one. The default configuration demonstrates the shape:

yaml
---
glob: "**/*.{html,text,js}{+*,}.erb"
exclude:
  - '**/vendor/**/*'
  - '**/node_modules/**/*'
linters:
  ErbSafety:
    enabled: true
    better_html_config: .better-html.yml
  Rubocop:
    enabled: true
    rubocop_config:
      inherit_from:
        - .rubocop.yml

The same exclude mechanism works at two levels, with a global exclude list and a per-linter exclude, so a single linter can be scoped away from part of the tree without switching it off everywhere.

Inline disable comments, and the linter that complains about them

Individual rules can be silenced at the point of the offense with a disable comment, and the format is prescribed. The example places the comment on the offending line, in an ERB comment, naming the rule:

erb
<hr /> <%# erb_lint:disable SelfClosingTag %>

There is a second lever on the same mechanism. Enabling NoUnusedDisable makes the tool raise an error when a useless disable comment is present, so a comment left behind after the code it silenced has been changed stops being dead weight. The --disable-inline-configs option goes further and turns off inline comments entirely, reporting all offenses, which is the setting to use if your policy is that suppressions belong in the config file rather than in the template. The ErbSafety linter also carries its own exclude list, so a directory can be exempted from the safety check while still being linted for whitespace.

The repository shows a small gem with a real release process

The top level listing is what you would expect from a maintained internal tool published to RubyGems. There is an exe/ directory for the command line entry point, lib/ for the library code, spec/ for the RSpec suite, and an Rakefile. The gemspec, a Gemfile, a Gemfile.lock and a .ruby-version sit at the root, along with a .rubocop.yml for the project's own style rules, a .rspec file for the test runner configuration and a .devcontainer/ directory for a reproducible development environment. The .github/ directory holds the CI workflow, whose badge is the one shown at the top of the README. A shipit.rubygems.yml file at the root indicates the release path is the internal one, and the licence is MIT with the text in LICENSE.txt. The requirements are stated plainly too, Ruby 2.3.0 or newer, because of the safe navigation operator and the tilde-heredoc syntax used in some tests.

Editorial conclusion

erb_lint suits a Rails or Sinatra shop that has decided template markup is code and wants whitespace, tag style and security rules enforced in CI rather than left to review, and it suits teams that already run RuboCop and want the same rules applied inside templates. It does not suit a project that wants one tool covering templates and plain Ruby files, because erb_lint only walks ERB globs, and it does not suit a team unwilling to keep the referenced config files in place, since a missing .better-html.yml or .rubocop.yml is an error rather than a warning. Before adopting, create .erb_lint.yml and confirm the two referenced config files exist, keep the exclude patterns prefixed with `**/`, and note that the last push to the repository was on 2026-04-20 with the newest tagged release v0.9.0 dated 2025-01-20.

Frequently asked questions

How do I install erb_lint?

Standalone, install the gem with `gem install erb_lint`. As a project dependency, add `gem 'erb_lint', require: false` to your Gemfile and run `bundle install`. Ruby 2.3.0 or newer is required.

Do I need an .erb_lint.yml configuration file?

No, the README states the tool works out of the box with default settings and the config file is not mandatory. If you do create one and it references .better-html.yml or .rubocop.yml, those referenced files must exist or the tool will error.

Which linters are enabled by default in erb_lint?

Fourteen are default, including AllowedScriptType, CommentSyntax, FinalNewline, NoJavascriptTagHelper, ParserErrors, RequireInputAutocomplete, RightTrim, SelfClosingTag, SpaceAroundErbTag and the whitespace checks. Six are opt-in, including ErbSafety, HardCodedString, PartialInstanceVariable, Rubocop, RequireScriptNonce and StrictLocals.

How do I disable a single erb_lint rule on one line?

Place a disable comment on the offending line naming the rule, for example `<hr /> <%# erb_lint:disable SelfClosingTag %>`. Enabling NoUnusedDisable makes the tool raise an error for such a comment that no longer suppresses anything, and the --disable-inline-configs option turns inline comments off entirely.

Why are my erb_lint exclude patterns not working?

The README notes that you must add **/ to exclude patterns, because that is what matches the target files' absolute paths. An exclude written without that prefix will not behave as intended.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. Shopify/erb_lint on GitHub
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/shopify-erb-lint.svg)](https://hysenlabs.com/projects/shopify-erb-lint)

Was this useful?