Template Placeholder In Regular String

ID

javascript.template_placeholder_in_regular_string

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

reliability, string, suspicious-literal

Description

Reports a single- or double-quoted string that carries a ${…​} placeholder. Interpolation only happens inside backticks. In a quoted string the placeholder is ordinary text, so the value the author meant to insert never appears and the literal braces reach the user instead.

const greeting = "Hello ${name}";    // FLAW — the output is literally: Hello ${name}
log('retry in ${delay}ms');          // FLAW

A placeholder needs a non-empty body and a closing brace to count, so an isolated "${" — a fragment being concatenated, or a documented syntax being described — is not reported. An escaped "\${name}" is deliberate and stays quiet.

${…​} is also the placeholder syntax of a dozen other languages that keep their templates inside JavaScript strings — Knockout bindings, Magento UI components, AWS flow-log formats, GitHub Actions expressions, shell parameter expansion, SQL templates. None of those was ever meant to interpolate here, so a placeholder is reported only when it could actually have worked as one: the body has to parse, whole, as a JavaScript expression, it has to name something, and every name it reads has to resolve in a scope the string can see.

That is what tells the foreign syntaxes apart, one by one. ${{ …​ }} is not an expression at all. ${VAR:-false} is not one either — the whole body has to be consumed, and stopping after VAR does not count. ${account-id} does parse, as a subtraction, but neither account nor id is declared anywhere near. ${1} names nothing. What survives is ${name}, ${user.id} and ${a + b} — the expressions a developer reaching for a template literal actually writes.

A placeholder rooted at $ is left out whether or not the name resolves. $ is the sigil of the interpolation syntax itself, and every engine that borrows ${…​} uses it, or a $-prefixed name, for its own context object rather than for anything in the host file: Knockout’s $data and $parent, Magento’s '${ $.provider }:data', Vue’s $props. Resolution alone cannot separate those, because in a file that also loads jQuery $ is a declared name — and what a backtick would then have interpolated is a library handle, [object Object], never the value the template meant.

Rationale

  • Nothing fails. There is no exception, no undefined, no type error — the wrong text simply ships, which is exactly why the defect survives review and reaches production.

  • The mistake is one character wide: a quote where a backtick belonged. It arrives when a template literal is pasted into code that already used quotes, or when a concatenation is rewritten into an interpolation and one string in the group is missed.

  • Placeholders show up in user-visible text far more often than anywhere else — greetings, error messages, log lines — so the failure is visible to exactly the audience that should never see it.

Remediation

Change the quotes to backticks, or build the string explicitly.

const greeting = `Hello ${name}`;
const greeting = "Hello " + name;

© 2026 Xygeni. All rights reserved