eslint-plugin-ember

May 15, 2026 · View on GitHub

NPM version NPM downloads CI

An ESLint plugin that provides a set of rules for Ember applications based on commonly known good practices.

❗️Requirements

🚀 Usage

1. Install plugin

npm install --save-dev eslint-plugin-ember

2. Update your config

// eslint.config.js (flat config)
const eslintPluginEmberRecommended = require('eslint-plugin-ember/configs/recommended');

module.exports = [
  ...eslintPluginEmberRecommended,
];

or

// .eslintrc.js (legacy config)
module.exports = {
  plugins: ['ember'],
  extends: [
    'eslint:recommended',
    'plugin:ember/recommended' // or other configuration
  ],
  rules: {
    // override / enable optional rules
    'ember/no-replace-test-comments': 'error'
  }
};

gts/gjs

lint files having First-Class Component Templates (fcct)

learn more at ember-template-imports

Note

special care should be used when setting up parsers, since they cannot be overwritten. thus they should be used in override only and specific to file types

gjs/gts support is provided by the ember-eslint-parser

Note

if you import .gts files in .ts files, then ember-eslint-parser is required for .ts as well to enable typed linting

// .eslintrc.js
module.exports = {
  overrides: [
    {
      files: ['**/*.{js,ts}'],
      plugins: ['ember'],
      parser: '@typescript-eslint/parser',
      extends: [
        'eslint:recommended',
        'plugin:ember/recommended', // or other configuration
      ],
      rules: {
        // override / enable optional rules
        'ember/no-replace-test-comments': 'error'
      }
    },
    {
      files: ['**/*.gts'],
      parser: 'ember-eslint-parser',
      plugins: ['ember'],
      extends: [
        'eslint:recommended',
        'plugin:@typescript-eslint/recommended',
        'plugin:ember/recommended',
        'plugin:ember/recommended-gts',
      ],
    },
    {
      files: ['**/*.gjs'],
      parser: 'ember-eslint-parser',
      plugins: ['ember'],
      extends: [
        'eslint:recommended',
        'plugin:ember/recommended',
        'plugin:ember/recommended-gjs',
      ],
    },
    {
      files: ['tests/**/*.{js,ts,gjs,gts}'],
      rules: {
        // override / enable optional rules
        'ember/no-replace-test-comments': 'error'
      }
    },
  ],
};

rules applied to fcct templates

  • semi rule, same as prettier plugin
  • no-undef rule will take effect for template vars (includes js scope)
  • no-unused rule will take effect for template block params

rules in templates can be disabled with eslint directives with mustache or html comments:

<template>
  <div>
    {{!eslint-disable-next-line}}
    {{test}}
  </div>
  <div>
    {{!--eslint-disable--}}
    {{test}}
    {{test}}
    {{test}}
    {{!--eslint-enable--}}
  </div>
</template>
<template>
  <div>
    <!--eslint-disable-next-line-->
    {{test}}
  </div>
  <div>
    <!-- eslint-disable -->
    {{test}}
    {{test}}
    {{test}}
    <!-- eslint-enable -->
  </div>
</template>

Migrating from ember-template-lint

If you are replacing ember-template-lint with this plugin, use the template-lint-migration config to get the equivalent set of rules:

// eslint.config.js (flat config)
import eslintPluginEmberRecommended from 'eslint-plugin-ember/configs/recommended';
import eslintPluginEmberTemplateLintMigration from 'eslint-plugin-ember/configs/template-lint-migration';

export default [
  ...eslintPluginEmberRecommended,
  ...eslintPluginEmberTemplateLintMigration,
  {
    rules: {
      // Migrate custom overrides from your .template-lintrc.*:
      'ember/template-no-bare-strings': ['error', { allowlist: ['×'] }],
      'ember/template-no-inline-styles': 'off',
    },
  },
];

template-lint-migration mirrors the ember-template-lint recommended preset.

Linting .hbs files

ESLint flat config only picks up .hbs files when a files glob names them. Add a dedicated block so they route to the Handlebars parser:

// eslint.config.mjs
import { hbsParser, plugin as emberPlugin } from 'eslint-plugin-ember/recommended';

