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 buildCondition in the Qdrant backend when an `in`/`nin` condition's value is not an array or is an empty array. Mirrors the same guard on Pinecone/Postgres/Supabase: an empty `in`/`nin` is meaningless. Operator and key are named in the message. This is the array-shape guard; element-type rules are enforced separately by error 116.

Source

Thrown at packages/@n8n/agents/src/vector-stores/qdrant.ts:170

function buildCondition(condition: FilterCondition): Schemas['Condition'] {
	const { key, operator, value } = condition;
	const payloadKey = `metadata.${key}`;

	switch (operator) {
		case 'eq':
		case 'ne': {
			if (typeof value === 'number' && !Number.isInteger(value)) {
				throw new Error(
					`Filter operator "${operator}" on key "${key}" does not support float values: Qdrant match only supports strings, integers, and booleans.`,
				);
			}
			const match: Schemas['Condition'] = { key: payloadKey, match: { value } };
			return operator === 'eq' ? match : { must_not: [match] };
		}
		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.`,
				);
			}
			const allStrings = value.every((v) => typeof v === 'string');
			const allIntegers = value.every((v) => typeof v === 'number' && Number.isInteger(v));
			if (!allStrings && !allIntegers) {
				throw new Error(
					`Filter operator "${operator}" on key "${key}" requires all array elements to be strings or all to be integers: Qdrant match does not support mixed-type or float values.`,
				);
			}
			// eslint-disable-next-line id-denylist -- `any` is Qdrant's match-schema field name
			const anyCondition: Schemas['Condition'] = { key: payloadKey, match: { any: value } };
			return operator === 'in' ? anyCondition : { must_not: [anyCondition] };
		}
		default:
			throw new Error(`Unsupported filter operator: "${String(operator)}"`);
	}
}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Omit the `in`/`nin` condition when the array is empty.
  2. Guard condition construction with a length check.
  3. Drop the whole filter if no conditions remain.

Example fix

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

// after
const conditions = [];
if (tags.length > 0) conditions.push({ key: 'tag', operator: 'in', value: tags });
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: `{ key: 'tag', operator: 'in', value: [] }`; passing a scalar to an `in` condition; a candidate list emptied by upstream logic.

Common situations: A multi-select UI cleared by the user but the filter still sent; default empty array; set operations removing all candidates.

Related errors


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