Share via

Dashboard Widget Collision Issue During API-Based Migration in Azure DevOps / TFS

Priyanka Patil 0 Reputation points
2026-02-11T06:47:00.68+00:00

We are facing a critical issue while migrating dashboards (including widgets) from one project to another using Azure DevOps / TFS REST APIs.

We are using the following REST APIs:

  • Dashboard Create API
  • Widget Create API

During widget migration, we observed inconsistent behavior between the REST API response and the actual dashboard layout rendered in the UI.

  1. When creating a widget via API, even if we provide a specific row value (e.g., row = 5), Azure DevOps/TFS places the widget in the first available empty row (typically row 1).
  2. However, the API response returns the row value that was originally passed in the request (e.g., row = 5), not the actual placement row used internally.
  3. When the next widget is created in the same column with row = 6 (based on source system layout), we receive the following error:

VS402428: Failed to add widget. Refresh your dashboard and try again.WidgetCollisionException

  1. Manually refreshing the dashboard from the UI resolves the issue temporarily. However:
  • There is no REST API available to refresh the dashboard.
  • We cannot use bulk widget replacement (PUT Replace Widgets API) due to architectural constraints.
  • Widgets must be created one by one in sequence.

-> Impact

  • Automated widget migration is failing.
  • Manual UI refresh is required to proceed, which breaks automation.
  • API response state and actual UI layout state are inconsistent.
  • This is blocking large-scale project-to-project dashboard migrations.

-> Questions

  1. Is there any REST API available to programmatically refresh or revalidate dashboard layout state?
  2. Why does the Widget Create API return the requested row instead of the actual persisted row?
  3. Is this behavior expected by design?
  4. What is the recommended approach for sequential widget creation without encountering WidgetCollisionException?

We require urgent clarification and guidance, as this issue is blocking our migration activities.

Please let us know if logs, request payloads are required from our side.

Thank you!

Azure DevOps
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Rakesh Mishra 6,410 Reputation points Microsoft External Staff Moderator
    2026-03-04T16:22:12.3766667+00:00

    Hi @Priyanka Patil , I have received an update from internal team. Please find the response below.

    Widget Position via API

    • When you create a widget through the REST API, the row and column values in your request are saved directly in the database.
    • The API does not perform any layout adjustment or auto-compaction (“gravity”) of widgets.
    • This is why, immediately after creation, GET Dashboard returns the same row/column values that were submitted and saved in DB.
    • e.g.: I created two widgets using the API (Widget A and Widget B) with Row 100, Column 1 and Row 200, Column 1. Since the rows are different for each widget, they were created without any exceptions and saved in the database as entered.
    • The gravity / auto-compact / reflow logic is executed exclusively in the UI rendering layer.
    • When you refresh the dashboard in the UI, the layout engine recalculates the widget positions to eliminate gaps and compact the layout. It then calls the internal API to save these updated positions to the database.

    As a result, the widget appears in its new position in the UI, and if you call the GetDashboard API after this process, it will also return the updated position values.

    Collision Exceptions

    Because the API does not trigger gravity, if you attempt to create another 2 widgets at the same position (row 200, column 1), the API detects a collision and throws WidgetCollisionException.

    This is expected and by design — the API cannot resolve layout conflicts automatically; only the UI reflow logic handles that.

    Implication for Programmatic Widget Creation

    Sequential widget creation via the API must use unique row/column positions to avoid collisions. There is no API endpoint to trigger gravity/reflow programmatically.

    Please let me know if you need help with sequential widget creation via API.

    0 comments No comments

  2. Rakesh Mishra 6,410 Reputation points Microsoft External Staff Moderator
    2026-02-11T07:57:17.62+00:00

    Hi @Priyanka Patil ,

    Welcome to the Microsoft Q&A Platform! Thank you for asking your question here.

    Please find the response below to your question and let me know in comments if you have any further questions.

    1. Is there any REST API available to programmatically refresh or revalidate dashboard layout state? Yes. While there is no explicit "Refresh" verb, the Get Dashboard (or Get Widgets) API serves this exact purpose. When you call this API, it retrieves the current, actual state of the dashboard from the server, including the resolved positions of all widgets after the layout engine (auto-fill/gravity) has processed them.
      • API Call:
             GET https://dev.azure.com/{organization}/{project}/{team}/_apis/dashboard/dashboards/{dashboardId}?api-version=7.1-preview.3
        
      • Usage in your script: After creating each widget, you must call this API to get the updated eTag and the actual positions of the placed widgets. This replaces the need for a manual UI refresh.
    2. Why does the Widget Create API return the requested row instead of the actual persisted row? The Create Widget API response typically echoes the intent of your request (the properties you submitted) rather than the final calculated layout.
      • Reason: The Azure DevOps Dashboard uses a "grid system" with auto-fill (gravity) rules. When you place a widget at Row=5, the API accepts this valid coordinate. However, the dashboard's layout engine subsequently (or asynchronously) detects the empty space above (Rows 1-4) and "slides" the widget up to Row=1 to compact the view.
      • The Discrepancy: The API response returns the widget as it was created (at Row 5), but the internal layout engine immediately invalidates that position and moves it. Your automation script receives "Row 5" and assumes that space is occupied, or calculates the next position based on that false premise.
    3. Is this behavior expected by design? Yes. This is the standard behavior of the Azure DevOps Dashboard grid.
      • Auto-Layout: Dashboards are designed to prevent "holes." Widgets automatically float up to the highest available slot in their column.
      • Collision Exception: The VS402428: WidgetCollisionException occurs because your script is likely trying to place the next widget in a position that conflicts with the server's view of the dashboard state.
        • Scenario: If you create Widget A at Row=5(Response says 5, Server moves it to 1). Then you try to create Widget B at Row=6.
        • If the server has not fully committed the "move to 1" or if the eTag (version) of the dashboard has changed due to the move, the next write operation might fail if it doesn't reference the updated state.
        • More commonly, collisions happen because the script assumes a layout (e.g., "A is at 5, so 5 is taken") that differs from reality ("A is at 1, so 5 is empty"), potentially leading to logic errors where you try to place a widget on top of another's actual location or trigger a version conflict.
    4. What is the recommended approach for sequential widget creation? Since you cannot use the Bulk API, you must update your loop to re-sync the state after every creation. Recommended Algorithm:
      1. Define Target Layout: Have your list of widgets to migrate.
      2. Create Widget A: Send POST with your desired position (e.g., Row 5).
      3. Immediately Fetch State: Call GET Dashboard.
        • This confirms where Widget A actually landed (e.g., Row 1).
        • It updates your client-side knowledge of the Dashboard's eTag (if you are using it for concurrency control).
      4. Calculate Next Position: Determine the position for Widget B.
        • Note: Since the dashboard auto-compacts, you might not need to force "Row 6" if you just want them in order. You could theoretically place the next widget at Row=100 (or just below the last known widget) and let gravity pull it up.
        • If you need exact positioning (with gaps), you would need to fill the empty rows with "dummy" widgets, but this is complex.
      5. Create Widget B: Send POST with the new position.

    Simple Fix: If you just want them to appear in the same relative order (A above B), you don't need to preserve the exact "Row 5" vs "Row 6" coordinates from the source if Rows 1-4 are empty.

    • Just place Widget A at the first available slot (or Row: 0).
      • Then place Widget B at Row: 0 (or the known bottom).
      • Key: Ignore the exact row number from the source system if the destination dashboard is starting empty. Just sort your widgets by their original row and create them sequentially. The Dashboard's gravity will naturally stack them A -> Row 1, B -> Row 2, etc.

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.