karatelabs/karate · error · MarkupHintException

ReferenceError: ' ' is not defined — did you mean `_. `?…

Error message

ReferenceError: '${missing}' is not defined — did you mean `_.${missing}`? ka:scope blocks namespace template state via the `_` map; ...

What it means

When JS evaluation in a markup template fails with a ReferenceError for an unknown name, karate-core checks whether the name exists in the template vars and produces a MarkupHintException suggesting `_.<name>`. ka:scope blocks namespace template state via the `_` map, so a variable set in scope is accessed as `_.name`, not as a bare global.

Solutions

  1. Prefix the variable with `_.` as the hint suggests: `_.count` instead of `count`
  2. Verify the exact variable name spelling against what was set via context.set() or `_.x =`
  3. If the value should be a plain template variable, define it in the template model instead of the scope map
  4. Check whether the code was moved into/out of a ka:scope block, which changes the namespace

Example fix

// before
<span>${count}</span>
// after
<span>${_.count}</span>
Defensive patterns

Strategy: type-guard

Validate before calling

// check the name exists in the underscore scope before use
if (!_.hasOwnProperty(name)) throw new Error(name + " not in _ scope; set it via context.set");

Type guard

function scopeRef(name) { return typeof _[name] !== 'undefined' ? _[name] : undefined; }

Try / catch

try { evalTemplate(expr); } catch (MarkupHintException e) { if (e.getMessage().contains("is not defined")) { log.error("scope hint: " + e.getMessage()); } else throw e; }

Prevention

When it happens

Trigger: Referencing a bare identifier inside a template (or inside a ka:scope block) that was stored via context.set() / `_.x = value`, e.g. writing `count` instead of `_.count`; typo'd variable names; variables that only exist in localVars but were shadowed.

Common situations: Authors forgetting the `_` namespace prefix after moving code into a ka:scope block; migration from older templates where variables were plain globals; typos in template variable names.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/a6ca51a058786f5a. Report an issue: GitHub.

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/markup/MarkupTemplateContext.java:282

        Map<String, Object> localVars = new HashMap<>();
        for (String name : getVariableNames()) {
            localVars.put(name, getVariable(name));
        }
        // Bind `_` as the dual-lookup ObjectLike (not the raw vars Map)
        // so template-attribute reads of `_.<name>` fall through to the
        // wrapped Thymeleaf scope when the underscore map is empty.
        localVars.put("_", underscoreView);
        // Strict ReferenceError on missing names — augment with an actionable
        // hint pointing at either the `_.<name>` discipline or the
        // th:with-at-call-site / context.get(...) optional-param pattern.
        try {
            return engine.evalWith(src, localVars);
        } catch (io.karatelabs.js.EngineException e) {
            String missing = extractMissingName(e);
            if (missing == null) {
                throw e;
            }
            throw new MarkupHintException(buildMissingNameHint(missing, e), e);
        }
    }

    private String buildMissingNameHint(String missing, Throwable cause) {
        String base = "ReferenceError: '" + missing + "' is not defined";
        if (vars.containsKey(missing)) {
            return base + " — did you mean `_." + missing + "`? "
                    + "ka:scope blocks namespace template state via the `_` map; "
                    + "bare names must come from a th:with or a parent template binding.";
        }
        return base + " — if `" + missing + "` is a fragment parameter, "
                + "declare it via th:with at the call site "
                + "(e.g. <div th:insert=\"~{file::frag}\" th:with=\"" + missing + ": value\">). "
                + "If it's optional, read it inside the fragment via "
                + "context.get('" + missing + "') or context.get('" + missing + "', defaultValue) — "
                + "returns null (or the default) when the name is unbound.";
    }

View on GitHub (pinned to a22eb90246)