{"record":{"id":"ee91fbc8838286ad","repo":"n8n-io/n8n","slug":"expr-newcredential-value-name-is-invalid-u","errorCode":null,"errorMessage":"expr(newCredential('${value.name}')) is invalid. Use newCredential() directly in the credentials config, not inside expr().","messagePattern":"expr\\(newCredential\\('(.+?)'\\)\\) is invalid\\. Use newCredential\\(\\) directly in the credentials config, not inside expr\\(\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/workflow-sdk/src/expression/index.ts","lineNumber":57,"sourceCode":"function isNewCredentialLike(value: unknown): value is { __newCredential: true; name: string } {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'__newCredential' in value &&\n\t\t(value as Record<string, unknown>).__newCredential === true &&\n\t\t'name' in value &&\n\t\ttypeof (value as Record<string, unknown>).name === 'string'\n\t);\n}\n\nexport function expr(expression: string): string {\n\tif (typeof expression !== 'string') {\n\t\t// At runtime, the AST interpreter may pass non-string values (e.g. NewCredentialImpl objects).\n\t\t// TypeScript narrows to `never` here since the param is typed as `string`,\n\t\t// so we re-bind as `unknown` to perform runtime type checks.\n\t\tconst value: unknown = expression;\n\t\tif (isNewCredentialLike(value)) {\n\t\t\tthrow new Error(\n\t\t\t\t`expr(newCredential('${value.name}')) is invalid. Use newCredential() directly in the credentials config, not inside expr().`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(`expr() requires a string argument, but received ${typeof value}.`);\n\t}\n\t// Strip any leading '=' to prevent double-equals patterns from LLM output\n\tconst normalized = expression.startsWith('=') ? expression.slice(1) : expression;\n\treturn '=' + normalized;\n}\n\n// =============================================================================\n// Explicit Node JSON Reference Generator\n// =============================================================================\n\ntype NodeJsonReference = NodeInstance<string, string, unknown> | string;\n\nconst IDENTIFIER_PATH_SEGMENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/workflow-sdk/src/expression/index.ts#L39-L75","documentation":"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().","triggerScenarios":"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().","commonSituations":"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.","solutions":["Move newCredential() out of expr() and into the node's credentials config object, e.g. addField({ credentials: { myApi: newCredential('My Api') } }).","If you only need the credential name as a string in an expression, pass the literal name string, not the newCredential() result.","Re-run lintWorkflowSource on the SDK file to confirm no expr(newCredential(...)) patterns remain."],"exampleFix":"// before\nconst n = telegram.addNode({\n  parameters: { url: expr(newCredential('myApi')) },\n});\n// after — credential belongs in credentials config, not in expr()\nconst n = telegram.addNode({\n  parameters: { url: expr('{{ $json.webhookUrl }}') },\n  credentials: { myApi: newCredential('myApi') },\n});","handlingStrategy":"type-guard","validationCode":"// Before calling expr(), confirm the value is a string and not a credential object.\nfunction isStringLiteral(v: unknown): v is string {\n  return typeof v === 'string';\n}\nif (!isStringLiteral(value)) {\n  // value is a credential/object — do NOT pass to expr(); put it in credentials config.\n}","typeGuard":"function isNewCredentialLike(v: unknown): v is { __newCredential: true; name: string } {\n  return typeof v === 'object' && v !== null && (v as any).__newCredential === true && typeof (v as any).name === 'string';\n}","tryCatchPattern":null,"preventionTips":["Never wrap newCredential() in expr() — credentials belong in the node's credentials config object.","Run lintWorkflowSource() on generated SDK code to catch expr(newCredential(...)) before deploy.","Treat expr() as taking only literal expression text containing {{ }} syntax."],"tags":["expression","credentials","sdk","runtime"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}