nexu-io/open-design · error · Error
template binding must resolve to a scalar: ${binding}
Error message
template binding must resolve to a scalar: ${binding} What it means
Thrown by scalarOrThrow when a resolved binding value is an Array or a non-null object. Live artifact interpolation only substitutes scalar values (string, number, boolean, null); binding a path that resolves to a structured value would either render '[object Object]' / JSON spam, or invite unsafe serialization, so the render rejects it and asks the author to drill down to a scalar.
Source
Thrown at apps/daemon/src/live-artifacts/render.ts:79
if (!/^\d+$/.test(segment)) throw new Error(`invalid array segment in template binding path: ${rawPath}`);
current = current[Number(segment)];
continue;
}
if (typeof current !== 'object') return '';
current = (current as Record<string, unknown>)[segment];
}
return current ?? '';
}
function readTemplatePath(dataJson: BoundedJsonObject, rawPath: string): unknown {
const segments = rawPath.split('.');
if (segments.shift() !== 'data') throw new Error(`unsupported template binding path: ${rawPath}`);
return walkPath(dataJson, segments, rawPath);
}
function scalarOrThrow(value: unknown, binding: string): string {
if (Array.isArray(value) || (value !== null && typeof value === 'object')) {
throw new Error(`template binding must resolve to a scalar: ${binding}`);
}
return escapeHtmlTemplateValue(value);
}
function rootResolver(dataJson: BoundedJsonObject): BindingResolver {
return (binding) => {
if (!TEMPLATE_PATH.test(binding) || !binding.startsWith('data')) {
throw new Error(`invalid template binding path: ${binding}`);
}
return scalarOrThrow(readTemplatePath(dataJson, binding), binding);
};
}
function childResolver(parent: BindingResolver, varName: string, item: unknown): BindingResolver {
return (binding) => {
if (!TEMPLATE_PATH.test(binding)) throw new Error(`invalid template binding path: ${binding}`);
const segments = binding.split('.');
if (segments[0] !== varName) return parent(binding);View on GitHub (pinned to 5be4028344)
Solutions
- Drill the path to a scalar leaf: {{data.user.name}} instead of {{data.user}}.
- For arrays, iterate with a repeat directive and bind a scalar inside the loop body.
- If you must surface structured data, select individual scalar fields in the refresh source rather than dumping the object.
Example fix
// before
<div>{{data.user}}</div>
// after
<div>{{data.user.name}} ({{data.user.email}})</div> Defensive patterns
Strategy: type-guard
Validate before calling
function isScalarLeaf(v: unknown): v is string | number | boolean | null {
return v === null || ['string','number','boolean'].includes(typeof v);
} Type guard
function isScalar(v: unknown): v is string | number | boolean | null {
return v === null || typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean';
} Prevention
- Bind only leaf scalar fields; arrays/objects are not rendered.
- Project nested objects down to scalar fields in the refresh source output.
- Use the repeat directive to iterate arrays instead of binding them whole.
When it happens
Trigger: {{data.items}} where data.items is an array; {{data.user}} where data.user is an object; {{data.repo}} bound to the full repository object from the GitHub metric instead of a single field like {{data.repo.full_name}}.
Common situations: Model binds a whole sub-object expecting JSON pretty-printing; data shape expanded (a flat field became nested); forgetting to select a leaf field from the refresh source output.
Related errors
- input.fields must be an array of strings
- invalid array segment in template binding path: ${rawPath}
- unsupported template binding path: ${rawPath}
- invalid template binding path: ${binding}
- connector refresh source requires connector metadata
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/230b099616342e4f.
Report an issue: GitHub.