n8n-io/n8n · error · Error
expr() requires a string argument, but received ${typeof val
Error message
expr() requires a string argument, but received ${typeof value}. What it means
expr() requires a string argument. If the value passed is neither a string nor a NewCredentialImpl-like object, this generic error reports the actual typeof it received. It exists because the AST interpreter does not enforce the TypeScript string annotation at runtime.
Source
Thrown at packages/@n8n/workflow-sdk/src/expression/index.ts:61
'__newCredential' in value &&
(value as Record<string, unknown>).__newCredential === true &&
'name' in value &&
typeof (value as Record<string, unknown>).name === 'string'
);
}
export function expr(expression: string): string {
if (typeof expression !== 'string') {
// At runtime, the AST interpreter may pass non-string values (e.g. NewCredentialImpl objects).
// TypeScript narrows to `never` here since the param is typed as `string`,
// so we re-bind as `unknown` to perform runtime type checks.
const value: unknown = expression;
if (isNewCredentialLike(value)) {
throw new Error(
`expr(newCredential('${value.name}')) is invalid. Use newCredential() directly in the credentials config, not inside expr().`,
);
}
throw new Error(`expr() requires a string argument, but received ${typeof value}.`);
}
// Strip any leading '=' to prevent double-equals patterns from LLM output
const normalized = expression.startsWith('=') ? expression.slice(1) : expression;
return '=' + normalized;
}
// =============================================================================
// Explicit Node JSON Reference Generator
// =============================================================================
type NodeJsonReference = NodeInstance<string, string, unknown> | string;
const IDENTIFIER_PATH_SEGMENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
function resolveNodeName(node: NodeJsonReference): string {
return typeof node === 'string' ? node : node.name;
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Coerce the value to a string before calling expr(), e.g. expr(`${$json.count}`) or expr(String(value)).
- Confirm the upstream field is actually a string; if it can be undefined, default it first.
- Use expr() only for literal expression text containing {{ }} syntax, not for arbitrary data.
Example fix
// before
const url = expr($json.baseUrl); // $json.baseUrl may be undefined/non-string
// after
const url = expr(`={{ $json.baseUrl }}`); // pass a literal expression string Defensive patterns
Strategy: type-guard
Validate before calling
// Coerce or reject non-strings before expr().
function asExprString(v: unknown): string {
if (typeof v !== 'string') throw new TypeError(`expr() needs a string, got ${typeof v}`);
return v;
}
const out = expr(asExprString(maybeValue)); Type guard
function isString(v: unknown): v is string { return typeof v === 'string'; } Prevention
- Only pass literal expression strings (with {{ }}) to expr().
- Coerce numbers/booleans with String() or template literals before expr().
- Default potentially-undefined fields before passing them in.
When it happens
Trigger: Calling expr(123), expr(someObject), expr(undefined), expr($json.count) where $json.count is a number, or expr(true). Also expr() with no argument.
Common situations: Passing numeric/boolean/object workflow data into expr(); LLM forgetting to interpolate a value into a string; referencing a field whose runtime type is not string.
Related errors
- expr(newCredential('${value.name}')) is invalid. Use newCred
- nodeJson() requires a non-empty JSON path.
- nodeJson() requires a node or node name.
- ${toolName} was registered without a runSubAgent callback, a
- Invalid memory configuration. Use: new Memory() for in-proce
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/95d883224cbad418.
Report an issue: GitHub.