mastra-ai/mastra · error · Error
${describeBadPlaceholder(template, idx, rawExpr)} must be of
Error message
${describeBadPlaceholder(template, idx, rawExpr)} must be of the form ${stepResults.<stepId>} or ${stepResults.<stepId>.<path>}. What it means
For placeholders scoped to stepResults, the expression must contain a step ID: ${stepResults.<stepId>} or ${stepResults.<stepId>.<path>}. If there is no step ID (e.g. "${stepResults}" or "${stepResults.}"), the template is invalid and validateTemplate throws.
Source
Thrown at packages/core/src/workflows/mapping-template.ts:68
* the value a primitive) stay in {@link resolveTemplate}.
*/
export function validateTemplate(template: string): void {
let idx = 0;
for (const match of template.matchAll(TEMPLATE_PLACEHOLDER)) {
idx++;
const rawExpr = match[1] ?? '';
if (rawExpr.length === 0 || rawExpr !== rawExpr.trim()) {
throw new Error(
`${describeBadPlaceholder(template, idx, rawExpr)} has empty or whitespace-padded contents. ` +
`Use \${<scope>.<path>} with no surrounding whitespace.`,
);
}
const { scope, rest } = parseTemplatePlaceholder(rawExpr);
if (scope === 'stepResults') {
const innerDot = rest.indexOf('.');
const stepId = innerDot === -1 ? rest : rest.slice(0, innerDot);
if (!stepId) {
throw new Error(
`${describeBadPlaceholder(template, idx, rawExpr)} must be of the form \${stepResults.<stepId>} or \${stepResults.<stepId>.<path>}.`,
);
}
continue;
}
if (scope === 'requestContext') {
if (!rest) {
throw new Error(
`${describeBadPlaceholder(template, idx, rawExpr)} requires a request-context key — use \${requestContext.<key>}.`,
);
}
continue;
}
if ((TEMPLATE_NAMESPACES as readonly string[]).includes(scope)) continue;
throw new Error(
`${describeBadPlaceholder(template, idx, rawExpr)} references unknown namespace "${scope}". ` +
`Use one of: ${TEMPLATE_NAMESPACES.join(', ')}.`,
);View on GitHub (pinned to 75dd419e61)
Solutions
- Add the step ID: "${stepResults.myStep}" or "${stepResults.myStep.output.field}".
- Fix accidental double dots or trailing dots in the expression.
- Add a validateTemplate() unit test over all mapping constants to catch this before runtime.
Example fix
// before
mapVariable({ value: '${stepResults.}' });
// after
mapVariable({ value: '${stepResults.fetchUser.name}' }); Defensive patterns
Strategy: validation
Validate before calling
const tpl = '${stepResults.fetchUser.name}';
for (const m of tpl.matchAll(/\$\{([^}]*)\}/g)) {
const [scope, ...rest] = m[1].split('.');
if (scope === 'stepResults' && (!rest[0] || !rest[0].trim())) {
throw new Error(`Placeholder '${m[0]}' is missing a step ID`);
}
} Try / catch
try {
validateTemplate(tpl);
} catch (err) {
if (err instanceof Error && err.message.includes('stepResults.<stepId>')) {
console.error(`Template '${tpl}' needs a step ID after stepResults`);
} else throw err;
} Prevention
- Always write stepResults.<stepId> or stepResults.<stepId>.<path> — never the bare scope.
- Extract templates into typed constants and validate them in tests.
- Beware of double/trailing dots introduced during manual edits.
When it happens
Trigger: Templates like "${stepResults}" or "${stepResults..path}" where the step ID portion before the first dot is empty.
Common situations: Forgetting the step ID entirely; double dots after refactoring; autocompletion inserting just the scope name.
Related errors
- ${describeBadPlaceholder(template, idx, rawExpr)} has empty
- ${describeBadPlaceholder(template, idx, rawExpr)} requires a
- ${describeBadPlaceholder(template, idx, rawExpr)} references
- mapping entries cannot appear inside .parallel(), .branch(),
- Mapping references unknown workflow init-data "${source.init
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/16fd608a0a06f564.
Report an issue: GitHub.