v1.0.0 Start Views Reference FAQ Support
IntelliJ IDEA plugin · 2024.1 and later

Elasticsearch DSL Debugger

Find the clause that took your results away. Paste a query, watch it narrow clause by clause, with a document count after every step.

IntelliJ IDEA 2024.1+ Elasticsearch 8 and 9 Read-only by construction No telemetry
Screenshot placeholderscreenshots/tool-window.pngThe whole tool window at the Match stage: sidebar, query, stage strip, graph, inspector
One query, mid-run at stage 4 of 10. Every panel is answering a different question at once: the index fields down the left, the query above, the ten stages across the middle, the clause graph below them carrying a count and a removal on every clause, and the Inspector on the right explaining whichever clause is under the cursor.

Start here

What it is, what it is not, and the first three minutes.

What it answersWhy a query returned what it did, and which clause is responsible.
Which languageThe Query DSL — the JSON bodies you send to _search. Not ES|QL, EQL, SQL or KQL.
What it is notAn editor. There is no completion in the query box, on purpose. Paste a query you already have.

Three minutes

  1. Open the ES Debugger tool window, on the bottom strip.
  2. Choose a source: a bundled dataset, or one of your saved clusters and an index.
  3. Paste a search body, or pick one of the 68 examples in the sidebar.
  4. Press Debug query. The run stops at stage one.
  5. Press Run all stages, then read the badges on the clauses.

Debug query, not Run. Pressing it sends _validate/query and stops at stage one. The first _search comes at the Match stage. Every stage shows exactly what it sent in Inspector › Request.

Results describe the run that produced them, not the text in the editor. Edit the query and the guidance line says so: The query has changed since this run. Press Debug query again — the stages below still describe the old one. Nothing recalculates on its own, deliberately: a debugger that re-ran on every keystroke would hit your cluster on every keystroke.

The same applies to switching clauses off. They change which clauses go into the next request, and the numbers move when you run again, not when you click the menu item.

The tool window

One line at the top always says what to do next, with the button that does it beside it. The rest is four areas, and any of them can be hidden: Focus the stages hides all but the stages, and Reset layout puts everything back.

AreaWhat is in it
SidebarFour tabs, each titled with how much it holds: Examples, Mapping, Outline and History. See the sidebar.
QueryThe source dropdown and the index box, a readiness line under them saying what comes next, and the editor with Debug query, Format and Clear. Against a cluster, Check cluster and Refresh fields sit on the same row.
ViewsA row of five segments above the results: Debug execution, Diagnose, Performance, Why not? and Text analysis. The query, the source and the sidebar stay put across them. A view that would open onto its empty state dims instead: Performance without a cluster.
Debug executionThe stage controls and the ten stage chips, the clause graph under them with a legend at its foot, and the Inspector beside the graph with eight tabs about the run, Compare among them.
Debug executionDiagnosePerformanceWhy not?Text analysis

The readiness line under the connection row is where the order of things is written. With a cluster selected it reads Step 1 of 2 — name the index this query runs against, then Step 2 of 2 — press Refresh fields to load the mapping of products, then Ready — products, 57 fields loaded. With a bundled dataset it reads Bundled dataset — nothing to load, and the index box shows that dataset’s index and is not editable.

Do thisOr else
Check clusterReports the cluster and its version, then walks every API the debugger depends on and says which answered. A version below 8 is flagged: this plugin models Elasticsearch 8 and 9. OpenSearch is named as OpenSearch, with a note that the version-aware rules assume Elasticsearch version numbers.
Name an indexThe index box takes an index, an alias, a wildcard like logs-* or a comma-separated list. With a cluster selected an empty box disables Debug query and says why. Names Elasticsearch would reject, such as capitals, spaces or a leading -, are caught before the request.
Refresh fieldsReads the mapping and the index settings. The field lists come from it, most diagnostics need it, and the analyzer resolution in Text analysis reads it.

Writing SQL? POST /_sql/translate returns the DSL a SQL statement would run, without running it. Paste that body here and everything below applies.

