n8n-io/n8n · error · Error
Cannot query across ${relation.relationType} for property ${
Error message
Cannot query across ${relation.relationType} for property ${path} What it means
Thrown in QueryBuilder.createPropertyPath when, while expanding an entity into WHERE conditions, the code encounters a relation of type one-to-many or many-to-many on entity[key]. TypeORM cannot turn a to-many collection into a scalar WHERE predicate without an explicit subquery, so it aborts rather than emit a wrong query.
Source
Thrown at packages/@n8n/typeorm/src/query-builder/QueryBuilder.ts:1229
// so if the join columns are all defined we can return just the relation itself
// because it will fetch only the join columns and do the lookup.
if (relation.relationType === 'one-to-one' || relation.relationType === 'many-to-one') {
const joinColumns = relation.joinColumns
.map((j) => j.referencedColumn)
.filter((j): j is ColumnMetadata => !!j);
const hasAllJoinColumns =
joinColumns.length > 0 &&
joinColumns.every((column) => column.getEntityValue(entity[key], false));
if (hasAllJoinColumns) {
paths.push(path);
continue;
}
}
if (relation.relationType === 'one-to-many' || relation.relationType === 'many-to-many') {
throw new Error(`Cannot query across ${relation.relationType} for property ${path}`);
}
// For any other case, if the `entity[key]` contains all of the primary keys we can do a
// lookup via these. We don't need to look up via any other values 'cause these are
// the unique primary keys.
// This handles the situation where someone passes the model & we don't need to make
// a HUGE where.
const primaryColumns = relation.inverseEntityMetadata.primaryColumns;
const hasAllPrimaryKeys =
primaryColumns.length > 0 &&
primaryColumns.every((column) => column.getEntityValue(entity[key], false));
if (hasAllPrimaryKeys) {
const subPaths = primaryColumns.map((column) => `${path}.${column.propertyPath}`);
paths.push(...subPaths);
continue;
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Do not pass to-many collections in where conditions. Filter by the owning side's FK or by an inverse id: where: { id: In(comments.map(c => c.userId)) }.
- Use a subquery: qb.where(qb => qb.subQuery().select().from(Comment,'c').where('c.userId = user.id').getQuery()).
- Strip relation arrays from the entity before using it as a where object, or pass only the primary-key map.
Example fix
// before
await repo.find({ where: { comments: post.comments } }); // comments is one-to-many
// after
await repo.find({ where: { id: In(post.comments.map(c => c.postId)) } }); Defensive patterns
Strategy: validation
Validate before calling
function stripToMany(entity: Record<string, unknown>, meta: any) {
const out: Record<string, unknown> = {};
for (const [k, v] of Object.entries(entity)) {
const rel = meta.relations.find((r: any) => r.propertyName === k);
const isCollection = rel && (rel.relationType === 'one-to-many' || rel.relationType === 'many-to-many');
if (!isCollection) out[k] = v;
}
return out;
}
// qb.where(meta.targetName, stripToMany(user, meta)) Type guard
function isToManyCollection(value: unknown, meta: any, key: string): boolean {
const rel = meta.relations.find((r: any) => r.propertyName === key);
return !!rel && (rel.relationType === 'one-to-many' || rel.relationType === 'many-to-many') && Array.isArray(value);
} Prevention
- Do not pass hydrated-with-relations entities as where conditions; pass only primary-key maps.
- Audit FindOptionsWhere usages that came from request bodies.
- Use In(...) with extracted ids instead of nested relation arrays.
When it happens
Trigger: Passing an entity instance into qb.where(Entity, { id }) (the object form) where the object has a populated to-many relation array, e.g. User.role entities passed as User with user.roles = [...]. Also when using repository.find({ where: { comments: [...] } }) with a one-to-many side.
Common situations: Developer loads an aggregate with relations via find({relations:true}) then reuses the hydrated object as a FindOptionsWhere; serializing a request body that nests a to-many array and feeding it straight into a where clause.
Related errors
- Relation "${notFoundRelations[0]}" was not found; please che
- Property "${propertyPath}" was not found in "${metadata.targ
- OnUpdateType "${relation.onUpdate}" is not valid for ${drive
- Relation ${entityMetadata.name}#${relation.propertyName} and
- Circular relations detected: ${path}. To resolve this issue
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/f897a77df637c9f7.
Report an issue: GitHub.