{"record":{"id":"1b55bee77abcba24","repo":"n8n-io/n8n","slug":"sdk-placeholder-wrapped","errorCode":"SDK_PLACEHOLDER_WRAPPED","errorMessage":"Do not wrap placeholder() in expr(). Use placeholder('hint') directly as the parameter value.","messagePattern":"Do not wrap placeholder\\(\\) in expr\\(\\)\\. Use placeholder\\('hint'\\) directly as the parameter value\\.","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts","lineNumber":306,"sourceCode":"\t\t\t\t\t\tlintIssue({\n\t\t\t\t\t\t\tcode: 'SDK_FORBIDDEN_CONSTRUCT',\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t`'.${method}()' is not available on SDK builder objects. Build strings with template ` +\n\t\t\t\t\t\t\t\t'literals, or do transforms in a Code node / expr().',\n\t\t\t\t\t\t\t...locationOf(call),\n\t\t\t\t\t\t\tlintTarget: 'sdk',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isExprCall(call)) {\n\t\t\t\tfor (const arg of call.arguments) {\n\t\t\t\t\tif (arg.type === 'SpreadElement') continue;\n\t\t\t\t\tif (arg.type === 'CallExpression' && isPlaceholderCall(arg)) {\n\t\t\t\t\t\tissues.push(\n\t\t\t\t\t\t\tlintIssue({\n\t\t\t\t\t\t\t\tcode: 'SDK_PLACEHOLDER_WRAPPED',\n\t\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\t\"Do not wrap placeholder() in expr(). Use placeholder('hint') directly as the parameter value.\",\n\t\t\t\t\t\t\t\t...locationOf(call),\n\t\t\t\t\t\t\t\tlintTarget: 'sdk',\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (arg.type === 'TemplateLiteral') {\n\t\t\t\t\t\tfor (const expr of arg.expressions) {\n\t\t\t\t\t\t\tif (expr.type === 'CallExpression' && isPlaceholderCall(expr)) {\n\t\t\t\t\t\t\t\tissues.push(\n\t\t\t\t\t\t\t\t\tlintIssue({\n\t\t\t\t\t\t\t\t\t\tcode: 'SDK_PLACEHOLDER_WRAPPED',\n\t\t\t\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\t\t\t'Do not embed placeholder() inside expr()/template strings. Use placeholder() as the direct parameter value.',\n\t\t\t\t\t\t\t\t\t\t...locationOf(call),\n\t\t\t\t\t\t\t\t\t\tlintTarget: 'sdk',\n\t\t\t\t\t\t\t\t\t}),","sourceCodeStart":288,"sourceCodeEnd":324,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts#L288-L324","documentation":"The linter rejects direct calls to `placeholder(...)` that appear as an argument to `expr(...)`. `placeholder('hint')` is itself an expression marker for an unset value the user must fill in; wrapping it in `expr()` defeats its purpose — the parameter should receive `placeholder('hint')` directly. The walker detects a `CallExpression` arg whose callee is `placeholder` inside an `expr()` call and emits `SDK_PLACEHOLDER_WRAPPED` at workflow-sdk-lint.ts:303-313.","triggerScenarios":"Writing `expr(placeholder('apiKey'))`, `expr(`prefix-${placeholder('x')}`)` (the template-literal case is a sibling check), or otherwise passing a direct `placeholder()` call as an argument to `expr()`.","commonSituations":"Agents wrapping every dynamic value in `expr()` defensively. Confusion about whether `placeholder()` is a runtime expression or a parameter marker. Copy-paste from an example that used `expr()` for a real lookup.","solutions":["Use `placeholder('hint')` directly as the parameter value — do not wrap it in `expr()`.","If the value needs runtime computation, use `expr()` with a real expression body referencing `$json` / `$now` / `$('Node')` — not `placeholder()`.","Audit every `expr(...)` argument and ensure none is a direct `placeholder(...)` call."],"exampleFix":"// before\naddNode('http', { url: expr(placeholder('apiUrl')) });\n\n// after — placeholder() is the direct parameter value\naddNode('http', { url: placeholder('apiUrl') });","handlingStrategy":"validation","validationCode":"import { parse } from 'acorn';\n\nfunction placeholderWrappedInExpr(builderSource: string): boolean {\n  const ast = parse(builderSource, { ecmaVersion: 'latest', sourceType: 'module', locations: true });\n  let found = false;\n  walk(ast, (n: any) => {\n    if (n.type !== 'CallExpression') return;\n    if (n.callee.type !== 'Identifier' || n.callee.name !== 'expr') return;\n    for (const arg of n.arguments) {\n      if (arg.type === 'SpreadElement') continue;\n      if (arg.type === 'CallExpression' && arg.callee.type === 'Identifier' && arg.callee.name === 'placeholder') {\n        found = true;\n      }\n    }\n  });\n  return found;\n}","typeGuard":"import type { Node, CallExpression } from 'estree';\n\nconst isPlaceholderCall = (n: Node): n is CallExpression =>\n  n.type === 'CallExpression' && n.callee.type === 'Identifier' && n.callee.name === 'placeholder';\n\nconst isExprCall = (n: Node): n is CallExpression =>\n  n.type === 'CallExpression' && n.callee.type === 'Identifier' && n.callee.name === 'expr';","tryCatchPattern":null,"preventionTips":["Use `placeholder('hint')` as the direct parameter value — never wrap it in `expr()`.","Audit every `expr(...)` argument; if it contains `placeholder(...)`, unwrap it.","Lint generated builder source with `lintWorkflowSdkSource`."],"tags":["sdk-lint","workflow-sdk","placeholder","expr","agent-guidance","builder-code"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}