Connecting a cluster

Settings › Tools › Elasticsearch DSL Debugger holds the clusters this project can query, as a list with the add, edit, remove and test buttons every other list in Settings has. Double-click edits, the buttons grey out when nothing is selected, and changes are saved as you make them. Remove asks first, because it takes the credentials with it. The tool window’s Settings button opens the same screen.

Screenshot placeholderscreenshots/settings-list.pngSettings › Tools › Elasticsearch DSL Debugger: the cluster list with its toolbar
The cluster list. Each row shows the name, a red PROD marker for a connection marked production, and the URL, drawn the same way as the source dropdown in the tool window.

The Add Cluster dialog shows only the fields the chosen authentication kind uses. It validates in place: the first thing missing is named and its field outlined, and OK waits until it is fixed. Its own Test Connection button tries what has been typed before it is kept, and the answer lands under the form.

AuthenticationSent as
No authentication
Basic (username / password)Authorization: Basic base64(user:password)
API keyAuthorization: ApiKey <key>, whole, as Elasticsearch issues it
Bearer tokenAuthorization: Bearer <token>
AWS SigV4 (Amazon OpenSearch)Each request signed per call. Implemented from the published algorithm and tested against it; not yet confirmed against a real Amazon domain.
Screenshot placeholderscreenshots/settings-dialog.pngThe Add Cluster dialog with Basic authentication chosen and Test Connection pressed
The Add Cluster dialog. Name, base URL, the authentication kind and only its fields, the default index, the timeout, and two checkboxes: ignore SSL certificate errors, and mark the cluster as production.

Secrets never reach the settings file. Passwords, API keys, bearer tokens and AWS secrets go to the IDE password safe, keyed by the connection’s id, so renaming a cluster or moving it to a new host keeps its credentials. Switching a connection’s authentication kind clears the secrets the new kind does not use. The settings file itself, which teams commit, holds only names, URLs and choices.

Run each feature

The short version: what to press, in order. Every feature reads the query in the editor; only the stages need it to have run. With the bundled dataset there is nothing to set up at all.

Once per index, for a live cluster

Settings + cluster pick it in the source box type the Index Refresh fields Check cluster (optional)

Do it again only when you change the index — the line under the toolbar says when the loaded fields belong to another one.

Debug execution — the ten stages

needs: a query
paste the query Debug query Next stage or Run all stages click a stage, read Explain

The count on each stage is Elasticsearch’s answer for that stage.

The funnel — where the documents went

needs: a run to Match
Debug query step to Match read removed on each clause

Then right-click a clause → Disable to see the count without it; Enable everything puts it back.

Diagnose

needs: a query · fields help
paste the query Diagnose Analyze query pick a finding Show what changes

No run needed. Try it on the data runs the remedy and reports what moved.

Performance

needs: a cluster
paste the query Performance Measure performance

One profiled run; the cause line names the heaviest part.

Why not? — one missing document

needs: a query
paste the query Why not? By _id or By field Find

No run needed. Or right-click a row in DataWhy this document?

Text analysis

needs: refreshed fields
Text analysis field + text Analyze

From query… fills both from a clause; add a Document id for the cluster’s verdict on it.

Compare

needs: two runs
Run all stages edit the query Run all stages Inspector › Compare

Totals, funnel, page and buckets, before against after.

Inspector tabs

needs: a run · Problems needs none
click a stage Explain · Data · Score · Aggs Request · Response

Problems lists the validator’s findings as soon as you paste.

The funnel

Where the documents went, clause by clause.

Elasticsearch runs a search as a query phase on every shard and a fetch phase for the returned page, and does not stop between them. The debugger breaks that into ten stages you can stop at, each reporting how many documents are still alive.

1 Parse · 2 kNN · 3 Rewrite · 4 Match · 5 Score · 6 Aggregate · 7 Sort & collect · 8 Rescore · 9 Reduce & paginate · 10 Fetch