export default [
  // ...other blocks...
  {
    files: ['app/**/*.hbs'],
    languageOptions: { parser: hbsParser },
    plugins: { ember: emberPlugin },
    rules: {
      'ember/template-no-bare-strings': 'error',
      // ...other template rules...
    },
  },
];

Make sure no earlier @typescript-eslint/parser block's files glob reaches .hbs — narrow it to ['**/*.{js,ts,gjs,gts}'] (or similar). Flat config merges rules across every matching block, so even if our HBS block overrides the parser, type-info rules from a matching TS block still layer on and fail with errors like:

Parsing error: was not found by the project service because the extension for the file (.hbs) is non-standard.

or

Error while loading rule @typescript-eslint/await-thenable: You have used a rule which requires type information.

Replacing template-lint-disable comments

Inline disable directives need to be rewritten to ESLint's syntax, prefixed with ember/template-. For now, only two scopes are supported: the next line, or the rest of the file. For example, replace:

{{!template-lint-disable no-invalid-role}}

with:

{{!eslint-disable-next-line ember/template-no-invalid-role}}

The template-no-template-lint-directives rule (enabled by the template-lint-migration config) does this rewrite for you: run eslint --fix once and it converts every template-lint-disable / template-lint-enable comment in your templates.

To disable a rule for an entire .gjs/.gts file, use a regular ESLint file-level directive in the JS region — it applies to the <template> contents as well:

/* eslint-disable ember/template-no-invalid-role */

<template>
  <div role="range"></div>
</template>

🧰 Configurations

Name
base
recommended
gjs logorecommended-gjs
gts logorecommended-gts
📋template-lint-migration

🍟 Rules

💼 Configurations enabled in.
✅ Set in the recommended configuration.
gjs logo Set in the recommended-gjs configuration.
gts logo Set in the recommended-gts configuration.
📋 Set in the template-lint-migration configuration.
🔧 Automatically fixable by the --fix CLI option.
💡 Manually fixable by editor suggestions.

Accessibility

Name                                           Description💼🔧💡
template-link-href-attributesrequire href attribute on link elements📋
template-no-abstract-rolesdisallow abstract ARIA roles📋
template-no-accesskey-attributedisallow accesskey attribute📋🔧
template-no-aria-hidden-bodydisallow aria-hidden on body element📋🔧
template-no-aria-unsupported-elementsdisallow ARIA roles, states, and properties on elements that do not support them📋
template-no-autofocus-attributedisallow autofocus attribute📋🔧
template-no-duplicate-landmark-elementsdisallow duplicate landmark elements without unique labels📋
template-no-empty-headingsdisallow empty heading elements📋
template-no-heading-inside-buttondisallow heading elements inside button elements📋
template-no-invalid-aria-attributesdisallow invalid aria-* attributes📋
template-no-invalid-interactivedisallow non-interactive elements with interactive handlers📋
template-no-invalid-link-textdisallow invalid or uninformative link text content📋
template-no-invalid-link-titledisallow invalid title attributes on link elements📋
template-no-invalid-roledisallow invalid ARIA roles📋
template-no-nested-interactivedisallow nested interactive elements📋
template-no-nested-landmarkdisallow nested landmark elements📋
template-no-pointer-down-event-bindingdisallow pointer down event bindings📋
template-no-positive-tabindexdisallow positive tabindex values📋
template-no-redundant-roledisallow redundant role attributes📋🔧
template-no-unsupported-role-attributesdisallow ARIA attributes that are not supported by the element role📋🔧
template-no-whitespace-within-worddisallow excess whitespace within words (e.g. "W e l c o m e")📋
template-require-aria-activedescendant-tabindexrequire non-interactive elements with aria-activedescendant to have tabindex📋🔧
template-require-context-rolerequire ARIA roles to be used in appropriate context📋
template-require-iframe-titlerequire iframe elements to have a title attribute📋
template-require-input-labelrequire label for form input elements📋
template-require-lang-attributerequire lang attribute on html element📋
template-require-mandatory-role-attributesrequire mandatory ARIA attributes for ARIA roles📋
template-require-media-captionrequire captions for audio and video elements📋
template-require-presentational-childrenrequire presentational elements to only contain presentational children📋
template-require-valid-alt-textrequire valid alt text for images and other elements📋
template-require-valid-form-groupsrequire grouped form controls to have fieldset/legend or WAI-ARIA group labeling
template-table-groupsrequire table elements to use table grouping elements📋

