{"record":{"id":"801ebb077d790996","repo":"emberjs/ember.js","slug":"in-tag-modifier-is-not-a-valid-modifier","errorCode":null,"errorMessage":"In ${tag}, ${modifier} is not a valid modifier","messagePattern":"In (.+?), (.+?) is not a valid modifier","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts","lineNumber":670,"sourceCode":"    : b.hash({\n        pairs: [],\n        loc: compiler.source.spanFor(end).collapse('end'),\n      });\n\n  return { path, params, hash };\n}\n\nfunction addElementModifier(\n  element: ParserNodeBuilder<StartTag>,\n  mustache: ASTv1.MustacheStatement\n) {\n  const { path, params, hash, loc } = mustache;\n\n  if (isHBSLiteral(path)) {\n    const modifier = `{{${printLiteral(path)}}}`;\n    const tag = `<${element.name} ... ${modifier} ...`;\n\n    throw generateSyntaxError(`In ${tag}, ${modifier} is not a valid modifier`, mustache.loc);\n  }\n\n  const modifier = b.elementModifier({ path, params, hash, loc });\n  element.modifiers.push(modifier);\n}\n\nfunction repairBlock(\n  source: src.Source,\n  block: HBS.UpstreamBlockStatement,\n  fallbackStart: SourceSpan\n): HBS.BlockStatement {\n  // Extend till the beginning of the block\n  if (!block.program.loc) {\n    const start = block.program.body.at(0);\n    const end = block.program.body.at(-1);\n\n    if (start && end) {\n      block.program.loc = {","sourceCodeStart":652,"sourceCodeEnd":688,"githubUrl":"https://github.com/emberjs/ember.js/blob/26f97246a8bf2e28edf26ac3093da2e86c04ffc5/packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts#L652-L688","documentation":"Element modifiers in Glimmer templates must be identifier paths (helpers like {{on \"click\" ...}} or {{action ...}}), never Handlebars literals. When a mustache used as an element modifier ({{...}} inside a tag) has a literal path, addElementModifier builds a synthetic tag snippet and throws a syntax error, since a literal cannot resolve to a modifier.","triggerScenarios":"Writing a literal inside an element tag as if it were a modifier: <div {{\"foo\"}}></div>, <input {{42}} />, <button {{true}}></button>. Thrown in addElementModifier (called from MustacheStatement handling) when isHBSLiteral(path) is true.","commonSituations":"Typos where a modifier name was deleted or quoted by accident (<div {{\"on\" \"click\" ...}}); copy/paste mangling of the {{on ...}} / {{action ...}} syntax; template editors auto-quoting identifiers.","solutions":["Use a real modifier helper path: <button {{on \"click\" this.save}}>Change</button> or <form {{action \"submit\" on=\"submit\"}}> in classic Ember.","Unquote the modifier name if it was accidentally quoted: {{\"action\" ...}} → {{action ...}}.","Remove the stray literal from the tag if it was leftover text, and validate the template with ember-template-lint to catch it earlier."],"exampleFix":"// before\n<button {{\"onclick\"}}></button>\n\n// after\n<button {{on \"click\" this.handleClick}}>Save</button>","handlingStrategy":"validation","validationCode":"// reject literal mustaches used as element modifiers\nfunction assertValidModifiers(template) {\n  const bad = template.match(/<[A-Za-z][^>]*{{\\s*(\"[^\"]*\"|'[^']*'|\\d+|true|false|null)\\s*[^}]*}}[^>]*>/g);\n  if (bad) throw new Error(`Literal is not a valid modifier: ${bad.join(', ')}`);\n}\nassertValidModifiers(template);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use recognized modifier helpers like {{on \"click\" fn}} or {{action ...}} inside tags","Never quote a modifier name inside the tag","Prefer the modern {{on}} modifier with element modifiers over inline handlers","Run ember-template-lint (rule: no-invalid-role / modifier checks) in CI"],"tags":["glimmer","handlebars","template-syntax","element-modifier"],"backgroundTag":"glimmer-invalid-element-modifier","analyzedSha":"26f97246a8bf2e28edf26ac3093da2e86c04ffc5","analyzedAt":"2026-09-01T05:01:28.182Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}