nocodb/nocodb · error · FormulaError
CIRCULAR_REFERENCE
CIRCULAR_REFERENCE
Error message
Circular reference detected
What it means
Thrown by the dependency cycle detector (validate-extract-tree.ts:741) after a Kahn topological sort over formula-column references. It builds an adjacency list and in-degree map from formula dependency paths, BFS-visits zero-in-degree nodes, and if the visited count is less than the vertex count it concludes a cycle exists among formula columns. This is a graph-level check, independent of any single formula's syntax.
Source
Thrown at packages/nocodb-sdk/src/lib/formula/validate-extract-tree.ts:741
// if this node has neighbours, increase visited by 1
const neighbours = adj.get(src) || new Set();
if (neighbours.size > 0) {
visited += 1;
}
// iterate each neighbouring nodes
neighbours.forEach((neighbour: string) => {
// decrease in-degree of its neighbours by 1
inDegrees.set(neighbour, inDegrees.get(neighbour) - 1);
// if in-degree becomes 0
if (inDegrees.get(neighbour) === 0) {
// then put the neighboring node to the queue
queue.push(neighbour);
}
});
}
// vertices not same as visited = cycle found
if (vertices !== visited) {
throw new FormulaError(
FormulaErrorType.CIRCULAR_REFERENCE,
{
key: 'msg.formula.cantSaveCircularReference',
},
'Circular reference detected'
);
}
}
}
export async function validateFormulaAndExtractTreeWithType({
formula,
column,
columns,
clientOrSqlUi,
getMeta,
trackPosition,
}: {View on GitHub (pinned to d3caaf4e89)
Solutions
- Break the cycle by replacing at least one formula column with a plain column or a value that does not reference back.
- Trace the dependency graph (the collected formulaPaths) to find the smallest cycle and remove one edge.
- Re-model the computed value as a single self-contained formula with no cross-formula references.
Example fix
// before (cycle)
// ColA = {ColB} + 1
// ColB = {ColA} + 1
// after
// ColA = {Base} + 1
// ColB = {ColA} + 1 Defensive patterns
Strategy: validation
Validate before calling
// Detect a cycle among formula column references BEFORE calling the API.
// refs: map of columnId -> columnIds it references in its formula
function hasFormulaCycle(refs: Record<string, string[]>): boolean {
const inDeg: Record<string, number> = {};
for (const [src, deps] of Object.entries(refs)) {
inDeg[src] = inDeg[src] || 0;
for (const d of deps) inDeg[d] = (inDeg[d] || 0) + 1;
}
const q = Object.keys(inDeg).filter((k) => inDeg[k] === 0);
let visited = 0;
while (q.length) {
const n = q.shift()!;
visited++;
for (const d of refs[n] || []) if (--inDeg[d] === 0) q.push(d);
}
return visited !== Object.keys(inDeg).length;
} Type guard
function isCircularReferenceError(e: unknown): e is FormulaError {
return e instanceof FormulaError && e.type === FormulaErrorType.CIRCULAR_REFERENCE;
} Try / catch
try {
await validateFormulaAndExtractTreeWithType({ formula, columns, clientOrSqlUi, getMeta });
} catch (e) {
if (e instanceof FormulaError && e.type === FormulaErrorType.CIRCULAR_REFERENCE) {
// tell user the formula columns form a cycle; break one reference
}
throw e;
} Prevention
- Maintain a dependency graph of formula columns and reject edits that create a cycle before saving.
- When renaming/swapping columns, re-check the graph for cycles.
- Avoid formulas that reference other formulas which can transitively point back.
When it happens
Trigger: Two or more formula columns that reference each other directly (ColA = {ColB} + 1, ColB = {ColA} + 1) or transitively (A→B→C→A).
Common situations: Editing a formula column to reference another formula that eventually points back; bulk-importing columns that form a cycle; renaming/swapping columns so a previously-acyclic graph becomes cyclic.
Related errors
- Paste operation is not supported on the active cell
- TYPE_MISMATCH
- INVALID_ARG
- INVALID_SYNTAX
- INVALID_FUNCTION_NAME
AI-assisted analysis of nocodb/nocodb@d3caaf4e89 (2026-08-12).
Data as JSON: /api/errors/975820129edb79bb.
Report an issue: GitHub.