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

  1. 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.
  2. Point moveContentTo at a shallower, valid group (e.g. the root "/") that resolves to a grid layout instead of a deeply nested container.
  3. 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.
  4. 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

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


AI-assisted analysis of grafana/grafana@ae3104e369 (2026-08-12). Data as JSON: /api/errors/6eab8a599245c74d. Report an issue: GitHub.