Step with Next stage and Previous stage, or jump to the end with Run all stages; Restart discards the results and starts again from stage one, and reads Re-run once the query has been edited. The position is spelled out beside them, Stage 4 of 10 — Match. Each chip carries the count of documents still in play, ↓N for how many the stage removed compared with the one before, and a count of the findings the stage raised in the worst severity’s colour. A skipped stage says why in two words, no knn, no aggs, and a failed one names the exception. Once the strip has focus, the Left and Right keys step through the stages.

Screenshot placeholderscreenshots/stage-strip.pngThe stage controls and the ten chips after Run all stages, with ↓N badges and a skipped stage
The stage strip after a full run. Stages 5 to 7 are one pass over the matching documents and are drawn with a bracket under them; the chips wrap into rows on a narrow window and keep those three together.

Reading the badges

BadgeMeaning
lead 2nd 4th=The order the conjunction is walked, cheapest first: one walk across the whole query, not one per bool, so the ranks and the removed numbers beside them describe the same pass. Ties share a rank and carry =, because which of two equals runs first is not a fact about your query. A bool inside must_not is opaque, so its children carry no rank.
N docsMatches on its own: what this clause finds across the whole index, ignoring every other clause.
removed NRemoved here: how many of the documents that actually reached it were turned away. The number that matters.
removed 0Shown in red. The clause turned nothing away: it costs a scorer on every shard and narrows nothing. This is the finding to act on, and the one number in the funnel that does not depend on the order chosen.
msm NA should gating membership: at least N of the group must match. The removal sits on the group, not on the clauses.
optionalA should beside a must or a filter: it affects score, never membership, and does not appear in the funnel.
disjunctionA should in a bool with no must and no filter. Elasticsearch requires one of them even with no minimum_should_match written, so the group does narrow, and it enters the funnel as a single clause.
excludedA must_not: matching here drops the document.
dashed outlineSwitched off by you, and absent from the request that ran. Faded means the clause plays no part in the stage on the strip.

The two numbers are unrelated, and the second is often much larger. A filter removes everything that does not match it. A clause matching 70,000 documents out of half a million removes the other 430,000 when it runs first, and almost none of them when it runs last. The first says how big the clause is; the second says what it did.

Step through it

Starts where you would: a query in the editor and nothing run yet. Press Next to press Debug query, then walk the funnel the way the debugger builds it. Every count comes from the plugin’s own engine run against the bundled E-commerce catalogue, 48 documents, with the query shown.

Simulation — the tool window as drawn in the IDE’s dark theme, from Debug query to Fetch. Arrow keys move too.

Switching clauses off

Right-click any clause in the graph.

Disable / EnableTake a clause out, everything inside it with it, and run again. Disabling can only widen a result, so the clause that brings your rows back is the one at fault.
IsolateSwitch off everything else, to see what one clause matches alone.
Enable everythingPut it all back.
AnalyzeOn a clause that analyses text: opens Text analysis with the field, the text and any clause analyzer already filled in.
Copy as JSONThe clause as written, for a ticket or a console.

Your query in the editor is never edited. A switched-off clause stays on screen, dashed, and only the body handed to the engine changes: in a must or filter it becomes match_all, inside a must_not it becomes match_none, so that disabling can widen the result and never narrow it. Results from a reduced query are labelled everywhere they appear, the guidance line counts the switched-off clauses, and the substitutions are listed in the Request tab.

Navigating a large query

Scroll to zoom, drag to pan, Shift+scroll to pan sideways. Find a clause jumps to a field or type. Collapse all, Expand all and Fit handle the shape; Indented tree rearranges the same graph as one clause per row for queries wider than the screen; Legend hides and shows the legend at the foot of the canvas. Arrow keys move the selection the way they move through a file tree, Enter opens the clause’s explanation and Space folds and unfolds it.

Where the numbers come from

Which parts are Elasticsearch’s own output, which are arithmetic on real queries, and which are ours.

