ember/template-no-mut-helper
April 13, 2026 · View on GitHub
Disallow usage of the (mut) helper.
The (mut) helper was used in classic Ember to create two-way bindings. In modern Ember (Octane and beyond), this pattern is discouraged in favor of explicit one-way data flow with actions or setters.
Rule Details
This rule disallows using the (mut) helper in templates.
Reasons to not use the mut helper
-
General problems in the programming model:
- The mut helper is non-intuitive to use, see, teach, and learn since it can either be a getter or a setter based on the context in which it’s used.
Example:
{{#let (mut this.foo) as |foo|}} <!-- When used like this, it's a getter --> {{foo}} <!-- When used like this, it's a setter --> <button {{action foo 123}}>Update Foo</button> {{/let}}- The need for the no-extra-mut-helper-argument rule is further evidence that
muthas a non-intuitive signature and frequently gets misused. - The mut helper is usually only used as a pure setter, in which case there are other template helpers that are pure setters that could be used instead of mut (e.g. ember-set-helper).
-
Incompatibility with Glimmer Component intentions:
- The mut helper can re-introduce 2 way data binding into Glimmer Components on named arguments where a child can change a parent’s data, which goes against the Data Down Actions Up principle, goes against Glimmer Components’ intention to have immutable arguments, and is discouraged by the Ember Core team.
Example:
<input type='checkbox' checked={{@checked}} {{on 'change' (fn (mut @checked) (not @checked))}} />
What this rule does
This rule forbids any use of the mut helper, both as a getter and a setter, in any context. It also
surfaces possible alternatives in the lint violation message to help guide engineers to resolving
the lint violations.
Examples
Incorrect ❌
<Input @value={{this.name}} @onChange={{mut this.name}} />
{{input value=(mut this.name)}}
<CustomComponent @onChange={{mut this.value}} />
Correct ✅
<Input @value={{this.name}} @onChange={{this.updateName}} />
<Input @value={{this.name}} @onChange={{fn this.updateName}} />
<CustomComponent @onChange={{this.handleChange}} />
Migration
- When used as a pure setter only,
mutcould be replaced by a JS action ("Option 1" below) or ember-set-helper ("Option 2" below):
Before:
<MyComponent @closeDropdown={{action (mut this.setIsDropdownOpen) false}} />
After (Option 1 HBS):
<MyComponent @closeDropdown={{action this.setIsDropdownOpen false}} />
After (Option 1 JS):
// in your component class
class MyComponent extends Component {
@action
setIsDropdownOpen(isDropdownOpen) {
set(this, 'isDropdownOpen', isDropdownOpen);
}
}
After (Option 2):
<MyComponent @closeDropdown={{set this 'isDropdownOpen' false}} />
2. When used as a pure getter only, mut could be removed:
Before:
<MyComponent @isDropdownOpen={{mut this.isDropdownOpen}} />
After:
<MyComponent @isDropdownOpen={{this.isDropdownOpen}} />
3. When mut is used as a getter and setter, mut could be replaced with a different namespace for the property and a dedicated action function to set the property: (Note: another other option could be to pull in the pick helper from ember-composable-helpers and use it like this.) (Note: Another option could be to use ember-box).
Before:
{{#let (mut this.foo) as |foo|}}
{{foo}}
<input onchange={{action foo value=”target.value”}} />
{{/let}}
After HBS:
{{this.foo}}
<input {{on “change” this.updateFoo}} />
After JS:
// in your component class
class MyComponent extends Component {
@tracked
foo;
@action
updateFoo(evt) {
this.foo = evt.target.value;
// or set(this, 'foo', evt.target.value); for legacy Ember code
}
}
4. When mut is being passed into a built-in classic component that uses 2 way data binding, mut could be removed:
Before:
<Input @value={{mut this.profile.description}} />
After:
<Input @value={{this.profile.description}} />
Options
| Name | Type | Default | Description |
|---|---|---|---|
setterAlternative | string | If provided, the error message suggests using this helper instead of (mut). |