grafana/grafana · error · Error
Layout nesting too deep (max 10 levels)
Error message
Layout nesting too deep (max 10 levels)
What it means
Thrown by resolveLeafLayout() in movePanelsHelper.ts while descending a layout tree to find a leaf grid (DefaultGridLayoutManager or AutoGridLayoutManager) that can directly hold panels. The for-loop runs at most 10 iterations; if it never reaches a leaf grid after descending through RowsLayoutManager/TabsLayoutManager containers 10 times, it throws. This is a defensive depth/cycle guard used by movePanelsToLayout, which is invoked from REMOVE_ROW and REMOVE_TAB when moveContentTo is set. Note: normal dashboards cap group nesting at MAX_NESTING_DEPTH=4, so hitting 10 implies corrupted, hand-edited, or legacy-migrated data.
Source
Thrown at public/app/features/dashboard-scene/mutation-api/commands/movePanelsHelper.ts:72
throw new Error('Cannot move panels to an empty RowsLayout (no rows to receive panels)');
}
current = firstRow.state.layout;
continue;
}
if (current instanceof TabsLayoutManager) {
const firstTab = current.state.tabs[0];
if (!firstTab) {
throw new Error('Cannot move panels to an empty TabsLayout (no tabs to receive panels)');
}
current = firstTab.state.layout;
continue;
}
throw new Error(`Cannot move panels to layout type: ${current.constructor.name}`);
}
throw new Error('Layout nesting too deep (max 10 levels)');
}
/**
* Move panels from one layout to a target layout without changing panel IDs.
*
* Panels are cloned (so they can be detached from their current parent) and
* added directly to the target grid's children, preserving their existing
* keys and grid positions.
*
* @param panels - VizPanels to relocate
* @param targetLayout - The layout manager at the moveContentTo path
*/
export function movePanelsToLayout(panels: VizPanel[], targetLayout: DashboardLayoutManager): void {
if (panels.length === 0) {
return;
}
const leaf = resolveLeafLayout(targetLayout);View on GitHub (pinned to ae3104e369)
Solutions
- Call GET_LAYOUT first to inspect the actual structure at the moveContentTo path and confirm the depth is sane; if it exceeds 4 group layers the dashboard is already invalid and needs repair.
- Point moveContentTo at a shallower, valid group (e.g. the root "/") that resolves to a grid layout instead of a deeply nested container.
- Repair the corrupted layout tree before retrying: flatten the excess nesting through UPDATE_LAYOUT conversions or rebuild the dashboard from a known-good save model.
- Omit moveContentTo so the contained panels are deleted with the row/tab rather than relocated through the broken tree.
Example fix
// before: moveContentTo points into a corrupt deep structure
mutationApi.execute('REMOVE_ROW', { path: '/rows/0', moveContentTo: '/tabs/0/rows/0/tabs/0/rows/0/tabs/0/rows/0' });
// after: relocate to the root grid, which is a valid leaf
mutationApi.execute('REMOVE_ROW', { path: '/rows/0', moveContentTo: '/' }); Defensive patterns
Strategy: validation
Validate before calling
// Before REMOVE_ROW/REMOVE_TAB with moveContentTo, fetch layout and sanity-check depth
const layoutRes = await mutationApi.execute('GET_LAYOUT', {});
function maxDepth(node, d = 0) {
if (!node.children) return d;
return Math.max(...node.children.map((c) => maxDepth(c, d + 1)), d);
}
const target = findNodeByPath(layoutRes.data.layout, moveContentTo);
if (maxDepth(target) > 4) {
throw new Error('Target layout is too deeply nested; choose a shallower path.');
} Type guard
null
Try / catch
const res = await mutationApi.execute('REMOVE_ROW', { path, moveContentTo });
if (!res.success && res.error.includes('nesting too deep')) {
// fall back to omitting moveContentTo (delete content) or use root
await mutationApi.execute('REMOVE_ROW', { path });
} Prevention
- Always call GET_LAYOUT before operations that use moveContentTo to verify the target structure.
- Prefer shallow moveContentTo paths (root or top-level groups) to avoid deep traversal.
- Repair dashboards whose group nesting exceeds MAX_NESTING_DEPTH=4 before attempting mutations.
When it happens
Trigger: Calling REMOVE_ROW or REMOVE_TAB with a moveContentTo path whose resolved layout manager sits atop more than 10 nested RowsLayoutManager/TabsLayoutManager layers without any DefaultGridLayoutManager/AutoGridLayoutManager in between. Also reachable via MOVE_PANEL through the same helper on a deeply malformed target.
Common situations: A dashboard JSON hand-edited or produced by an older migration path that bypassed the MAX_NESTING_DEPTH=4 nesting validation; a corrupted scene graph where a cycle of rows/tabs references each other; imported dashboards from external tools that emit pathological nesting.
Related errors
- Cannot add tabs at "${parentPath}": tabs cannot be nested di
- Cannot add ${addingType} at "${parentPath}": maximum nesting
- Cannot convert to TabsLayout at "${path}": tabs cannot be ne
- Cannot convert to TabsLayout: a child's inner layout is alre
- Annotation '${name}' already exists
AI-assisted analysis of grafana/grafana@ae3104e369 (2026-08-12).
Data as JSON: /api/errors/6eab8a599245c74d.
Report an issue: GitHub.