Inside a bool, the result is the intersection of the clauses. Two quantities follow from that and are measured directly, one search each:

  • What a clause matches alone — that clause run by itself against the index.
  • The running intersection — the first n clauses run together. removed is the difference between one of these and the next.

Against a cluster, every number in the funnel is the count of a query that was really run. The _msearch carrying them is in the Request tab, and any line of it pasted into Kibana returns the same number.

Two things worth knowing about them

removed depends on the order the clauses are walked, and that order is ours: measured counts, cheapest first, which approximates what Lucene does rather than reproducing it. N docs does not depend on it. A clause that removes zero placed last removes zero in any order, which is why that finding is the one to act on.

The funnel checks itself. The last step is the whole conjunction, so its count must equal the count the query itself returned: two independent requests. If they disagree, the panel says so and names both numbers.

Source of each number

NumberSource
N docsthat clause run by itself
removed Ndifference of two running intersections
leadour ordering, by measured count
Performance timingsElasticsearch profile
Score breakdownexplain: true
Why not? rowsone search per clause, for the document you named
Text analysis tokens_analyze with explain: true

That describes a run against a cluster. Offline the counts come from the plugin’s own engine, timings are absent rather than invented, and Text analysis answers with the bundled analyzer and says so above the table. See the offline simulator.

The five views

Debug execution steps through the stages. The other four each ask their own question of the query in the editor, and need no run.

Diagnose

38 rules for the mistakes that never raise an error: the ones that return a result rather than a failure, so nothing tells you they happened. A term on a text field is the familiar example; most of the others are less obvious. Press Analyze query; it needs no run.

The view is a list and a detail. The list is filtered by severity and shows, per finding, the rule, the stage, how sure the rule is and how many examples it offers. Every finding states its certainty separately from what it proposes: Certain follows from the mapping, the analyzer, the settings or the version; Likely was inferred from a sample or a rule of thumb; Depends on what you meant says what the query does so you can decide.

A finding carries examples to try rather than a fix: several whole requests, each with a sentence saying when it is the right reading, its own risk level and, when a data source is available, its own measured outcome. Try it on the data runs the example and your query through every stage without touching the editor and shows what moved in the Inspector’s Compare tab. Show what changes shows the edit before it is applied. The apply button says what it is doing: Apply enhancement for an additive change, Apply suggestion… for one that changes what matches or how it ranks, which asks first, and Copy suggested query where the fix needs a mapping change or a value only you have. From the list, 19 pick an example, and cycle them and Enter shows the change.

Screenshot placeholderscreenshots/diagnose.pngDiagnose: the findings list on the left, a finding's certainty and examples to try on the right
A finding, its certainty, and three examples to try, each with what it did on the data. The hit-set change is measured, not predicted.

Performance

Where the milliseconds went, from Elasticsearch’s own profiler. Measure performance sends one profiled search: the query exactly as written, with profile: true as the only addition. It needs a cluster; without one the view dims.

It reads verdict first, evidence after: one sentence saying which phase dominated, one stacked bar of the phases with time and share in its legend, then sections that open and close. Why first, with the cause and a suggestion; then every clause by its own time, one row per clause as written, named in your DSL rather than in Lucene’s toString(), with the Lucene text on hover; aggregations when there are any; fetch closed until asked for; and By shard, opening itself only when one shard set the response time. Clicking a clause row selects that clause in the graph. Collector time is shown on its own line and kept out of the shares, as Elasticsearch documents it. A clause that a 31-shard index describes 31 times is still one row, with the shards’ time summed onto it.

Measure more than once. The first sample pays for cold caches. The view counts the runs of the same query against the same target, keeps the earlier totals in view, and says when the last two agree, when the numbers are still falling, and when a run came out slower than the one before.

Screenshot placeholderscreenshots/performance.pngPerformance: the verdict, the phase bar, and the clauses table with the slowest at the top
Where the time went, clause by clause. One clause taking half the total is the funnel’s argument applied to milliseconds.

Why not?

