n8n-io/n8n · error · ExpressionExtensionError
renameKeys(): expected an even amount of args: from1, to1 [,
Error message
renameKeys(): expected an even amount of args: from1, to1 [, from2, to2, ...]. e.g. .renameKeys("name", "title") What it means
Thrown as ExpressionExtensionError by the .renameKeys() array extension when extraArgs is empty or has an odd length. renameKeys takes key pairs (from1,to1,from2,to2,...) and renames matching keys on each object in the array; an odd count means one 'from' has no matching 'to', so the call is ambiguous and rejected.
Source
Thrown at packages/@n8n/expression-runtime/src/extensions/array-extensions.ts:191
return o;
}, {});
}
function chunk(value: unknown[], extraArgs: number[]) {
const [chunkSize] = extraArgs;
if (typeof chunkSize !== 'number' || chunkSize === 0) {
throw new ExpressionExtensionError('chunk(): expected non-zero numeric arg, e.g. .chunk(5)');
}
const chunks: unknown[][] = [];
for (let i = 0; i < value.length; i += chunkSize) {
chunks.push(value.slice(i, i + chunkSize));
}
return chunks;
}
function renameKeys(value: unknown[], extraArgs: string[]): unknown[] {
if (extraArgs.length === 0 || extraArgs.length % 2 !== 0) {
throw new ExpressionExtensionError(
'renameKeys(): expected an even amount of args: from1, to1 [, from2, to2, ...]. e.g. .renameKeys("name", "title")',
);
}
return value.map((v) => {
if (typeof v !== 'object' || v === null) {
return v;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
const newObj = { ...(v as any) };
const chunkedArgs = chunk(extraArgs, [2]) as string[][];
chunkedArgs.forEach(([from, to]) => {
if (from in newObj) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
newObj[to] = newObj[from];
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
delete newObj[from];
}
});View on GitHub (pinned to 5ac6606e81)
Solutions
- Pass arguments in pairs: .renameKeys('name','title').
- For multiple renames, keep the count even: .renameKeys('name','title','age','years').
- If building args dynamically, assert args.length % 2 === 0 before calling.
- Check that template-expanded argument lists didn't drop a value.
Example fix
// before — odd number of args
{{ $json.rows.renameKeys('name', 'title', 'age') }}
// after — even number of args (pairs)
{{ $json.rows.renameKeys('name', 'title', 'age', 'years') }} Defensive patterns
Strategy: validation
Validate before calling
function renameKeysSafe(arr, ...pairs) {
if (pairs.length === 0 || pairs.length % 2 !== 0) {
throw new TypeError('renameKeys requires an even number of args: from1, to1, ...');
}
// ...proceed
} Type guard
function hasEvenNonEmptyArgs(args) { return Array.isArray(args) && args.length > 0 && args.length % 2 === 0; } Try / catch
try {
result = arr.renameKeys('name', 'title');
} catch (e) {
if (e.name === 'ExpressionExtensionError' && /renameKeys/i.test(e.message)) {
// supply args in pairs: from1, to1 [, from2, to2, ...]
}
} Prevention
- Pass arguments in pairs: .renameKeys('name','title').
- Assert args.length % 2 === 0 before calling.
- When building the arg list dynamically, verify no element was dropped.
- Test with the documented example first.
When it happens
Trigger: Calling .renameKeys() with zero arguments, or an odd number of arguments. Examples: [...].renameKeys() (empty), [...].renameKeys('name') (one arg, odd), [...].renameKeys('a','b','c') (three args, odd).
Common situations: Forgetting the second half of a pair. Building the args list dynamically and dropping one element. Copy-paste error leaving a dangling 'from' without a 'to'.
Related errors
- arguments must be passed to pluck
- smartJoin(): expected two string args, e.g. .smartJoin("name
- chunk(): expected non-zero numeric arg, e.g. .chunk(5)
- merge(): expected object arg
- merge(): expected array arg, e.g. .merge([{ id: 1, otherValu
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/21ce25ed83051998.
Report an issue: GitHub.