Paste KQL from Microsoft Sentinel, Defender XDR, Log Analytics or Azure Data Explorer, or start from one of the nine examples.
Every | step becomes a card with a coloured operator badge and a plain-English explanation. It updates while you type.
On the sample tables each step shows how many rows are left and how much it filtered, so you see where the data shrinks or grows.
The panel on the right shows the rows at that point. With render at the end you get a simple chart. Copy any result as CSV.
Yellow ⚡ faster tips point out slow patterns, orange ones possible mistakes. Then share the query with share link.
Simulated on the sample tables.
Scalar and aggregation.
Recognised and explained, not run.
KQL, the Kusto Query Language, is the query language of Microsoft Sentinel, Defender XDR advanced hunting, Azure Monitor Log Analytics, Application Insights and Azure Data Explorer. A KQL query is a pipeline: it starts with a table and passes the rows through a chain of operators separated by the pipe character |. Each step receives the output of the step before it, like an assembly line. Reading a query from top to bottom therefore tells you exactly what happens to the data, and that is what this visualizer shows.
| Operator | What it does | Example |
|---|---|---|
| where | Keeps rows that match a condition | where ResultType != "0" |
| project | Chooses and renames columns | project TimeGenerated, User = UserPrincipalName |
| extend | Adds a calculated column | extend Hour = hourofday(TimeGenerated) |
| summarize | Groups rows and aggregates | summarize count() by Computer |
| sort / top | Orders rows, top also limits | top 10 by count_ desc |
| join | Combines two tables on a key | join kind=inner (…) on Account |
| render | Draws a chart | render timechart |
Filter early and on time first: put where TimeGenerated > ago(1d) right after the table name, so the engine only scans the data partitions you need. Prefer has over contains: has looks up whole terms in the index, contains has to scan every string. Use == for exact matches and =~ only when case really varies. Project away columns you don't need before a join, and put the smaller table on the left. And use top N by instead of sort plus take.
A single = in a where clause is an error: comparisons use ==. sort by is descending by default, which surprises people coming from SQL. A join without kind= is innerunique, which silently removes duplicate keys from the left side. Grouping by a raw TimeGenerated creates one group per timestamp; wrap it in bin(TimeGenerated, 1h). And take returns arbitrary rows, so two runs can give different results.
Statements before the main query start with let and end with a semicolon. let threshold = 5; defines a value; let BadIPs = SigninLogs | where … | distinct IPAddress; defines a table you can use as a source, in a join or in where IPAddress in (BadIPs). The visualizer runs both kinds and shows them above the pipeline.
The five sample tables are generated in your browser with realistic columns from Microsoft Sentinel and Defender (SigninLogs, SecurityEvent, DeviceProcessEvents, Perf) and the classic StormEvents tutorial table. They contain a few planted incidents (a password spray, MFA fatigue, a brute force that succeeds and encoded PowerShell) so the security examples return something. The engine is a small JavaScript reimplementation written for learning: it covers the common operators and functions, not every edge case of Kusto, and some details such as integer division and default column names may differ. Always test production queries in your own workspace.