The problem. The funnel says how many documents each clause removed. A bug report is about one document: order 4471 should be in these results and it isn’t. Elasticsearch’s _explain gives a readable breakdown when the document matches, a terse nested “no matching term” when it does not, and nothing about paging either way.

How to use it. Have the query in the editor — it does not need to have run. Name the document by its _id, or by a field and value you know, such as sku = SKU-0000003, and press Find. Every clause is then asked about that one document separately, one _msearch entry per clause, so a document failing three clauses reports all three rather than the first one Lucene reached.

What you get. One row per clause and what it did — matches, rejects it, excludes it for a must_not that matched — and, on every row that decided against the document, the value the document holds in that field, read from _source: range price ≥ 50 — rejects it — price is 42. When every clause accepts it, and post_filter and min_score let it through, its position in the hits is measured against the page the request returns: at position 137 of 480, sorted by price desc, and the request returns positions 1–10 — paging is why it is missing. Offline the position is exact; against a cluster it comes from two counts, so it is a range when other documents tie with it. A text clause that rejected the document has a button into Text analysis, with the field, the text and the document filled in.

“No document with that id” is its own answer, an indexing problem rather than a query problem; against an alias holding the same id in several indices the row names the index; when several documents match your key they stay in a dropdown you can move between without re-running. The Data tab’s right-click, Why this document?, opens this view on a row that did match.

Screenshot placeholderscreenshots/why-not.pngWhy not?: one row per clause for one document, the rejecting clause named above
One row per clause, with the clause that turned the document away named in the verdict above the table.

Text analysis

Why a match finds nothing when the words look right.

A search succeeds when the tokens a field stored and the tokens your query produces agree. This shows both. Against a cluster it asks _analyze twice with explain: true: by field for the index side, by analyzer name for the search side, resolved through the whole chain the cluster uses — the clause’s own analyzer, search_quote_analyzer for phrases, search_analyzer, the field’s analyzer, the index’s default_search, then default and standard — and it names which one ran and what chose it. A custom analyzer shows every tokenizer and filter as its own row with the tokens it added or removed; a built-in one shows its tokens, because _analyze does not expose its steps.

From query… fills field, text and analyzer from a clause in the editor. Document names the one you expected to match: its value is read from _source, analysed the way the index analysed it, and the clause is put to the cluster against that one document, so the verdict is Elasticsearch’s. Then it says what the difference means, one sentence per finding: a term on a text field is not analysed, a stemmer on one side only, a stop word that still counts a position, a keyword normalizer, a synonym_graph in the index chain.

Screenshot placeholderscreenshots/text-analysis.pngText analysis: both chains as tables of chips, the synonym step named, the verdict under the card
Both sides of the cable of the tv on title_en. The index chain drops the stop words and stems; the search chain’s lab_synonyms adds television first, and lab_stemmer turns it into televis — green on the last row is what both sides produced, red is what only the query does, struck through is what a step took away.
Simulation — the same text in title and title_en of the bundled catalogue, with the tokens a 9.5.3 cluster returned for each.

Offline, the view answers with the bundled analyzer and says so above the table. The chain’s steps are not shown, and a custom analyzer, a synonym set or a stemmer can differ; pick a cluster for the real answer.

The Inspector

Beside the graph in Debug execution: eight tabs, each about the run, dimmed until it has what they read.

ExplainWhat the stage did, in sections: the funnel, what this run found, the rewrites, what happens for this request in order, and what the stage is. Select a clause and it explains that clause, read against your mapping.
DataThe documents still alive at the stage you stopped on, with how many of how many matched. Double-click a row for the whole document in a read-only JSON viewer with folding and Copy; right-click to ask why it matched. An empty table says which kind of empty it is.
ScoreWhy one document ranked above another: BM25 term by term, with the largest added term named and an operator legend when a factor or a replacement applies. Against a cluster, the per-shard term statistics that make the same document score differently by shard.
AggsThe buckets, their share of the matched documents, and the reconciliation of buckets against hits: a field with no value, a multi-valued field, a terms trimmed to size, said in words under the header.
ProblemsWhat the validator found in the request, attached to the exact clause.
RequestExactly what was sent for the stage, including the substitutions for any clauses you switched off, and the extra calls the Match stage made.
ResponseExactly what came back. Offline the envelope is rebuilt from what the engine computed, in the shape a cluster returns.
CompareWhat moved between two runs. Dimmed until there are two. Described below.

