n8n-io/n8n · error

Filter operator "${operator}" on key "${key}" requires a non

Error message

Filter operator "${operator}" on key "${key}" requires a non-empty array value.

What it means

Thrown by buildConditionClause in the Postgres backend when an `in`/`nin` filter condition's value is not an array or is empty. The `in`/`nin` operators compile to per-candidate jsonb containment OR-clauses, which are meaningless for zero candidates, so the value is rejected before SQL is built. Operator and key are named in the message.

Source

Thrown at packages/@n8n/agents/src/vector-stores/postgres.ts:191

	private buildConditionClause(
		condition: FilterCondition,
		nextParam: (value: unknown) => string,
	): string {
		const { key, operator, value } = condition;

		switch (operator) {
			case 'eq': {
				const v = nextParam(JSON.stringify({ [key]: value }));
				return `metadata @> ${v}::jsonb`;
			}
			case 'ne': {
				const v = nextParam(JSON.stringify({ [key]: value }));
				return `NOT (metadata @> ${v}::jsonb)`;
			}
			case 'in':
			case 'nin': {
				if (!Array.isArray(value) || value.length === 0) {
					throw new Error(
						`Filter operator "${operator}" on key "${key}" requires a non-empty array value.`,
					);
				}
				// Containment per candidate (rather than `metadata->>key = ANY(text[])`) keeps
				// numeric and string metadata distinct — text extraction would otherwise make
				// the number 5 and the string "5" match the same filter value.
				const anyMatch = value
					.map(
						(candidate) => `metadata @> ${nextParam(JSON.stringify({ [key]: candidate }))}::jsonb`,
					)
					.join(' OR ');
				return operator === 'in' ? `(${anyMatch})` : `NOT (${anyMatch})`;
			}
			default:
				throw new Error(`Unsupported filter operator: "${String(operator)}"`);
		}
	}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Drop the `in`/`nin` condition from `conditions` when the array is empty.
  2. Build conditions defensively: `if (values.length) conditions.push({ key, operator: 'in', value: values })`.
  3. If all conditions drop out, omit the filter entirely so the query is unfiltered.

Example fix

// before
await store.search('q', {
  filter: { conditions: [{ key: 'status', operator: 'in', value: [] }] },
});

// after
const conditions = [];
if (statuses.length > 0) {
  conditions.push({ key: 'status', operator: 'in', value: statuses });
}
await store.search('q',
  conditions.length ? { filter: { conditions, combineWith: 'and' } } : undefined,
);
Defensive patterns

Strategy: validation

Validate before calling

function dropEmptyInConditions(filter: VectorFilter): VectorFilter | undefined {
  const conditions = filter.conditions.filter(
    (c) => !(c.operator === 'in' || c.operator === 'nin') || (Array.isArray(c.value) && c.value.length > 0),
  );
  return conditions.length > 0 ? { ...filter, conditions } : undefined;
}

const f = dropEmptyInConditions(inputFilter);
await store.search('q', f ? { filter: f } : undefined);

Type guard

function isInCondition(c: FilterCondition): c is FilterCondition & { operator: 'in' | 'nin'; value: (string | number)[] } {
  return (c.operator === 'in' || c.operator === 'nin') && Array.isArray(c.value) && c.value.length > 0;
}

Prevention

When it happens

Trigger: Searching with `{ filter: { conditions: [{ key: 'status', operator: 'in', value: [] }] } }`; passing a scalar to an `in` condition; a dynamic filter whose candidate list was emptied by upstream filtering.

Common situations: A UI multi-select where the user clears all options but the filter is still constructed; a default empty-array filter; set difference/remove operations leaving no candidates.

Related errors


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