n8n-io/n8n · error · Error

expr(newCredential('${value.name}')) is invalid. Use newCred

Error message

expr(newCredential('${value.name}')) is invalid. Use newCredential() directly in the credentials config, not inside expr().

What it means

expr() only marks a string as an n8n expression by prepending '='. At runtime the SDK AST interpreter can pass non-string values into it, and when that value looks like a NewCredentialImpl (has __newCredential===true and a string name) this dedicated error fires instead of the generic type error. The message tells you credentials must live in the node's credentials config, never inside expr().

Source

Thrown at packages/@n8n/workflow-sdk/src/expression/index.ts:57

function isNewCredentialLike(value: unknown): value is { __newCredential: true; name: string } {
	return (
		typeof value === 'object' &&
		value !== null &&
		'__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_$]*$/;

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Move newCredential() out of expr() and into the node's credentials config object, e.g. addField({ credentials: { myApi: newCredential('My Api') } }).
  2. If you only need the credential name as a string in an expression, pass the literal name string, not the newCredential() result.
  3. Re-run lintWorkflowSource on the SDK file to confirm no expr(newCredential(...)) patterns remain.

Example fix

// before
const n = telegram.addNode({
  parameters: { url: expr(newCredential('myApi')) },
});
// after — credential belongs in credentials config, not in expr()
const n = telegram.addNode({
  parameters: { url: expr('{{ $json.webhookUrl }}') },
  credentials: { myApi: newCredential('myApi') },
});
Defensive patterns

Strategy: type-guard

Validate before calling

// Before calling expr(), confirm the value is a string and not a credential object.
function isStringLiteral(v: unknown): v is string {
  return typeof v === 'string';
}
if (!isStringLiteral(value)) {
  // value is a credential/object — do NOT pass to expr(); put it in credentials config.
}

Type guard

function isNewCredentialLike(v: unknown): v is { __newCredential: true; name: string } {
  return typeof v === 'object' && v !== null && (v as any).__newCredential === true && typeof (v as any).name === 'string';
}

Prevention

When it happens

Trigger: Writing expr(newCredential('My Api')) in SDK builder code, or wrapping a newCredential() call inside a template literal fed to expr() (e.g. expr(`={{ $creds.${newCredential('x')} }}`)). Also triggered when an LLM inlines a credential object as a parameter value through expr().

Common situations: AI-generated workflow code that confuses where credentials go; trying to reference a credential inside an expression string; porting old patterns that put credential names into expression syntax.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/ee91fbc8838286ad. Report an issue: GitHub.