Compare

The rest of the Inspector answers what is happening. This tab answers did my edit work. It sits in the Inspector rather than in the view row because it reads what the stages produced: a comparison exists only once they have run twice, and the tab dims until then.

Every run that reaches Match is remembered, the last six, in memory. Change something, run again, and this shows what moved: the total, which documents on the returned page, which clauses’ counts, which buckets, and any stage whose status changed. The verdict leads with whatever would be missed most: a stage that started failing, then documents, then a total that moved while the page stayed the same, then clauses, then buckets.

The finding it exists for is the one a count cannot give you. A query going from 47 hits to 47 hits looks unchanged; if three of those documents are different ones, it is not.

Simulation — ten documents of the bundled catalogue named by _id, so every number is exact; the verdicts are the sentences the view produces.

By default a run is measured against the one before it. Several edits into chasing a regression that compares two broken states, so Pin as baseline holds the run that was last right, and everything is measured against it until you unpin. Warnings appear above the table only when the pair is not a fair comparison: a different index or source, a changed size, clauses switched off in one run only, a run stopped before Fetch, a reordering in a query with no sort, and a page that is a slice of the matches.

How far you run decides what you can compare

Stopped atYou can compare
Parse, kNN, Rewritenothing: no run is recorded before Match
Matchthe total and each clause’s counts, but no documents
Fetch (all stages)everything, including which documents changed

The offline simulator

How the plugin runs a query with no cluster, and where that stops being the same thing.

Pick a bundled dataset instead of a connection and the whole tool still works: ten stages, the funnel, per-clause counts, aggregations, sorting, scoring. There is no Elasticsearch behind it. The plugin carries its own query engine and runs your query against the documents inside the jar.

DatasetIndexDocumentsExamples
E-commerce catalogueproducts4852
Application logsapp-logs9616

It is there so the tool can be learned, and a query understood, without a connection. Because the sample data is static, the simulator pins now to just after the newest sample document, so date-math examples keep selecting data however old the build is.

What it does itself

The plugin’s coverage file, generated from the code by the test suite, counts three catalogues separately.

CatalogueKnownSimulatedApproximatedLive cluster only
Query clauses5826626
Aggregations8037835
Search-body features3820711

Approximated means evaluated with a stated caveat carried on the result itself: kNN scans every vector rather than walking an HNSW graph, scripts run a Painless subset. BM25 is computed as Lucene computes it, with the lead clause chosen by a Lucene-style cost estimate; absolute scores differ from a real index because the term statistics do, and the arithmetic is the same.

Where it stops

The line matters more than the coverage, because a simulator that guesses is worse than no simulator. Where it cannot reproduce Elasticsearch it reports that it cannot, rather than producing a number:

  • A clause or aggregation it does not evaluate stops the simulation of that stage and is reported as not simulated. No counts and no scores are invented, and the live engine is offered instead.
  • An unknown clause name is reported as the parsing_exception it would cause; an unsimulated real clause as a limit of the tool. The two are kept apart.
  • Text analysis answers with the bundled analyzer and says so. The built-in analyzers are approximated; a custom one falls back to standard and the note names it.
  • Timings are absent rather than invented. There is no shard, so there is nothing to time, and the Performance view dims.

Learn the tool on the bundled data; trust a number from the index it is about. The offline counts are exact for the sample corpus and produced by the plugin’s code rather than by Elasticsearch. Those are not the same claim, and a finding you intend to act on should come from your own cluster.

Limitations

What the plugin caps, approximates, or cannot know. Each one is stated in the interface at the moment it applies; they are gathered here so none of them is a surprise.

