MSSQL Plan Viewer is a Blazor web application that parses SQL Server Showplan XML and presents it as a Graphical plan / Table view / Diagnostics / Operator details / Plan details / Compare plans experience.
- Paste Showplan XML or load
.sqlplan/.xmlfiles via drag-and-drop or file picker - Retrieve estimated execution plans from SQL Server by entering a connection string and query
- Load multiple plans, switch between tabs, and use Compare plans
- Graphical plan
- Inline SVG rendering
- Operator icons
- Zoom, reset, and drag-to-scroll
- Dashed outline highlight for operators above a cost threshold
- Estimated-cost-based critical path
- SVG / PNG download
- Table view
- Hierarchical operator list
- CSV / Markdown / JSON download and CSV copy
- Diagnostics
- Cardinality estimate skew
- TempDB spills
- Expensive lookups
- High-impact missing indexes
- Implicit conversions
- Memory grant mismatch
- Stale statistics
- Large scans with residual predicates
- Parallel thread skew
- Operator details / Plan details
- Warning details
- Query Time Stats
- Query Plan
- MemoryGrantInfo
- OptimizerHardwareDependentProperties
- OptimizerStatsUsage
- MissingIndexes
- WaitStats
- Accessed objects
- Per-thread runtime counters
- Graphical plan node selection is synchronized with Table view, Diagnostics, and Operator details
- Export API
- JSON
POSTAPI for automation and tool integration - Accepts
showplanXmlin the request body and returns downloadable files POST /api/exports/table?format=csvPOST /api/exports/table?format=mdPOST /api/exports/table?format=jsonPOST /api/exports/graph?format=svgPOST /api/exports/graph?format=png
- JSON
- Estimated Showplan API
- Accepts a connection string and query
- Uses
SET SHOWPLAN_XML ON, so the target query is compiled but not executed - Can include parsed statement summaries and diagnostics with
includeAnalysis POST /api/showplans/estimated
- API Documentation
- Development-only OpenAPI JSON at
/openapi/v1.json - Swagger UI is available from the in-app
APItab and/api-docs
- Development-only OpenAPI JSON at
| Path | Description |
|---|---|
src\MSSQLPlanViewer.Web |
Blazor Web App (net10.0) |
src\MSSQLPlanViewer.Core |
Showplan parser, diagnostics, comparison, projection, and rendering (net10.0) |
tests\MSSQLPlanViewer.Core.Tests |
Parser, rendering, export, and API integration tests (net10.0) |
scripts\Test-PlanExportApi.ps1 |
PowerShell script for export API smoke testing |
Install the .NET 10 SDK, then run:
dotnet run --project .\src\MSSQLPlanViewer.Web\MSSQLPlanViewer.Web.csprojThe default launch profile starts the app at http://localhost:5293.
Build and run the web app container:
docker build -t mssql-plan-viewer .
docker run --rm -p 5293:5293 mssql-plan-viewerOpen http://localhost:5293 in your browser.
Open the repository in VS Code with the Dev Containers extension, then choose Reopen in Container.
Inside the container, restore and test the solution:
dotnet restore ./MSSQLPlanViewer.sln
dotnet test ./MSSQLPlanViewer.slnRun the app inside the container:
dotnet run --project ./src/MSSQLPlanViewer.Web/MSSQLPlanViewer.Web.csproj --urls http://0.0.0.0:5293The Dev Container forwards port 5293, so the app is available at http://localhost:5293.
To debug the app, select MSSQL Plan Viewer (Dev Container) in VS Code and press F5.
If bin or obj permission errors appear after changing Dev Container settings, run Dev Containers: Rebuild Container.
- Download
MSSQLPlanViewer-vX.Y.Z-win-x64.zipfrom the GitHub Releases page. - Extract the ZIP.
- Keep
wwwroot,appsettings.json, and the static web assets file in the same folder asMSSQLPlanViewer.Web.exe. - Start the app:
.\MSSQLPlanViewer.Web.exe --urls http://localhost:5293- Open
http://localhost:5293in your browser.
Pushing a v* tag builds a Windows x64 self-contained release ZIP and attaches it to a GitHub Release.
git tag v0.1.0
git push origin v0.1.0- Open
http://localhost:5293. - Paste SQL Server Showplan XML into the input box, drop/select one or more
.sqlplan/.xmlfiles, or switch to Query and enter a SQL Server connection string plus query. - Click Parse for XML input, or Get estimated plan for query input. Loaded plans appear as tabs.
- If a plan contains multiple statements, choose the active statement from the statement selector.
- Inspect the Graphical plan, Table view, Diagnostics, Plan details, and Operator details panes.
- Select an operator in the graph, table, or Diagnostics table to synchronize the focused node and details panel.
- Use Download CSV, Download Markdown, Download JSON, or Copy in Table view to export tabular data.
- Use Export SVG or Export PNG in Graphical plan to export the current graph.
- Load two or more plans to compare aggregate metrics with Compare plans.
The Query input mode connects to SQL Server and retrieves estimated Showplan XML with SET SHOWPLAN_XML ON. SQL Server compiles the query and returns the XML plan; the target query is not executed.
Requirements and behavior:
- The connection string is entered per request and is not saved by the app.
- The app does not log the connection string.
- The SQL Server login must have permission to compile the submitted query and
SHOWPLANpermission for referenced databases. - Use this feature only in a trusted internal deployment or behind appropriate network/application access controls.
- Submit T-SQL that SQL Server can execute as a single client command. Client-side batch separators such as
GOare not interpreted byMicrosoft.Data.SqlClient.
dotnet test .\MSSQLPlanViewer.slnSee docs\TEST_REPORT.md for the test report.
POST /api/exports/table?format=csvPOST /api/exports/table?format=mdPOST /api/exports/table?format=json
Request body:
{
"showplanXml": "<ShowPlanXML ...>...</ShowPlanXML>",
"statementId": "1"
}POST /api/exports/graph?format=svgPOST /api/exports/graph?format=png
Use layoutDirection=vertical or layoutDirection=horizontal as an optional query parameter for graph exports. If both the query parameter and request body specify layoutDirection, the query parameter wins.
Request body:
{
"showplanXml": "<ShowPlanXML ...>...</ShowPlanXML>",
"statementId": "1",
"costHighlightThresholdPercent": 20,
"showCriticalPath": true,
"layoutDirection": "horizontal"
}The response is returned as a downloadable file. If statementId is omitted, the first statement is used. The format query parameter is required. Invalid or unsupported format values return 400. Invalid XML returns 400, and a missing statement returns 404.
The Export API is intended for local automation and integration with tools that already have SQL Server Showplan XML.
| Item | Specification |
|---|---|
| Base URL | Same as the running web app, for example http://localhost:5293 |
| Method | POST |
| Authentication | None by default |
| Request content type | application/json |
| Antiforgery token | Not required for /api/exports/* |
| Response | A downloadable file returned in the response body |
Supported endpoints:
| Endpoint | Required query | Response content type | Output |
|---|---|---|---|
/api/exports/table |
format=csv |
text/csv |
CSV table export |
/api/exports/table |
format=md or format=markdown |
text/markdown |
Markdown table export |
/api/exports/table |
format=json |
application/json |
JSON table export |
/api/exports/graph |
format=svg |
image/svg+xml |
SVG graph export |
/api/exports/graph |
format=png |
image/png |
PNG graph export |
/api/exports/graph |
format=svg or format=png, optional layoutDirection=vertical or layoutDirection=horizontal |
Matching image type | Graph export with explicit layout direction |
Table export request body:
| Field | Type | Required | Description |
|---|---|---|---|
showplanXml |
string | Yes | SQL Server Showplan XML content. |
statementId |
string | No | Statement ID to export. If omitted, the first statement is used. |
Graph export request body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
showplanXml |
string | Yes | - | SQL Server Showplan XML content. |
statementId |
string | No | First statement | Statement ID to export. |
costHighlightThresholdPercent |
number | No | 20 |
Cost percentage threshold used for dashed outline highlighting. |
showCriticalPath |
boolean | No | true |
Whether to highlight the estimated-cost critical path. |
layoutDirection |
string | No | vertical |
Graph layout direction. Supported values: vertical, horizontal. |
Error responses use ASP.NET Core problem details JSON.
| Status | When |
|---|---|
400 |
format is missing or unsupported, showplanXml is empty, or the XML cannot be parsed. |
404 |
The Showplan XML contains no statements, or the requested statementId does not exist. |
POST /api/showplans/estimated
Request body:
{
"connectionString": "Server=.;Database=AdventureWorks2022;Integrated Security=true;TrustServerCertificate=true;",
"query": "SELECT TOP (10) * FROM Sales.SalesOrderHeader;",
"label": "Sales order lookup",
"commandTimeoutSeconds": 60,
"includeAnalysis": true
}Response body:
{
"plans": [
{
"label": "Sales order lookup",
"showplanXml": "<ShowPlanXML ...>...</ShowPlanXML>",
"statementCount": 1,
"schemaVersion": "SqlServer2022",
"totalNodeCount": 4,
"totalWarningCount": 0,
"analysis": {
"format": "json",
"contentType": "application/json",
"statements": [
{
"statementId": "1",
"statementType": "SELECT",
"statementText": "SELECT TOP (10) * FROM Sales.SalesOrderHeader;",
"estimatedSubtreeCost": 0.01,
"estimatedRows": 10,
"nodeCount": 4,
"edgeCount": 3,
"warningCount": 0,
"rootNodeIds": ["0"]
}
],
"diagnostics": [],
"diagnosticCount": 0,
"criticalDiagnosticCount": 0,
"warningDiagnosticCount": 0,
"infoDiagnosticCount": 0
}
}
]
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
connectionString |
string | Yes | - | SQL Server connection string used only for the current request. |
query |
string | Yes | - | Query used to retrieve the estimated execution plan. |
label |
string | No | Estimated |
Base label for returned plans. |
commandTimeoutSeconds |
number | No | 60 |
Command timeout from 1 to 300 seconds. |
includeAnalysis |
boolean | No | false |
When true, each returned plan includes parsed statement summaries and diagnostics in analysis. |
analysisFormat |
string | No | json |
Optional table export format when includeAnalysis is true. Supported values: json, md, markdown, csv. When supplied, plans[].analysis.content contains the same first-statement table export body as POST /api/exports/table for that format. |
| Status | When |
|---|---|
400 |
Required fields are missing, analysisFormat is unsupported, or commandTimeoutSeconds is outside 1 to 300. |
502 |
SQL Server connection, permission, compilation, or Showplan retrieval fails. |
504 |
SQL Server does not return before the command timeout. |
Start the app first:
dotnet run --project .\src\MSSQLPlanViewer.Web\MSSQLPlanViewer.Web.csprojRetrieve an estimated execution plan and save the first returned Showplan XML as a .sqlplan file:
$baseUrl = "http://localhost:5293"
$body = @{
connectionString = "Server=.;Database=AdventureWorks2022;Integrated Security=true;TrustServerCertificate=true;"
query = "SELECT TOP (10) * FROM Sales.SalesOrderHeader;"
label = "Sales order lookup"
commandTimeoutSeconds = 60
} | ConvertTo-Json -Depth 5
$response = Invoke-RestMethod `
-Uri "$baseUrl/api/showplans/estimated" `
-Method Post `
-ContentType "application/json" `
-Body $body
$response.plans |
Select-Object label, statementCount, schemaVersion, totalNodeCount, totalWarningCount
$response.plans[0].showplanXml |
Set-Content .\estimated-plan.sqlplan -Encoding utf8You can also save the request body and call the endpoint with curl.exe:
$body | Set-Content .\estimated-showplan-request.json -Encoding utf8
curl.exe -X POST "$baseUrl/api/showplans/estimated" `
-H "Content-Type: application/json" `
--data-binary "@estimated-showplan-request.json" `
-o estimated-showplan-response.jsonThe response JSON contains one or more plans entries. Each entry includes showplanXml, which can be pasted into the viewer, saved as .sqlplan, or sent to the Export API as showplanXml. If includeAnalysis is true, each entry also includes analysis with parsed statement summaries and diagnostics. Use analysisFormat to include json, md, markdown, or csv table export content in plans[].analysis.content; the HTTP response body stays JSON.
Set includeAnalysis to true to return parsed statement summaries and diagnostics together with the Showplan XML. Set analysisFormat to json, md, markdown, or csv to include the same table export content produced by POST /api/exports/table.
$baseUrl = "http://localhost:5293"
$estimatedPlanRequest = @{
connectionString = "Server=.;Database=AdventureWorks2022;Integrated Security=true;TrustServerCertificate=true;"
query = "SELECT TOP (10) * FROM Sales.SalesOrderHeader;"
label = "Sales order lookup"
commandTimeoutSeconds = 60
includeAnalysis = $true
analysisFormat = "md"
} | ConvertTo-Json -Depth 8
$estimatedPlanResponse = Invoke-RestMethod `
-Uri "$baseUrl/api/showplans/estimated" `
-Method Post `
-ContentType "application/json" `
-Body $estimatedPlanRequest
$plan = $estimatedPlanResponse.plans[0]
$plan.analysis.statements | Select-Object statementId, statementType, nodeCount, warningCount
$plan.analysis.diagnostics | Select-Object severity, ruleId, statementId, nodeId, message
$plan.analysis.content | Set-Content .\estimated-analysis.md -Encoding utf8
$plan.showplanXml | Set-Content .\estimated-plan.sqlplan -Encoding utf8The analysis object is omitted when includeAnalysis is omitted or false. When analysisFormat is omitted, analysis.content is omitted and the structured statements and diagnostics fields remain available. When analysisFormat is supplied, analysis.content contains the same first-statement table export body as POST /api/exports/table for json, md/markdown, or csv, and analysis.contentType identifies the media type. You can still send showplanXml to the Export API to create CSV, Markdown, JSON, SVG, or PNG artifacts.
Start the app first:
dotnet run --project .\src\MSSQLPlanViewer.Web\MSSQLPlanViewer.Web.csprojThe examples below use tests\MSSQLPlanViewer.Core.Tests\Samples\nested-loops-2022.sqlplan.
Export the table view as CSV:
$baseUrl = "http://localhost:5293"
$xml = Get-Content .\tests\MSSQLPlanViewer.Core.Tests\Samples\nested-loops-2022.sqlplan -Raw
$body = @{
showplanXml = $xml
} | ConvertTo-Json -Depth 5
Invoke-WebRequest `
-Uri "$baseUrl/api/exports/table?format=csv" `
-Method Post `
-ContentType "application/json" `
-Body $body `
-OutFile .\plan-table.csvExport the table view as Markdown for a specific statement:
$body = @{
showplanXml = $xml
statementId = "1"
} | ConvertTo-Json -Depth 5
Invoke-WebRequest `
-Uri "$baseUrl/api/exports/table?format=md" `
-Method Post `
-ContentType "application/json" `
-Body $body `
-OutFile .\plan-table.mdExport the table view as JSON:
Invoke-WebRequest `
-Uri "$baseUrl/api/exports/table?format=json" `
-Method Post `
-ContentType "application/json" `
-Body $body `
-OutFile .\plan-table.jsonExport the graphical plan as SVG:
$body = @{
showplanXml = $xml
costHighlightThresholdPercent = 20
showCriticalPath = $true
} | ConvertTo-Json -Depth 5
Invoke-WebRequest `
-Uri "$baseUrl/api/exports/graph?format=svg" `
-Method Post `
-ContentType "application/json" `
-Body $body `
-OutFile .\plan-graph.svgExport the graphical plan as PNG:
Invoke-WebRequest `
-Uri "$baseUrl/api/exports/graph?format=png" `
-Method Post `
-ContentType "application/json" `
-Body $body `
-OutFile .\plan-graph.pngYou can also save the JSON request body and call the API with curl.exe:
$body | Set-Content .\graph-request.json -Encoding utf8
curl.exe -X POST "http://localhost:5293/api/exports/graph?format=svg" `
-H "Content-Type: application/json" `
--data-binary "@graph-request.json" `
-o plan-graph.svgscripts\Test-PlanExportApi.ps1 calls all five endpoints and saves the returned files.
pwsh -File .\scripts\Test-PlanExportApi.ps1Or:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-PlanExportApi.ps1Main parameters:
-BaseUrl-ShowplanPath-ShowplanXml-OutputDirectory-StatementId-CostHighlightThresholdPercent-ShowCriticalPath
If -ShowplanXml is provided, the script uses that string directly. Otherwise it uses -ShowplanPath. If neither is provided, it falls back to tests\MSSQLPlanViewer.Core.Tests\Samples\nested-loops-2022.sqlplan.
ShowplanParserusesXDocumentandLocalNameso it can handle XML namespace differences.- Parser, diagnostics, graph, table, comparison, and export logic live in
MSSQLPlanViewer.Coreto keep UI dependencies minimal. - Blazor Server receive message size is increased to
10 MB. - Graph export uses a shared server-side SVG renderer, and PNG export rasterizes that SVG output.
This project is licensed under the MIT License. See LICENSE for details.
Third-party notices are listed in THIRD-PARTY-NOTICES.md.
