n8n-io/n8n · error · ExpressionExtensionError

argument of keepFieldsContaining must be a non-empty string

Error message

argument of keepFieldsContaining must be a non-empty string

What it means

Twin of 333: the object extension `keepFieldsContaining(match)` retains only fields whose string value contains `match`, deleting the rest. The same guard rejects a non-string or empty `match` — an empty match would (correctly but uselessly) keep everything, and a non-string match cannot substring-test.

Source

Thrown at packages/@n8n/expression-runtime/src/extensions/object-extensions.ts:54

function removeFieldsContaining(value: object, extraArgs: string[]): object {
	const [match] = extraArgs;
	if (typeof match !== 'string' || match === '') {
		throw new ExpressionExtensionError('removeFieldsContaining(): expected non-empty string arg');
	}
	const newObject = { ...value };
	for (const [key, val] of Object.entries(value)) {
		if (typeof val === 'string' && val.includes(match)) {
			// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
			delete (newObject as any)[key];
		}
	}
	return newObject;
}

function keepFieldsContaining(value: object, extraArgs: string[]): object {
	const [match] = extraArgs;
	if (typeof match !== 'string' || match === '') {
		throw new ExpressionExtensionError(
			'argument of keepFieldsContaining must be a non-empty string',
		);
	}
	const newObject = { ...value };
	for (const [key, val] of Object.entries(value)) {
		if (typeof val !== 'string' || (typeof val === 'string' && !val.includes(match))) {
			// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
			delete (newObject as any)[key];
		}
	}
	return newObject;
}

export function compact(value: object): object {
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	const newObj: any = {};
	for (const [key, val] of Object.entries(value)) {
		if (val !== null && val !== undefined && val !== 'nil' && val !== '') {

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Pass a non-empty string match: `.keepFieldsContaining('email')`.
  2. Pre-validate the match value in a Code node.
  3. Coerce numerics with `.toString()` if a numeric substring match is intended.

Example fix

// before
{{ $json.row.keepFieldsContaining($json.q) }}
// after
{{ $json.row.keepFieldsContaining(String($json.q || '')) || $json.row }}
Defensive patterns

Strategy: validation

Validate before calling

const match = $json.match;
if (typeof match !== 'string' || match === '') {
  throw new Error('keepFieldsContaining(): match must be a non-empty string');
}
return $json;

Type guard

const isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.length > 0;

Prevention

When it happens

Trigger: `.keepFieldsContaining('')` or `.keepFieldsContaining(null)` or `.keepFieldsContaining(42)`.

Common situations: Dynamic match value that is empty or the wrong type; field rename leaving `match` undefined; numeric ID used as a substring match.

Related errors


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