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.
Start here
What it is, what it is not, and the first three minutes.
_search. Not ES|QL, EQL, SQL or KQL.Three minutes
- Open the ES Debugger tool window, on the bottom strip.
- Choose a source: a bundled dataset, or one of your saved clusters and an index.
- Paste a search body, or pick one of the 68 examples in the sidebar.
- Press Debug query. The run stops at stage one.
- 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.
| Area | What is in it |
|---|---|
| Sidebar | Four tabs, each titled with how much it holds: Examples, Mapping, Outline and History. See the sidebar. |
| Query | The 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. |
| Views | A 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 execution | The 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. |
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 this | Or else |
|---|---|
| Check cluster | Reports 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 index | The 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 fields | Reads 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.
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.
| Authentication | Sent as |
|---|---|
| No authentication | — |
| Basic (username / password) | Authorization: Basic base64(user:password) |
| API key | Authorization: ApiKey <key>, whole, as Elasticsearch issues it |
| Bearer token | Authorization: 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. |
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
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 queryThe count on each stage is Elasticsearch’s answer for that stage.
The funnel — where the documents went
needs: a run to MatchThen right-click a clause → Disable to see the count without it; Enable everything puts it back.
Diagnose
needs: a query · fields helpNo run needed. Try it on the data runs the remedy and reports what moved.
Performance
needs: a clusterOne profiled run; the cause line names the heaviest part.
Why not? — one missing document
needs: a queryNo run needed. Or right-click a row in Data → Why this document?
Text analysis
needs: refreshed fieldsFrom query… fills both from a clause; add a Document id for the cluster’s verdict on it.
Compare
needs: two runsTotals, funnel, page and buckets, before against after.
Inspector tabs
needs: a run · Problems needs noneProblems 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.
Reading the badges
| Badge | Meaning |
|---|---|
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 docs | Matches on its own: what this clause finds across the whole index, ignoring every other clause. |
removed N | Removed here: how many of the documents that actually reached it were turned away. The number that matters. |
removed 0 | Shown 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 N | A should gating membership: at least N of the group must match.
The removal sits on the group, not on the clauses. |
optional | A should beside a must or a filter: it
affects score, never membership, and does not appear in the funnel. |
disjunction | A 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. |
excluded | A must_not: matching here drops the document. |
| dashed outline | Switched 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.
Switching clauses off
Right-click any clause in the graph.
| Disable / Enable | Take 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. |
| Isolate | Switch off everything else, to see what one clause matches alone. |
| Enable everything | Put it all back. |
| Analyze | On a clause that analyses text: opens Text analysis with the field, the text and any clause analyzer already filled in. |
| Copy as JSON | The 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.
removedis 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
| Number | Source |
|---|---|
N docs | that clause run by itself |
removed N | difference of two running intersections |
lead | our ordering, by measured count |
| Performance timings | Elasticsearch profile |
| Score breakdown | explain: true |
| Why not? rows | one 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, 1–9 pick an example, ← and → cycle them and Enter shows the change.
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.
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.
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.
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.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.
| Explain | What 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. |
| Data | The 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. |
| Score | Why 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. |
| Aggs | The 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. |
| Problems | What the validator found in the request, attached to the exact clause. |
| Request | Exactly what was sent for the stage, including the substitutions for any clauses you switched off, and the extra calls the Match stage made. |
| Response | Exactly what came back. Offline the envelope is rebuilt from what the engine computed, in the shape a cluster returns. |
| Compare | What 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.
_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 at | You can compare |
|---|---|
| Parse, kNN, Rewrite | nothing: no run is recorded before Match |
| Match | the 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.
| Dataset | Index | Documents | Examples |
|---|---|---|---|
| E-commerce catalogue | products | 48 | 52 |
| Application logs | app-logs | 96 | 16 |
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.
| Catalogue | Known | Simulated | Approximated | Live cluster only |
|---|---|---|---|---|
| Query clauses | 58 | 26 | 6 | 26 |
| Aggregations | 80 | 37 | 8 | 35 |
| Search-body features | 38 | 20 | 7 | 11 |
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_exceptionit 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
standardand 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
| Limit | What 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
_idor 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_matchover five fields is five checks. - Built-in analyzers show tokens, not steps, because
_analyzedoes 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
sizecovers 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’stime_in_nanos; the plugin times nothing itself. - Per-clause time has its children subtracted, so a
boolis 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
tookand 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
| Stage | Request |
|---|---|
| Parse | _validate/query, and _settings for the shard count |
| kNN | the 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 |
| Match | the 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 |
| Score | the query with explain: true and min_score |
| Aggregate | the 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 & collect | the collector’s view: post_filter, sort, collapse,
search_after, terminate_after, size: from + size, scores tracked and
_source: false |
| Rescore | the same plus rescore; skipped unless the query has one |
| Reduce & paginate | the request as written minus explain and profile, with
typed_keys=true |
| Fetch | the 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+Space | Reopen 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 · drag | Zoom, pan sideways, pan the graph |
| ↑ ↓ ← → | Move the selection through the graph; in the stage strip, step through the stages |
| Enter · Space | In the graph, explain the clause; fold and unfold it |
| 1–9 · ← → · Enter | In the Diagnose list, pick an example, cycle them, show the change |
| Delete · F2 | In 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 |
| Telemetry | None. Nothing about your queries or your cluster leaves your machine. |
| Credentials | The IDE password safe, never a project file. |
| Queries and history | Your project’s workspace.xml, which is personal rather than shared. |
| TLS | Ignoring 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 services | None. The plugin talks to your cluster and nothing else. |
Common questions
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.
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.
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.
No. The editor is untouched. The body actually sent is in Request, and the guidance line counts how many clauses are switched off.
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.
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.
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.
No, the Query DSL only. For SQL, POST /_sql/translate gives you the DSL body without
running it, and you can debug that here.
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.