{"record":{"id":"f897a77df637c9f7","repo":"n8n-io/n8n","slug":"cannot-query-across-relation-relationtype-for-p","errorCode":null,"errorMessage":"Cannot query across ${relation.relationType} for property ${path}","messagePattern":"Cannot query across (.+?) for property (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/typeorm/src/query-builder/QueryBuilder.ts","lineNumber":1229,"sourceCode":"\t\t\t\t// so if the join columns are all defined we can return just the relation itself\n\t\t\t\t// because it will fetch only the join columns and do the lookup.\n\t\t\t\tif (relation.relationType === 'one-to-one' || relation.relationType === 'many-to-one') {\n\t\t\t\t\tconst joinColumns = relation.joinColumns\n\t\t\t\t\t\t.map((j) => j.referencedColumn)\n\t\t\t\t\t\t.filter((j): j is ColumnMetadata => !!j);\n\n\t\t\t\t\tconst hasAllJoinColumns =\n\t\t\t\t\t\tjoinColumns.length > 0 &&\n\t\t\t\t\t\tjoinColumns.every((column) => column.getEntityValue(entity[key], false));\n\n\t\t\t\t\tif (hasAllJoinColumns) {\n\t\t\t\t\t\tpaths.push(path);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (relation.relationType === 'one-to-many' || relation.relationType === 'many-to-many') {\n\t\t\t\t\tthrow new Error(`Cannot query across ${relation.relationType} for property ${path}`);\n\t\t\t\t}\n\n\t\t\t\t// For any other case, if the `entity[key]` contains all of the primary keys we can do a\n\t\t\t\t// lookup via these.  We don't need to look up via any other values 'cause these are\n\t\t\t\t// the unique primary keys.\n\t\t\t\t// This handles the situation where someone passes the model & we don't need to make\n\t\t\t\t// a HUGE where.\n\t\t\t\tconst primaryColumns = relation.inverseEntityMetadata.primaryColumns;\n\t\t\t\tconst hasAllPrimaryKeys =\n\t\t\t\t\tprimaryColumns.length > 0 &&\n\t\t\t\t\tprimaryColumns.every((column) => column.getEntityValue(entity[key], false));\n\n\t\t\t\tif (hasAllPrimaryKeys) {\n\t\t\t\t\tconst subPaths = primaryColumns.map((column) => `${path}.${column.propertyPath}`);\n\t\t\t\t\tpaths.push(...subPaths);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n","sourceCodeStart":1211,"sourceCodeEnd":1247,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/typeorm/src/query-builder/QueryBuilder.ts#L1211-L1247","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait repo.find({ where: { comments: post.comments } }); // comments is one-to-many\n// after\nawait repo.find({ where: { id: In(post.comments.map(c => c.postId)) } });","handlingStrategy":"validation","validationCode":"function stripToMany(entity: Record<string, unknown>, meta: any) {\n  const out: Record<string, unknown> = {};\n  for (const [k, v] of Object.entries(entity)) {\n    const rel = meta.relations.find((r: any) => r.propertyName === k);\n    const isCollection = rel && (rel.relationType === 'one-to-many' || rel.relationType === 'many-to-many');\n    if (!isCollection) out[k] = v;\n  }\n  return out;\n}\n// qb.where(meta.targetName, stripToMany(user, meta))","typeGuard":"function isToManyCollection(value: unknown, meta: any, key: string): boolean {\n  const rel = meta.relations.find((r: any) => r.propertyName === key);\n  return !!rel && (rel.relationType === 'one-to-many' || rel.relationType === 'many-to-many') && Array.isArray(value);\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["typeorm","query-builder","relations","where-clause"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}