emberjs/ember.js · error · SyntaxError

Changing context using "../" is not supported in Glimmer

Error message

Changing context using "../" is not supported in Glimmer

What it means

Glimmer's Handlebars parser rejects path expressions that begin with '../' because Glimmer templates have a single, flat scope: dynamic context switching via relative paths was an Ember-Classic (curly component / pre-Glimmer) behavior that no longer exists. The parser inspects every PathExpression's original string during handlebars-node-visitors traversal and throws a syntax error with the offending path's source span.

Source

Thrown at packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts:405

  SubExpression(sexpr: HBS.SubExpression): ASTv1.SubExpression {
    const { path, params, hash } = acceptCallNodes(this, sexpr);
    return b.sexpr({ path, params, hash, loc: this.source.spanFor(sexpr.loc) });
  }

  PathExpression(path: HBS.PathExpression): ASTv1.PathExpression {
    const { original } = path;
    let parts: string[];

    if (original.indexOf('/') !== -1) {
      if (original.slice(0, 2) === './') {
        throw generateSyntaxError(
          `Using "./" is not supported in Glimmer and unnecessary`,
          this.source.spanFor(path.loc)
        );
      }
      if (original.slice(0, 3) === '../') {
        throw generateSyntaxError(
          `Changing context using "../" is not supported in Glimmer`,
          this.source.spanFor(path.loc)
        );
      }
      if (original.indexOf('.') !== -1) {
        throw generateSyntaxError(
          `Mixing '.' and '/' in paths is not supported in Glimmer; use only '.' to separate property paths`,
          this.source.spanFor(path.loc)
        );
      }
      parts = [path.parts.join('/')];
    } else if (original === '.') {
      throw generateSyntaxError(
        `'.' is not a supported path in Glimmer; check for a path with a trailing '.'`,
        this.source.spanFor(path.loc)
      );
    } else {
      parts = path.parts;

View on GitHub (pinned to 26f97246a8)

Solutions

  1. Rewrite the path to reference the value directly via a block param or local variable instead of the parent context (e.g. wrap the each in {{each items as |item|}} and use {{item.name}}).
  2. Bind the needed outer value to a local: {{#let this.parent as |parent|}} ... {{parent.x}} {{/let}}, or pass it down as an argument/component prop.
  3. Use a helper or getter in the backing class to expose the outer-scope value instead of reaching up the context stack.
  4. If this is a generated/legacy template, update the code generator so it no longer emits '../' paths.

Example fix

// before
{{#each items}}
  {{title}} of {{../sectionName}}
{{/each}}

// after
{{#each items as |item|}}
  {{item.title}} of {{this.sectionName}}
{{/each}}
Defensive patterns

Strategy: validation

Validate before calling

// before compiling a template
function assertNoRelativePaths(template) {
  const offenders = template.match(/\.\.\/[^\s}]+/g);
  if (offenders) throw new Error(`Relative '../' paths not allowed: ${offenders.join(', ')}`);
}
assertNoRelativePaths(template);

Prevention

When it happens

Trigger: Any template compiled with @glimmer/syntax (e.g. via Ember's template compiler) containing a path expression whose original string starts with '../', such as {{../foo}}, {{../item.name}}, or (../parentHelper arg). The check runs in the PathExpression visitor when original.slice(0,3) === '../'.

Common situations: Migrating legacy Ember (pre-Octane / pre-Glimmer) templates to modern Glimmer; copying patterns from old blog posts using contextual components or itemController-era relative paths; nested each/if blocks that relied on ../ to reach an outer scope's variable instead of using block params.

Related errors


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/d9239923e6dc9a80. Report an issue: GitHub.