Connecting Elasticsearch to n8n: index, search and feed a RAG
Published 26 August 2026 · 8 min read
Elasticsearch shows up in two conversations that have almost nothing in common. In the first, it is the engine behind a Kibana, the place where documents land so they can be searched in full text. In the second, it is a serious candidate for the retrieval layer of a RAG pipeline, with BM25 on one side and dense vectors on the other. The n8n Elasticsearch node (n8n-nodes-base.elasticsearch) serves both, but not to the same depth: it covers indexing and everyday search, and leaves the rest to the HTTP Request node. This guide draws that boundary, with the node's real parameters, the request bodies you have to write by hand, and the traps that cost the most.
Two use cases not to confuse
Use case 1 — search engine and observability. n8n feeds an index (tickets, orders, business events) or queries it to trigger something. The native node is enough in the vast majority of cases.
Use case 2 — retriever for a RAG pipeline. The corpus is chunked, indexed, then queried with BM25, vector kNN, or both. The native node no longer suffices: vector search must go through HTTP Request, and you owe yourself an honest comparison with what a dedicated vector database such as Qdrant offers.
Conflating the two always ends in the same disappointment: an index thrown together for logging, later recycled as a knowledge base, with an unsuitable mapping and no vector field.
The credential: three fields, that is all
The n8n Elasticsearch credential is deliberately minimal:
- Base URL — your cluster URL, port included (
https://es.example.com:9200); - Username and Password — basic authentication;
- Ignore SSL Issues — a toggle to tolerate a self-signed certificate.
Basic auth is the only supported method: no API key, no Cloud ID. On Elastic Cloud it still works — the deployment endpoint as Base URL, a cluster account — but if your organisation mandates API keys, switch to an HTTP Request node with a Header Auth credential carrying Authorization: ApiKey <encoded value>. The HTTP Request node can also reuse the Elasticsearch credential through the Predefined Credential Type option. Either way, apply the principles of secure credential storage: a dedicated n8n account, with roles restricted to the relevant indices, never the elastic superuser.
What the node actually does
Index resource
Four operations: Create, Delete, Get, Get Many. Create is richer than it looks: its Additional Fields include Aliases, Mappings, Settings and Wait for Active Shards. So you can create a properly typed index straight from n8n — the move described below. Get Many, by contrast, is limited to Return All / Limit.
Document resource
Five operations: Create, Delete, Get, Get Many, Update.
- Create and Update use the usual
Data to Send/Fields to Send/Inputs to Ignoreselector to build the document from the item's fields. Their options includeRefreshand, for Create,Pipeline IDif an ingest pipeline should enrich the document. - Get accepts
Source Includes,Source ExcludesandStored Fields, plus aSimplifytoggle that flattens the response (_idand the contents of_source) instead of the raw Elasticsearch envelope. - Get Many is by far the richest: beyond
Return AllandLimit, its options coverQuery,Query Parameters,Sort,Routing,Search Type,Track Scores,Track Total Hits,Explain,Terminate After,TimeoutandRequest Cache.
The Query option is the one most tutorials miss: it takes raw Query DSL, with a parameter mechanism — you write $1, $2 in the query and supply the values in Query Parameters, exactly like the prepared statements of a Postgres node. The Sort option expects field:direction pairs separated by commas, for instance created_at:desc,priority:asc.
Bulk indexing exists too, quietly: Create, Update and Delete each carry a boolean option (Bulk Create, Bulk Update, Bulk Delete) that groups incoming items and sends them to the _bulk API in batches.
What stays the HTTP Request node's job
The list of gaps is short but structural: _msearch, vector knn search, aggregations (aggs), deep pagination with search_after + PIT, optimistic concurrency control (if_seq_no / if_primary_term), and updating an existing mapping.
For a hand-written _bulk, the format is NDJSON: one action line, one document line, and a mandatory trailing newline.
POST /tickets/_bulk
{"index":{"_id":"TCK-1042"}}
{"subject":"Duplicate invoice","status":"open","created_at":"2026-08-26T09:12:00Z"}
{"index":{"_id":"TCK-1043"}}
{"subject":"Forgotten password","status":"resolved","created_at":"2026-08-26T09:14:00Z"}
On the n8n side: a Raw body with content type application/x-ndjson, assembled by a Code node. And above all, read the response — a _bulk returns 200 OK even if half the documents failed. It is the errors field in the body that matters, not the HTTP status.
A real _search request, meanwhile, looks like this:
{
"query": {
"bool": {
"must": [{ "match": { "subject": "invoice" } }],
"filter": [
{ "term": { "status": "open" } },
{ "range": { "created_at": { "gte": "now-7d" } } }
]
}
},
"size": 20,
"sort": [{ "created_at": "desc" }]
}
The must / filter distinction is decisive: filter does not contribute to the score and goes through the filter cache, whereas must brings the term into relevance scoring — the same reasoning as metadata filtering in a RAG.
Set the mapping before you index
Without an explicit mapping, Elasticsearch guesses each field's type on first insertion. This dynamic mapping fails in predictable ways: a date written 26/08/2026 becomes a text, an identifier such as TCK-1042 is split into tokens and will never again be found by an exact search, an amount sent as a string rules out any range. And a field type cannot be changed afterwards: the only way out is a full reindex. Hence the move — create the index with its mappings from the start, through Index → Create and its Mappings field.
{
"properties": {
"subject": { "type": "text", "analyzer": "english" },
"status": { "type": "keyword" },
"customer_id":{ "type": "keyword" },
"created_at": { "type": "date" },
"embedding": { "type": "dense_vector", "dims": 1536, "index": true, "similarity": "cosine" }
}
}
The rule of thumb fits in one sentence: text for what you search in natural language, keyword for what you filter, aggregate or sort on.
Elasticsearch as a RAG building block
BM25, the Elasticsearch relevance function, is not an engineering trick: it is the outcome of the Probabilistic Relevance Framework, whose reference synthesis Stephen Robertson and Hugo Zaragoza published as "The Probabilistic Relevance Framework: BM25 and Beyond" in 2009 in Foundations and Trends in Information Retrieval (see on Google Scholar). Thirty years of literature behind an algorithm that costs nothing at inference time: that is why you should never assume an embedding will automatically do better.
The BEIR benchmark by Nandan Thakur, Nils Reimers, Andreas Rücklé, Abhishek Srivastava and Iryna Gurevych — "BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models", NeurIPS Datasets and Benchmarks 2021 (see on Google Scholar) — measured exactly that across 18 datasets and 10 retrieval systems: BM25 remains a remarkably robust out-of-domain baseline, while several dense models that excel on their training corpus degrade noticeably once moved. So do not replace a lexical search that works with embeddings without measuring, and remember that the choice of embedding model weighs as much as the choice of engine.
Elasticsearch can do both: an indexed dense_vector field — on an HNSW structure, the same index family as pgvector — queried through the knn clause:
{
"knn": {
"field": "embedding",
"query_vector": [0.021, -0.117, 0.083],
"k": 10,
"num_candidates": 100,
"filter": { "term": { "customer_id": "ACME" } }
}
}
The real vector carries as many dimensions as declared in the mapping, and this body goes through HTTP Request: the node does not expose it. Elasticsearch also offers an rrf retriever to fuse a lexical query with a knn, whose availability depends on your cluster's licence tier. The principle behind that fusion is detailed in our guide to hybrid search for RAG, and the gain is almost always consolidated by a reranking step.
Four concrete use cases
- Internal search over support tickets. Every closed ticket goes into an index, subject as
textand status askeyword; agents find similar past cases in one query — the natural complement to AI ticket scoring. - Application-level business logs. Not system logs: Beats and Logstash remain far better at that, and monitoring an n8n instance belongs to other tools. Business events that exist nowhere else (a cancelled order, a requalified case), on the other hand, deserve their index.
- Alerting from a scheduled query. A Schedule Trigger every fifteen minutes, a Document → Get Many operation with a
Queryfiltering oncreated_at >= now-15m, and a notification if the count crosses a threshold. - Multi-source Kibana dashboard. n8n aggregates CRM, billing and form data into a shared index, Kibana displays the lot — without writing an ETL.
The traps
- The 10,000-result ceiling.
index.max_result_windowcapsfrom + sizeat 10,000 by default, and the node'sReturn Alloption translates in its code to asizefixed at 10,000. Beyond that you needsearch_afterwith a point in time, so HTTP Request and a pagination loop. Raisingmax_result_windowis a false good idea: memory use grows with depth. - Indexing document by document. Without the Bulk options, every item fires a separate HTTP request. Over 5,000 rows the difference is measured in minutes and in needless pressure on the cluster.
- No concurrency control. The node exposes neither
if_seq_nonorif_primary_term: two workflows updating the same document overwrite each other silently. If order matters, serialise on the n8n side or go through HTTP Request. - Refresh is not immediate. Elasticsearch is near real-time, with a one-second refresh interval by default: a workflow that indexes then immediately reads back will find an empty index. The
Refreshoption (trueorwait_for) exists for those cases, and should be reserved for small volumes. - A cluster with no authentication. An Elasticsearch open to the internet without a password is a guaranteed incident, not a risk. HTTPS, a dedicated account, roles restricted to the relevant indices, and
Ignore SSL Issueskept to the lab.
Key takeaways
The Elasticsearch node covers more ground than its reputation suggests: two resources, nine operations, a Query option that accepts parameterised Query DSL, and Bulk toggles that avoid the worst of one-by-one indexing. Its limits are clear-cut: no knn, no aggregations, no search_after, no concurrency control, and a credential that only knows basic auth. Everything else happens in the HTTP Request node. And before anything else, set the mapping: it is the one decision in this guide that a full reindex will be needed to fix.
Going further
If your goal is an assistant that answers over your documents, the RAG Assistant Pack (€119) provides the full chain — chunking, embeddings, retrieval and anti-hallucination guardrails — ready to plug into Elasticsearch or the vector database of your choice. And if the corpus to index is your inbound emails and tickets, the AI Inbox Pack (€79) builds the upstream triage layer that decides what deserves to enter the index.
FAQ
Frequently asked questions
Can the n8n Elasticsearch node do bulk indexing?
Yes, partly. The Create, Update and Delete operations on the Document resource each expose a boolean option (Bulk Create, Bulk Update, Bulk Delete) that groups incoming items and sends them to the Elasticsearch _bulk API in batches, rather than one HTTP request per document. That is more than enough to ingest a few thousand rows. What you do not get is control over batch size, per-line action types or per-document pipelines: for that, build the NDJSON body yourself and send it with an HTTP Request node.
Does the n8n Elasticsearch credential accept an API key or a Cloud ID?
No. The n8n Elasticsearch credential offers only three fields — Base URL, Username, Password — plus an Ignore SSL Issues toggle. Basic authentication is the only supported method. On Elastic Cloud it still works: point Base URL at your deployment endpoint and use a cluster user account; the Cloud ID is not used here. If your security policy mandates API keys, use an HTTP Request node with a Header Auth credential carrying an Authorization header of type ApiKey.
Why does the node never return more than 10,000 documents?
Two ceilings stack up. On the n8n side, the Return All option of the Get Many operation translates in the node's code to a size fixed at 10,000. On the Elasticsearch side, the index.max_result_window setting caps from + size at 10,000 documents by default, and a request beyond that returns an explicit result window too large error. Past that point there is no magic option: you have to paginate with search_after plus a point in time (PIT), which means the HTTP Request node.
Should I pick Elasticsearch or a vector database for RAG in n8n?
It depends on what you are retrieving. If your corpus already lives in Elasticsearch and users search for exact references — an invoice number, a product code, a precise phrase — BM25 does the job with no embeddings and no inference cost. If queries are written in natural language and paraphrasing is the norm, a dedicated vector database is simpler to operate and better integrated with n8n's Vector Store nodes. A good share of real cases end up in hybrid search.
Bundle FlowKit Complet
€269