The funnel

LimitWhat it means
Sampled above 12 clauses Up to twelve, every step of the conjunction is measured. Above that it measures twelve spread points, always including the whole conjunction, then spends up to sixteen more probes bisecting every span that lost documents until each drop belongs to one clause. The final count is your real result at any size, and where a drop could not be separated the panel says it belongs to a span rather than guessing.
60 clauses carry counts The per-clause search carries sixty items. A query with more leaf clauses than that has no N docs badge beyond the sixtieth.
Checked against the query The last step is the whole conjunction, so its count must equal the count the query itself returned. If the two disagree, the panel says so and names both numbers.
removed depends on the order And the order is ours: measured counts, cheapest first. Lucene orders by estimated cost, which is not exposed. A clause that removes zero placed last removes zero in every order.

Results and documents

  • The last two stages cap the transfer at 100 documents even when the query asks for ten thousand. Totals, aggregations and ordering are decided on the shard before documents are sent, so nothing computed changes; the stage says so when the cap applies.
  • Why not? takes an _id or up to three fields, combined with and: enough to identify a document, not a query builder.
  • The per-shard listing stops at six shards. It costs one search per shard, so on a 100-shard index it would mean a hundred extra searches. Performance timings, its clause table and its shard-skew finding come from the single profiled search and cover every shard.

Text analysis

  • One field and one text at a time. A multi_match over five fields is five checks.
  • Built-in analyzers show tokens, not steps, because _analyze does not expose them. A custom analyzer shows every step.
  • Offline is an approximation, labelled as one above the table.

Compare

  • Six runs, this session only. A baseline from last week would be measured against an index that has changed underneath it.
  • The document rows compare a page unless size covers every match, and the warning says how many of how many.
  • Nothing is recorded before Match, and documents only exist from Fetch.

Performance timings

  • Elasticsearch’s numbers, not ours. Every figure is profile’s time_in_nanos; the plugin times nothing itself.
  • Per-clause time has its children subtracted, so a bool is not credited with the work of every clause inside it.
  • Shard work is not wall clock. Shards run in parallel, so the summed work exceeds took and is meant to. The subtitle states both.
  • Profiling changes what it measures, which is Elasticsearch’s own caveat. Read the proportions, not the milliseconds, and measure more than once.

By design, and not planned

Deep pagination can be diagnosed but never demonstrated. _pit and _scroll leave state on your cluster, so they are refused. The plugin will tell you that from: 10000 is past the result window and what to do instead; it will not open a point in time to show you. That is the read-only guarantee costing a feature, and it is the right trade.

  • Elasticsearch 8 and 9. Older majors are flagged at Check cluster. OpenSearch is named correctly and its version numbers are not modelled, so the version-aware rules do not apply to it.
  • The Query DSL only, not ES|QL, EQL, SQL or KQL.
  • Nothing re-runs on its own. A debugger that re-ran on every keystroke would reach your cluster on every keystroke.
  • No completion in the query editor, deliberately: a suggestion that is right most of the time is worse here than none. Field names are still offered where the question is unambiguous, in the index box and the field boxes of Why not? and Text analysis.
  • AWS SigV4 is implemented and tested against the algorithm, not yet confirmed against a real Amazon domain. If it matters to you, write and it will be confirmed or fixed.

Reference

Every request the plugin makes, and where it makes it.

What each stage sends

StageRequest
Parse_validate/query, and _settings for the shard count
kNNthe knn clause alone with size: k; skipped unless the query has one
Rewrite_validate/query?explain=true&rewrite=true for the Lucene query, and _analyze per analysed clause for the tokens
Matchthe query alone, sorted by _doc with track_total_hits: true, plus one _msearch with a count per clause and one per prefix of the conjunction: that pair is the funnel
Scorethe query with explain: true and min_score
Aggregatethe query with size: 0 and the aggregations, pipelines removed, a top_hits of three per bucket for samples; post_filter omitted, as the collector chain omits it
Sort & collectthe collector’s view: post_filter, sort, collapse, search_after, terminate_after, size: from + size, scores tracked and _source: false
Rescorethe same plus rescore; skipped unless the query has one
Reduce & paginatethe request as written minus explain and profile, with typed_keys=true
Fetchthe request exactly as written, reusing the previous response when identical