Best Practices

Name                                            Description💼🔧💡
template-builtin-component-argumentsdisallow setting certain attributes on builtin components📋
template-no-action-modifiersdisallow usage of {{action}} modifiers🔧
template-no-action-on-submit-buttondisallow action attribute on submit buttons📋
template-no-args-pathsdisallow args.foo paths in templates, use @foo instead📋🔧
template-no-arguments-for-html-elementsdisallow @arguments on HTML elements📋
template-no-array-prototype-extensionsdisallow usage of Ember Array prototype extensions📋🔧
template-no-at-ember-render-modifiersdisallow usage of @ember/render-modifiers📋
template-no-bare-stringsdisallow bare strings in templates (require translation/localization)
template-no-bare-yielddisallow templates whose only meaningful content is a bare {{yield}}
template-no-block-params-for-html-elementsdisallow block params on HTML elements📋
template-no-builtin-form-componentsdisallow usage of built-in form components📋
template-no-capital-argumentsdisallow capital arguments (use lowercase @arg instead of @Arg)📋
template-no-chained-thisdisallow redundant this.this in templates🔧
template-no-class-bindingsdisallow passing classBinding or classNameBindings as arguments in templates📋
template-no-curly-component-invocationdisallow curly component invocation, use angle bracket syntax instead📋🔧
template-no-debuggerdisallow {{debugger}} in templates📋
template-no-duplicate-attributesdisallow duplicate attribute names in templates📋🔧
template-no-duplicate-iddisallow duplicate id attributes📋
template-no-dynamic-subexpression-invocationsdisallow dynamic subexpression invocations
template-no-element-event-actionsdisallow element event actions (use {{on}} modifier instead)
template-no-forbidden-elementsdisallow specific HTML elements📋
template-no-html-commentsdisallow HTML comments in templates📋🔧
template-no-implicit-thisrequire explicit this in property access📋
template-no-index-component-invocationdisallow index component invocations📋
template-no-inline-event-handlersdisallow DOM event handler attributes
template-no-inline-linktodisallow inline form of LinkTo component🔧
template-no-inline-stylesdisallow inline styles📋
template-no-input-blockdisallow block usage of {{input}} helper📋
template-no-input-tagnamedisallow tagName attribute on {{input}} helper📋
template-no-invalid-metadisallow invalid meta tags📋
template-no-logdisallow {{log}} in templates📋
template-no-model-argument-in-route-templatesdisallow @model argument in route templates🔧
template-no-multiple-empty-linesdisallow multiple consecutive empty lines in templates🔧
template-no-mut-helperdisallow usage of (mut) helper
template-no-negated-conditiondisallow negated conditions in if/unless📋🔧
template-no-nested-splattributesdisallow nested ...attributes usage📋
template-no-obscure-array-accessdisallow obscure array access patterns like objectPath.@each.property📋🔧
template-no-obsolete-elementsdisallow obsolete HTML elements📋
template-no-outlet-outside-routesdisallow {{outlet}} outside of route templates📋
template-no-page-title-componentdisallow usage of ember-page-title component
template-no-passed-in-event-handlersdisallow passing event handlers directly as component arguments📋
template-no-positional-data-test-selectorsdisallow positional data-test-* params in curly invocations📋🔧
template-no-potential-path-stringsdisallow potential path strings in attribute values📋
template-no-redundant-fndisallow unnecessary usage of (fn) helper📋🔧
template-no-restricted-invocationsdisallow certain components, helpers or modifiers from being used
template-no-splattributes-with-classdisallow splattributes with class attribute
template-no-template-lint-directivesdisallow {{! template-lint-* }} directives (use the {{! eslint-* }} equivalents)📋🔧
template-no-this-in-template-only-componentsdisallow this in template-only components🔧
template-no-trailing-spacesdisallow trailing whitespace at the end of lines in templates🔧
template-no-unavailable-thisdisallow this in templates that are not inside a class or function
template-no-unnecessary-component-helperdisallow unnecessary component helper📋🔧
template-no-unnecessary-concatdisallow unnecessary string concatenation🔧
template-no-unnecessary-curly-parensdisallow unnecessary parentheses enclosing statements in curlies📋🔧
template-no-unused-block-paramsdisallow unused block parameters in templates📋
template-no-valueless-argumentsdisallow valueless named arguments📋
template-no-whitespace-for-layoutdisallow using whitespace for layout purposes📋
template-no-yield-block-params-to-else-inversedisallow yielding block params to else or inverse block
template-no-yield-onlydisallow components that only yield📋
template-no-yield-to-defaultdisallow yield to default block📋
template-require-button-typerequire button elements to have a valid type attribute📋🔧
template-require-each-keyrequire key attribute in {{#each}} loops🔧
template-require-form-methodrequire form method attribute🔧
template-require-has-block-helperrequire (has-block) helper usage instead of hasBlock property📋🔧
template-require-iframe-src-attributerequire iframe elements to have src attribute🔧
template-require-input-typerequire input elements to have a valid type attribute🔧
template-require-splattributesrequire splattributes usage in component templates
template-require-strict-moderequire templates to be in strict mode
template-require-valid-named-block-naming-formatrequire valid named block naming format📋🔧
template-self-closing-void-elementsrequire self-closing on void elements🔧
template-simple-modifiersrequire simple modifier syntax📋
template-simple-unlessrequire simple conditions in unless blocks📋🔧
template-sort-invocationsrequire sorted attributes and modifiers🔧
template-splat-attributes-onlydisallow ...spread other than ...attributes📋
template-style-concatenationdisallow string concatenation in inline styles📋

Components

Name                        Description💼🔧💡
no-attrs-in-componentsdisallow usage of this.attrs in components
no-attrs-snapshotdisallow use of attrs snapshot in the didReceiveAttrs and didUpdateAttrs component hooks
no-builtin-form-componentsdisallow usage of built-in form components
no-classic-componentsenforce using Glimmer components
no-component-lifecycle-hooksdisallow usage of "classic" ember component lifecycle hooks. Render modifiers or custom functional modifiers should be used instead.
no-on-calls-in-componentsdisallow usage of on to call lifecycle hooks in components
require-tagless-componentsdisallow using the wrapper element of a component

Computed Properties

Name                                                           Description💼🔧💡
computed-property-gettersenforce the consistent use of getters in computed properties
no-arrow-function-computed-propertiesdisallow arrow functions in computed properties
no-assignment-of-untracked-properties-used-in-tracking-contextsdisallow assignment of untracked properties that are used as computed property dependencies🔧
no-computed-properties-in-native-classesdisallow using computed properties in native classes
no-deeply-nested-dependent-keys-with-eachdisallow usage of deeply-nested computed property dependent keys with @each
no-duplicate-dependent-keysdisallow repeating computed property dependent keys🔧
no-incorrect-computed-macrosdisallow incorrect usage of computed property macros🔧
no-invalid-dependent-keysdisallow invalid dependent keys in computed properties🔧
no-side-effectsdisallow unexpected side effects in computed properties
no-volatile-computed-propertiesdisallow volatile computed properties
require-computed-macrosrequire using computed property macros when possible🔧
require-computed-property-dependenciesrequire dependencies to be declared statically in computed properties🔧
require-return-from-computeddisallow missing return statements in computed properties
use-brace-expansionenforce usage of brace expansion in computed property dependent keys

Controllers

NameDescription💼🔧💡
alias-model-in-controllerenforce aliasing model in controllers
avoid-using-needs-in-controllersdisallow using needs in controllers
no-controllersdisallow non-essential controllers

Deprecations

NameDescription💼🔧💡
closure-actionsenforce usage of closure actions
new-module-importsenforce using "New Module Imports" from Ember RFC #176
no-array-prototype-extensionsdisallow usage of Ember's Array prototype extensions🔧
no-at-ember-render-modifiersdisallow importing from @ember/render-modifiers
no-deprecated-router-transition-methodsenforce usage of router service transition methods🔧
no-function-prototype-extensionsdisallow usage of Ember's function prototype extensions
no-implicit-injectionsenforce usage of implicit service injections🔧
no-mixinsdisallow the usage of mixins
no-new-mixinsdisallow the creation of new mixins
no-observersdisallow usage of observers
no-old-shimsdisallow usage of old shims for modules🔧
no-string-prototype-extensionsdisallow usage of String prototype extensions
template-deprecated-inline-view-helperdisallow inline {{view}} helper📋🔧
template-deprecated-render-helperdisallow {{render}} helper📋🔧
template-no-actiondisallow {{action}} helper📋
template-no-attrs-in-componentsdisallow attrs in component templates📋
template-no-link-to-positional-paramsdisallow positional params in LinkTo component📋
template-no-link-to-tagnamedisallow tagName attribute on LinkTo component📋
template-no-route-actiondisallow usage of route-action helper📋
template-no-unbounddisallow {{unbound}} helper📋
template-no-withdisallow {{with}} helper📋

Ember Data

Name                              Description💼🔧💡
no-empty-attrsdisallow usage of empty attributes in Ember Data models
require-async-inverse-relationshiprequire inverse to be specified in @belongsTo and @hasMany decorators
use-ember-data-rfc-395-importsenforce usage of @ember-data/ package imports instead ember-data🔧

Ember Object

Name                                Description💼🔧💡
avoid-leaking-state-in-ember-objectsdisallow state leakage
no-getrequire using ES5 getters instead of Ember's get / getProperties functions🔧
no-get-with-defaultdisallow usage of the Ember's getWithDefault function🔧
no-modifier-argument-destructuringdisallow destructuring of modifier arguments to avoid consuming tracked data too early
no-proxiesdisallow using array or object proxies
no-try-invokedisallow usage of the Ember's tryInvoke util
require-super-in-lifecycle-hooksrequire super to be called in lifecycle hooks🔧
use-ember-get-and-setenforce usage of Ember.get and Ember.set🔧

Ember Octane

Name                                Description💼🔧💡
classic-decorator-hooksenforce using correct hooks for both classic and non-classic classes
classic-decorator-no-classic-methodsdisallow usage of classic APIs such as get/set in classes that aren't explicitly decorated with @classic
no-actions-hashdisallow the actions hash in components, controllers, and routes
no-classic-classesdisallow "classic" classes in favor of native JS classes
no-ember-super-in-es-classesdisallow use of this._super in ES class methods🔧
no-empty-glimmer-component-classesdisallow empty backing classes for Glimmer components
no-tracked-built-insenforce usage of @ember/reactive/collections imports instead of tracked-built-ins🔧
no-tracked-properties-from-argsdisallow creating @tracked properties from this.args
template-no-deprecateddisallow using deprecated Glimmer components, helpers, and modifiers in templates
template-no-let-referencedisallow referencing let variables in <template>gjs logo gts logo

jQuery

NameDescription💼🔧💡
jquery-ember-rundisallow usage of jQuery without an Ember run loop
no-global-jquerydisallow usage of global jQuery object
no-jquerydisallow any usage of jQuery

Miscellaneous

Name                                              Description💼🔧💡
named-functions-in-promisesenforce usage of named functions in promises
no-html-safedisallow the use of htmlSafe
no-incorrect-calls-with-inline-anonymous-functionsdisallow inline anonymous functions as arguments to debounce, once, and scheduleOnce
no-invalid-debug-function-argumentsdisallow usages of Ember's assert() / warn() / deprecate() functions that have the arguments passed in the wrong order.
no-restricted-property-modificationsdisallow modifying the specified properties🔧
no-runloopdisallow usage of @ember/runloop functions
require-fetch-importenforce explicit import for fetch()

Possible Errors

Name                                                Description💼🔧💡
template-no-extra-mut-helper-argumentdisallow passing more than one argument to the mut helper📋
template-no-jsx-attributesdisallow JSX-style camelCase attributes🔧
template-no-scope-outside-table-headingsdisallow scope attribute outside th elements📋
template-no-shadowed-elementsdisallow ambiguity with block param names shadowing HTML elements📋
template-no-unbalanced-curliesdisallow unbalanced mustache curlies📋
template-no-unknown-arguments-for-builtin-componentsdisallow unknown arguments for built-in components📋🔧

Routes

Name                            Description💼🔧💡
no-capital-letters-in-routesdisallow routes with uppercased letters in router.js
no-controller-access-in-routesdisallow routes from accessing the controller outside of setupController/resetController
no-private-routing-servicedisallow injecting the private routing service
no-shadow-route-definitionenforce no route path definition shadowing
no-unnecessary-index-routedisallow unnecessary index route definition
no-unnecessary-route-path-optiondisallow unnecessary usage of the route path option🔧
route-path-styleenforce usage of kebab-case (instead of snake_case or camelCase) in route paths💡
routes-segments-snake-caseenforce usage of snake_cased dynamic segments in routes

Security

Name                      Description💼🔧💡
template-link-rel-noopenerrequire rel="noopener noreferrer" on links with target="_blank"📋🔧
template-no-triple-curliesdisallow usage of triple curly brackets (unescaped variables)📋

Services

Name                                     Description💼🔧💡
no-implicit-service-injection-argumentdisallow omitting the injected service name argument🔧
no-restricted-service-injectionsdisallow injecting certain services under certain paths
no-unnecessary-service-injection-argumentdisallow unnecessary argument when injecting services🔧
no-unused-servicesdisallow unused service injections (see rule doc for limitations)💡

Style

NameDescription💼🔧💡
template-no-quoteless-attributesrequire quotes on all attribute values📋🔧
template-no-unnecessary-curly-stringsdisallow unnecessary curly braces in string interpolations📋🔧

Stylistic Issues

Name                          Description💼🔧💡
order-in-componentsenforce proper order of properties in components🔧
order-in-controllersenforce proper order of properties in controllers🔧
order-in-modelsenforce proper order of properties in models🔧
order-in-routesenforce proper order of properties in routes🔧
template-attribute-indentationenforce proper indentation of attributes and arguments in multi-line templates
template-attribute-orderenforce consistent ordering of attributes in template elements🔧
template-block-indentationenforce consistent indentation for block statements and their children🔧
template-eol-lastrequire or disallow newline at the end of template files🔧
template-linebreak-styleenforce consistent linebreaks in templates🔧
template-modifier-name-caserequire dasherized names for modifiers🔧
template-no-only-default-slotdisallow using only the default slot🔧
template-quotesenforce consistent quote style in templates🔧
template-template-lengthenforce template size constraints

Testing

Name                                      Description💼🔧💡
no-current-route-namedisallow usage of the currentRouteName() test helper
no-ember-testing-in-module-scopedisallow use of Ember.testing in module scope
no-invalid-test-waitersdisallow incorrect usage of test waiter APIs
no-legacy-test-waitersdisallow the use of the legacy test waiter APIs
no-noop-setup-on-error-in-beforedisallows using no-op setupOnerror in before or beforeEach🔧
no-pause-testdisallow usage of the pauseTest helper in tests
no-replace-test-commentsdisallow 'Replace this with your real tests' comments in test files
no-restricted-resolver-testsdisallow the use of patterns that use the restricted resolver in tests
no-settled-after-test-helperdisallow usage of await settled() right after test helper that calls it internally🔧
no-test-and-thendisallow usage of the andThen test wait helper
no-test-import-exportdisallow importing of "-test.js" in a test file and exporting from a test file
no-test-module-fordisallow usage of moduleFor, moduleForComponent, etc
no-test-support-importdisallow importing of "test-support" files in production code.
no-test-this-renderdisallow usage of the this.render in tests, recommending to use @ember/test-helpers' render instead.
prefer-ember-test-helpersenforce usage of @ember/test-helpers methods over native window methods
require-valid-css-selector-in-test-helpersdisallow using invalid CSS selectors in test helpers🔧

🍻 Contribution Guide

If you have any suggestions, ideas, or problems, feel free to create an issue, but first please make sure your question does not repeat previous ones.

Creating a New Rule

  • Create an issue with a description of the proposed rule
  • Create files for the new rule:
    • lib/rules/new-rule.js (implementation, see no-proxies for an example)
    • docs/rules/new-rule.md (documentation, start from the template -- raw, rendered)
    • tests/lib/rules/new-rule.js (tests, see no-proxies for an example)
  • Run pnpm update to automatically update the README and other files (and re-run this if you change the rule name or description)
  • Make sure your changes will pass CI by running:
    • pnpm test
    • pnpm lint (pnpm lint:js --fix can fix many errors)
  • Create a PR and link the created issue in the description

Note that new rules should not immediately be added to the recommended configuration, as we only consider such breaking changes during major version updates.

🔓 License

See the LICENSE file for license rights and limitations (MIT).