n8n-io/n8n · error · TypeORMError
Nested CTEs aren't supported (CTE: ${cte.alias})
Error message
Nested CTEs aren't supported (CTE: ${cte.alias}) What it means
createCteExpression() throws when a CTE's queryBuilder itself has common table expressions (cte.queryBuilder.hasCommonTableExpressions()). SQL does not allow a WITH clause inside the body of another WITH clause entry; each CTE must be a flat sibling. TypeORM enforces this at query build time rather than emitting invalid SQL.
Source
Thrown at packages/@n8n/typeorm/src/query-builder/QueryBuilder.ts:1013
return '(' + condition.parameters.join(' OR ') + ')';
}
throw new TypeError(`Unsupported FindOperator ${FindOperator.constructor.name}`);
}
protected createCteExpression(): string {
if (!this.hasCommonTableExpressions()) {
return '';
}
const databaseRequireRecusiveHint =
this.connection.driver.cteCapabilities.requiresRecursiveHint;
const cteStrings = this.expressionMap.commonTableExpressions.map((cte) => {
const cteBodyExpression =
typeof cte.queryBuilder === 'string' ? cte.queryBuilder : cte.queryBuilder.getQuery();
if (typeof cte.queryBuilder !== 'string') {
if (cte.queryBuilder.hasCommonTableExpressions()) {
throw new TypeORMError(`Nested CTEs aren't supported (CTE: ${cte.alias})`);
}
if (
!this.connection.driver.cteCapabilities.writable &&
!InstanceChecker.isSelectQueryBuilder(cte.queryBuilder)
) {
throw new TypeORMError(
`Only select queries are supported in CTEs in ${this.connection.options.type} (CTE: ${cte.alias})`,
);
}
this.setParameters(cte.queryBuilder.getParameters());
}
let cteHeader = this.escape(cte.alias);
if (cte.options.columnNames) {
const escapedColumnNames = cte.options.columnNames.map((column) => this.escape(column));
if (InstanceChecker.isSelectQueryBuilder(cte.queryBuilder)) {
if (
cte.queryBuilder.expressionMap.selects.length &&
cte.options.columnNames.length !== cte.queryBuilder.expressionMap.selects.lengthView on GitHub (pinned to 5ac6606e81)
Solutions
- Flatten all CTEs into a single WITH list on the outermost QueryBuilder; do not chain CTE-bearing builders as CTE bodies.
- If a CTE body needs another CTE, hoist that inner CTE to the outer builder's addCommonTableExpression list.
- Use raw SQL strings for CTE bodies if composition is complex, ensuring no nested WITH.
- Refactor shared subqueries into separate top-level CTEs referenced by name.
Example fix
// before const inner = dataSource.createQueryBuilder().select(...) .addCommonTableExpression(deeperQb, 'deep'); const outer = dataSource.createQueryBuilder().select(...) .addCommonTableExpression(inner, 'inner'); // throws: nested CTE // after — flatten const outer = dataSource.createQueryBuilder().select(...) .addCommonTableExpression(deeperQb, 'deep') .addCommonTableExpression(innerFlatQb, 'inner');
Defensive patterns
Strategy: validation
Validate before calling
function isFlatCteBody(qb: { hasCommonTableExpressions: () => boolean }): boolean {
return !qb.hasCommonTableExpressions();
}
if (!isFlatCteBody(bodyQb)) throw new Error('CTE body must not itself contain CTEs; flatten them to the outer builder'); Type guard
function isFlatCteBody(qb: { hasCommonTableExpressions: () => boolean }): qb is { hasCommonTableExpressions: () => false } {
return !qb.hasCommonTableExpressions();
} Prevention
- Flatten all CTEs onto the outermost QueryBuilder.
- Hoist inner CTEs from reusable subquery builders before composing.
- Use raw SQL strings for CTE bodies if composition is complex.
- Add a helper that collects CTEs from all composed builders into one flat list.
When it happens
Trigger: Calling .addCommonTableExpression(qbA, 'a') where qbA was itself built with .addCommonTableExpression(qbB, 'b'). The inner CTE nests inside the outer, which the database would reject.
Common situations: Composing reusable QueryBuilders that each add their own CTEs and then embedding one inside another's CTE list. Refactoring a flat CTE list into a nested builder by mistake.
Related errors
- Only select queries are supported in CTEs in ${this.connecti
- cte.options.columnNames length (${cte.options.columnNames.le
- The ‘prompt’ parameter is empty.
- No binary data found, please connect a binary to the input i
- No data source found, please configure data source
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/592ce051b3bf9259.
Report an issue: GitHub.