Outside the stage loop: Performance sends one search with profile: true, Text analysis calls _analyze, Why not? calls _search once per clause for the document you named, Refresh fields reads _mapping, _settings and _field_caps, and the per-shard listing at Reduce re-runs the query with preference=_shards:n.

Keyboard

Ctrl+SpaceReopen the field or index list in any box that has one. The list also opens on click and narrows as you type.
Scroll · Shift+scroll · dragZoom, pan sideways, pan the graph
Move the selection through the graph; in the stage strip, step through the stages
Enter · SpaceIn the graph, explain the clause; fold and unfold it
19 · · EnterIn the Diagnose list, pick an example, cycle them, show the change
Delete · F2In History, delete and rename

Next stage, Previous stage, Run all stages and Restart are in Find Action and bindable under Settings › Keymap. Six tips appear once each, chosen by what just happened, and Show Tips Again in Find Action brings them back.

Safety and privacy

What it can do to your cluster, and what leaves your machine.

It reads and it cannot write. Not by policy but by construction: every request is checked against an allow-list of read-only endpoints before a socket is opened, and everything else is refused, including anything unfamiliar. No _bulk, no _doc, no _update_by_query, no _delete_by_query, no _reindex, no PUT, no DELETE.

Endpoints that would leave server-side state, _pit, _scroll and _async_search, are refused too. A point in time may be suggested to you; the plugin never opens one.

Allowed_search _msearch _count _explain _validate _analyze _mapping _settings _field_caps _alias _aliases _cat _cluster _nodes _stats _resolve _terms_enum _synonyms
TelemetryNone. Nothing about your queries or your cluster leaves your machine.
CredentialsThe IDE password safe, never a project file.
Queries and historyYour project’s workspace.xml, which is personal rather than shared.
TLSIgnoring certificate errors is a per-connection choice, for a development cluster, and it is scoped to this plugin’s own HTTP client rather than the IDE’s.
AI or cloud servicesNone. The plugin talks to your cluster and nothing else.

Common questions

Why does a clause show a huge count but removed 0?

It matches almost everything, so by the time it runs there is nothing left for it to exclude. It is costing you a scorer on every shard and narrowing nothing. This is the funnel’s most common finding.

Why is “removed” bigger than the clause’s own count?

They count different things. A filter removes everything that does not match it, so a clause matching 70,000 of half a million removes the other 430,000 when it runs first.

My query has no results and every clause looks fine.

Open Text analysis on a match clause: right-click it in the graph and choose Analyze. A stemmer on one side, a stop list or an index-level default_search analyzer can mean the tokens your query produces were never the tokens the field stored.

Does disabling a clause change my query?

No. The editor is untouched. The body actually sent is in Request, and the guidance line counts how many clauses are switched off.

Why does Compare say nothing changed when I edited the query?

A run is recorded when it reaches Match. If you edited and did not run again, there is nothing new to compare. If you stopped before Match, no run was recorded at all.

Why is there no autocomplete in the query editor?

Deliberate. This is a debugger, not an editor, and a completion that offers field names where query types belong is worse than none. Write your query in Kibana or in your code, then bring it here.

Can I use it without a cluster?

Yes. Two bundled datasets and 68 worked examples. Everything runs offline except Performance, which needs a real shard to profile; Text analysis answers offline with the bundled analyzer and says so.

Does it support ES|QL, EQL or SQL?

No, the Query DSL only. For SQL, POST /_sql/translate gives you the DSL body without running it, and you can debug that here.

Is it safe to point at production?

It cannot write. Every request goes through a read-only allow-list, the full list of what it sends is in Reference, and a connection marked production carries a red PROD marker everywhere it appears.