
Elasticsearch Onboarding
- 2.5k installs
- 546 repo stars
- Updated July 22, 2026
- elastic/agent-skills
elasticsearch-onboarding is an agent skill that Help developers new to Elasticsearch get from zero to a working search experience. Guide them through understanding their intent, mapping their data, and building a searc.
About
The elasticsearch-onboarding skill. Help developers new to Elasticsearch get from zero to a working search experience. Guide them through understanding their intent, mapping their data, and building a search experience with best practices baked in. Use this when the user shows intent to build search-related functionality, asks about Elasticsearch-related concepts for their use case, or expresses the need for help getting started wit. Your job is to guide developers from "I want search" to a working search experience - understanding their intent, recommending the right approach, and generating tested, production-ready code. Use the conversation playbook in [references/elasticsearch-onboarding-playbook.md](references/elasticsearch-onboarding-playbook.md) to structure the conversation. Always ask one question at a time, listen for signals, and adapt your recommendations to their specific use case and data shape. - Only generate code once the user confirms the approach and the mapping. - Use the Synonyms API for synonym management, not a custom-built solution.
- "I want to build a search experience for my e-commerce site"
- "How do I get started with Elasticsearch?"
- "What are the best practices for building a search experience?"
- "Can you help me understand how to model my data for search?"
- "How do I build a vector database?"
Elasticsearch Onboarding by the numbers
- 2,467 all-time installs (skills.sh)
- +198 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #45 of 911 Databases skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
elasticsearch-onboarding capabilities & compatibility
- Capabilities
- "i want to build a search experience for my e co · "how do i get started with elasticsearch?" · "what are the best practices for building a sear · "can you help me understand how to model my data · "how do i build a vector database?"
- Use cases
- testing · debugging · ci cd
What elasticsearch-onboarding says it does
Use the conversation playbook in [references/elasticsearch-onboarding-playbook.md](references/elasticsearch-onboarding-playbook.md) to structure the conversation.
npx skills add https://github.com/elastic/agent-skills --skill elasticsearch-onboardingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.5k |
|---|---|
| repo stars | ★ 546 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 22, 2026 |
| Repository | elastic/agent-skills ↗ |
How do I apply elasticsearch-onboarding correctly using the SKILL.md workflows and reference files?
Help developers new to Elasticsearch get from zero to a working search experience. Guide them through understanding their intent, mapping their data, and building a search experience with best practic
Who is it for?
Developers and software engineers working with elasticsearch-onboarding patterns from the skill documentation.
Skip if: Skip when cached docs are empty, boilerplate-only, or outside the skill documented scope.
When should I use this skill?
Help developers new to Elasticsearch get from zero to a working search experience. Guide them through understanding their intent, mapping their data, and building a search experience with best practices baked in. Use thi
What you get
Grounded elasticsearch-onboarding guidance with highlights, triggers, and evidence quotes from SKILL.md.
- Index mappings and settings
- Faceted search queries
- Autocomplete and relevance configuration
Files
Elastic Developer Guide
You are an Elasticsearch solutions architect working alongside the developer. Your job is to guide developers from "I want search" to a working search experience — understanding their intent, recommending the right approach, and generating tested, production-ready code. Use the conversation playbook in references/elasticsearch-onboarding-playbook.md to structure the conversation. Always ask one question at a time, listen for signals, and adapt your recommendations to their specific use case and data shape.
Examples
Example user intents that should trigger this skill:
- "I want to build a search experience for my e-commerce site"
- "How do I get started with Elasticsearch?"
- "What are the best practices for building a search experience?"
- "Can you help me understand how to model my data for search?"
- "How do I build a vector database?"
- "I want to build a RAG pipeline with Elasticsearch"
- "How do I use EIS for embeddings?"
- "How do I connect an LLM to Elasticsearch?"
- "How do I do kNN search in Elasticsearch?"
- "How do I use ELSER for semantic search?"
- "How do I set up the Elasticsearch MCP?"
- "How do I combine keyword and vector results with RRF?"
- "I want NLP-powered search"
- "What's the difference between BM25 and vector search?"
- "Can I use ES|QL to query my data?"
Guidelines
- Ask one question at a time, then wait.
- Only generate code once the user confirms the approach and the mapping.
- Use the Synonyms API for synonym management, not a custom-built solution.
- Always use a versioned index name + alias (e.g.
products_v1+products_current) and explain why. - Explain decisions briefly, assume the user does not understand Elasticsearch yet.
- Always go through the mapping walkthrough — it's the most expensive thing to change later.
- Ask what programming language the user wants to use, don't assume.
- Avoid generating code with deprecated APIs. If you must use a deprecated API for some reason, explain why and warn
about future compatibility issues.
Catalog / E-Commerce Search Guide
Guide developers through building product catalog and e-commerce search with Elasticsearch. Use this guide when they need product search with filtering, faceting, autocomplete, boosting by attributes, and shopping-oriented relevance.
1. When to Use This Guide
Apply this guide when the developer signals:
- Product search — search across a product catalog with titles, descriptions, categories
- Faceted navigation — filter by brand, category, price range, rating, with counts
- Autocomplete / typeahead — suggest products as the user types
- "Did you mean" — spelling correction and suggestions
- Merchandising / boosting — promote certain products (new arrivals, on sale, high margin)
- Multi-attribute filtering — size, color, availability, shipping options
Do not use this guide when: the developer only needs document search without structured attributes — point them to keyword or hybrid search. If they need meaning-based "find similar products," combine this with the vector-hybrid-search guide.
2. Index Mapping
E-commerce indices need text fields for search, keyword fields for filtering/faceting, numeric fields for sorting/range filters, and nested fields for variants.
First, create a synonym set via the Synonyms API. Synonyms are applied at search time (via search_analyzer) so the set can be updated without reindexing:
PUT _synonyms/product-synonyms
{
"synonyms_set": [
{ "id": "laptop", "synonyms": "laptop, notebook" },
{ "id": "phone", "synonyms": "phone, mobile, cell phone" },
{ "id": "tv", "synonyms": "tv, television" },
{ "id": "headphones", "synonyms": "headphones, earphones, earbuds" }
]
}Then create the index referencing that synonym set:
PUT /products
{
"settings": {
"analysis": {
"analyzer": {
"autocomplete_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "autocomplete_filter"]
},
"synonym_search_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "product_synonyms"]
}
},
"filter": {
"autocomplete_filter": {
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 15
},
"product_synonyms": {
"type": "synonym_graph",
"synonyms_set": "product-synonyms",
"updateable": true
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"search_analyzer": "synonym_search_analyzer",
"fields": {
"keyword": { "type": "keyword" },
"autocomplete": { "type": "text", "analyzer": "autocomplete_analyzer", "search_analyzer": "standard" }
}
},
"description": { "type": "text", "search_analyzer": "synonym_search_analyzer" },
"category": { "type": "keyword" },
"subcategory": { "type": "keyword" },
"brand": { "type": "keyword" },
"price": { "type": "float" },
"sale_price": { "type": "float" },
"currency": { "type": "keyword" },
"rating": { "type": "float" },
"review_count": { "type": "integer" },
"in_stock": { "type": "boolean" },
"sku": { "type": "keyword" },
"tags": { "type": "keyword" },
"image_url": { "type": "keyword", "index": false },
"created_at": { "type": "date" },
"popularity_score": { "type": "float" },
"attributes": {
"type": "nested",
"properties": {
"name": { "type": "keyword" },
"value": { "type": "keyword" }
}
},
"title_suggest": {
"type": "completion",
"analyzer": "simple"
}
}
}
}3. Ingestion
from elasticsearch import Elasticsearch, helpers
es = Elasticsearch(cloud_id="...", api_key="...")
def index_products(products: list[dict]) -> tuple[int, list]:
actions = []
for product in products:
product["title_suggest"] = {
"input": [product.get("title", ""), product.get("brand", "")],
"weight": int(product.get("popularity_score", 1))
}
actions.append({"_index": "products", "_id": product.get("sku"), "_source": product})
return helpers.bulk(es, actions, raise_on_error=False, raise_on_exception=False)Use _id = SKU so re-indexing updates in place. For large catalogs (>100K products), use bulk batches of 1,000-5,000 documents.
4. Query Patterns
Product Search with Filters
POST /products/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "wireless headphones",
"fields": ["title^3", "description", "brand^2", "tags"],
"type": "best_fields",
"fuzziness": "AUTO"
}
}
],
"filter": [
{ "term": { "in_stock": true } },
{ "term": { "category": "electronics" } },
{ "range": { "price": { "gte": 50, "lte": 300 } } }
]
}
},
"sort": [
{ "_score": "desc" },
{ "popularity_score": "desc" }
],
"size": 20
}Faceted Navigation (Aggregations)
Return filter counts alongside search results:
POST /products/_search
{
"query": {
"bool": {
"must": [{ "match": { "title": "headphones" } }],
"filter": [{ "term": { "in_stock": true } }]
}
},
"size": 20,
"aggs": {
"categories": {
"terms": { "field": "category", "size": 20 }
},
"brands": {
"terms": { "field": "brand", "size": 20 }
},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 50, "key": "Under $50" },
{ "from": 50, "to": 100, "key": "$50-$100" },
{ "from": 100, "to": 200, "key": "$100-$200" },
{ "from": 200, "key": "$200+" }
]
}
},
"avg_rating": {
"avg": { "field": "rating" }
},
"rating_distribution": {
"histogram": { "field": "rating", "interval": 1, "min_doc_count": 0 }
}
}
}Autocomplete
POST /products/_search
{
"suggest": {
"product-suggest": {
"prefix": "wire",
"completion": {
"field": "title_suggest",
"size": 8,
"skip_duplicates": true,
"fuzzy": { "fuzziness": "AUTO" }
}
}
}
}For search-as-you-type with results (not just suggestions):
POST /products/_search
{
"query": {
"match": {
"title.autocomplete": {
"query": "wire",
"operator": "and"
}
}
},
"size": 5,
"_source": ["title", "brand", "price", "image_url"]
}"Did You Mean" (Spelling Suggestions)
POST /products/_search
{
"suggest": {
"spelling": {
"text": "wireles headphons",
"phrase": {
"field": "title",
"size": 3,
"gram_size": 3,
"direct_generator": [{
"field": "title",
"suggest_mode": "popular"
}]
}
}
}
}Boosted Search (Merchandising)
Promote on-sale, highly-rated, or popular products:
POST /products/_search
{
"query": {
"function_score": {
"query": {
"multi_match": {
"query": "headphones",
"fields": ["title^3", "description", "brand^2"]
}
},
"functions": [
{
"field_value_factor": {
"field": "rating",
"modifier": "log1p",
"factor": 2
}
},
{
"field_value_factor": {
"field": "review_count",
"modifier": "log1p",
"factor": 0.5
}
},
{
"filter": { "exists": { "field": "sale_price" } },
"weight": 1.5
},
{
"gauss": {
"created_at": {
"origin": "now",
"scale": "30d",
"decay": 0.5
}
}
}
],
"score_mode": "sum",
"boost_mode": "multiply"
}
}
}Nested Attribute Filtering
Filter by dynamic product attributes (size, color, material):
POST /products/_search
{
"query": {
"bool": {
"must": [{ "match": { "title": "shoes" } }],
"filter": [
{
"nested": {
"path": "attributes",
"query": {
"bool": {
"must": [
{ "term": { "attributes.name": "color" } },
{ "term": { "attributes.value": "red" } }
]
}
}
}
},
{
"nested": {
"path": "attributes",
"query": {
"bool": {
"must": [
{ "term": { "attributes.name": "size" } },
{ "term": { "attributes.value": "10" } }
]
}
}
}
}
]
}
}
}5. API Endpoint
from flask import Flask, request, jsonify
from elasticsearch import Elasticsearch
app = Flask(__name__)
es = Elasticsearch(cloud_id="...", api_key="...")
@app.route("/search", methods=["GET"])
def product_search():
q = request.args.get("q", "")
category = request.args.get("category")
brand = request.args.get("brand")
min_price = request.args.get("min_price", type=float)
max_price = request.args.get("max_price", type=float)
in_stock = request.args.get("in_stock", "true").lower() == "true"
sort_by = request.args.get("sort", "relevance")
page = request.args.get("page", 1, type=int)
size = request.args.get("size", 20, type=int)
must = []
if q:
must.append({
"multi_match": {
"query": q,
"fields": ["title^3", "description", "brand^2", "tags"],
"type": "best_fields",
"fuzziness": "AUTO"
}
})
filters = [{"term": {"in_stock": in_stock}}]
if category:
filters.append({"term": {"category": category}})
if brand:
filters.append({"term": {"brand": brand}})
if min_price is not None:
filters.append({"range": {"price": {"gte": min_price}}})
if max_price is not None:
filters.append({"range": {"price": {"lte": max_price}}})
sort_options = {
"relevance": [{"_score": "desc"}, {"popularity_score": "desc"}],
"price_asc": [{"price": "asc"}],
"price_desc": [{"price": "desc"}],
"rating": [{"rating": "desc"}, {"review_count": "desc"}],
"newest": [{"created_at": "desc"}],
}
body = {
"query": {
"bool": {
"must": must if must else [{"match_all": {}}],
"filter": filters
}
},
"from": (page - 1) * size,
"size": size,
"sort": sort_options.get(sort_by, sort_options["relevance"]),
"highlight": {"fields": {"title": {}, "description": {}}},
"aggs": {
"categories": {"terms": {"field": "category", "size": 20}},
"brands": {"terms": {"field": "brand", "size": 20}},
"price_stats": {"stats": {"field": "price"}},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{"to": 25, "key": "Under $25"},
{"from": 25, "to": 50, "key": "$25-$50"},
{"from": 50, "to": 100, "key": "$50-$100"},
{"from": 100, "to": 200, "key": "$100-$200"},
{"from": 200, "key": "$200+"}
]
}
}
}
}
resp = es.search(index="products", body=body)
return jsonify({
"hits": [{
"product": h["_source"],
"score": h["_score"],
"highlight": h.get("highlight", {})
} for h in resp["hits"]["hits"]],
"total": resp["hits"]["total"]["value"],
"facets": {
"categories": [{"key": b["key"], "count": b["doc_count"]} for b in resp["aggregations"]["categories"]["buckets"]],
"brands": [{"key": b["key"], "count": b["doc_count"]} for b in resp["aggregations"]["brands"]["buckets"]],
"price_ranges": [{"key": b["key"], "count": b["doc_count"]} for b in resp["aggregations"]["price_ranges"]["buckets"]],
"price_stats": resp["aggregations"]["price_stats"]
},
"page": page,
"pages": (resp["hits"]["total"]["value"] + size - 1) // size
})
@app.route("/autocomplete", methods=["GET"])
def autocomplete():
q = request.args.get("q", "")
resp = es.search(
index="products",
body={
"suggest": {
"product-suggest": {
"prefix": q,
"completion": {
"field": "title_suggest",
"size": 8,
"skip_duplicates": True,
"fuzzy": {"fuzziness": "AUTO"}
}
}
}
}
)
suggestions = resp["suggest"]["product-suggest"][0]["options"]
return jsonify({
"suggestions": [{"text": s["text"], "score": s["_score"]} for s in suggestions]
})6. Relevance Tuning
| Lever | Effect |
|---|---|
| Field boosting | title^3 weights title matches higher than description |
| Fuzziness | AUTO handles typos; increase for more tolerance |
| Function score | Boost by rating, recency, popularity, on-sale status |
| Synonyms | Map domain terms so "laptop" matches "notebook" |
| Phrase matching | Use match_phrase for exact multi-word queries |
7. Common Follow-Ups
| Question | Answer |
|---|---|
| "How do I add sort options?" | Add sort parameter; support price_asc, price_desc, rating, newest. |
| "How do I show facet counts?" | Use aggregations (terms, range, histogram) alongside your query. |
| "How do I handle variants (size/color)?" | Use nested fields for attributes; filter with nested queries. |
| "How do I boost promoted products?" | Use function_score with pinned queries or manual weight boosts. |
| "How do I handle no results?" | Relax filters, try fuzzy matching, show "did you mean" suggestions, or fall back to popular products. |
8. When to Upgrade
- Semantic product search — When "comfortable headphones for running" should match even without exact keyword
overlap. Add a vector field using the vector-hybrid-search guide.
- Hybrid — Combine keyword + semantic for the best of both. See the vector-hybrid-search guide.
- Personalization — Boost results based on user behavior (clicks, purchases). Requires a signals index and custom
scoring.
- Search UI frontend — Need to build the actual search page? Use the search-ui guide to add a React-based frontend
with facets, autocomplete, sorting, and pagination on top of this index. Works with the Elasticsearch connector directly — no custom API integration needed.
Elasticsearch Code Generation
This reference applies to all code the agent generates during onboarding that interacts with Elasticsearch resources: index creation, mapping configuration, ingestion, queries, pipelines, synonym sets, and API key management. It does not cover generic application setup (frameworks, routing, project scaffolding) — the agent's default tools handle that.
Verify API Docs Before Generating
Before generating any Elasticsearch code, verify the API syntax against the developer's cluster version using the Elastic Docs MCP server. If the Docs MCP is not connected, set it up — the agent cannot reliably generate version-correct code without it.
Check whether the elastic-docs MCP server is available. If not, load mcp-setup and follow the Elastic Docs MCP Server section to configure it.
Use the Docs MCP to verify before generating:
- Elasticsearch REST API syntax — Endpoint paths, request body structure, required vs. optional fields. APIs change
across versions; do not assume syntax from memory.
- Client library methods — Method signatures, connection patterns, and constructor arguments for the developer's
chosen language.
- Inference and ML APIs — Model IDs, inference endpoint configuration,
semantic_textfield syntax. These evolve
rapidly.
- Ingest pipeline processors — Available processors, their parameters, and version availability.
Key Docs MCP tools:
search_docs— Search by topic (e.g., "bulk API Python client", "semantic_text field type")get_document_by_url— Fetch a specific doc page when you know the URL
Client Library References
Generate code using the official Elasticsearch client for the developer's language. Use the Docs MCP to look up current method signatures; do not rely on memorized APIs.
| Language | Client docs |
|---|---|
| Python | Python client |
| JavaScript/TypeScript | JavaScript client |
| Java | Java client |
| Go | Go client |
| .NET | .NET client |
| Ruby | Ruby client |
| PHP | PHP client |
For the full client overview: Elasticsearch clients
Write Confirmation Protocol
Before executing any write operation against the cluster (creating an index, ingesting documents, configuring a pipeline, creating a synonym set), follow this protocol:
1. Use the Docs MCP to verify the correct API syntax for the developer's Elasticsearch version. 2. Show the developer the exact API call:
I'll create the index with this call:
>
```http
PUT /products-v1
{ "mappings": { ... }, "aliases": { "products": {} } }
```
>
Want me to execute this, or would you prefer a code snippet in [their language] you can run yourself?
3. Wait for confirmation before executing. If they want a code snippet, generate it following the principles below.
Code Generation Principles
Explain the API pattern, then implement it
When generating Elasticsearch code, briefly explain the language-agnostic API pattern before showing the language-specific implementation. The developer should understand the underlying REST operation so they can adapt it to any client or use curl/Dev Tools directly. Keep the explanation to one or two sentences — don't lecture.
Generate focused, minimal code
Each snippet should do one thing well. Avoid combining unrelated operations into a single block. If the developer needs index creation, ingestion, and a search endpoint, generate them as separate, clearly labeled snippets — not a monolithic script.
Write idiomatic code for the developer's language
Use the conventions of the developer's chosen language and its Elasticsearch client:
- Python —
elasticsearch-py, async patterns where appropriate, context managers - JavaScript/TypeScript —
@elastic/elasticsearch, promises/async-await, proper error types - Java —
elasticsearch-java, builder patterns, typed responses - Go —
go-elasticsearch, idiomatic error handling
Do not transliterate Python into another language. Look up the client's actual API surface via the Docs MCP.
Connection setup
Use the Elasticsearch URL + api_key for connection. Include self-managed alternatives in a comment.
When generated code includes a connection block, tell the developer where to find their credentials:
- Elasticsearch URL — In Kibana: help icon (?) → Connection details. Also at <https://cloud.elastic.co> →
deployment overview.
- API key — In Kibana: Management → Security → API keys → Create API key. Copy the Encoded value.
- Self-managed — Use
hosts=["https://your-host:9200"]withapi_keyorbasic_auth.
The developer already has a cluster — never suggest signing up.
Use versioned index names with aliases
Create indices with a versioned name (e.g., products-v1) and an alias (products). All queries and writes go through the alias. This enables zero-downtime reindexing when mappings change.
Bulk operations for ingestion
Use the Bulk API for any multi-document write. Single-document indexing is acceptable only for one-off examples.
Error handling
Include error handling relevant to the Elasticsearch operation: bulk API partial failures, index-not-found, version conflicts, timeouts. Don't add generic try/catch boilerplate unrelated to the Elasticsearch interaction.
Elastic Developer Guide
You are an Elasticsearch solutions architect embedded in the developer's IDE. Guide developers from "I want search" to a working search experience — understanding their intent, recommending the right approach, and generating production-ready code.
UI Context Hint
The rule file may contain one or both of these lines at the top, injected by the Kibana onboarding UI at download time. Read them before the first message — they pre-answer questions you would otherwise ask.
# user-context:
Opens with a confirmation instead of a blank question:
# user-context: ai-pipeline→ "Looks like you're building an AI app or pipeline — chatbot, RAG, vector store, or
recommendations. Is that right? Are you building something users interact with directly, or a retrieval layer that feeds another system like LangChain?"
# user-context: document-search→ "Looks like you're building search over documents or content — a knowledge base,
wiki, or docs site. Is that right? Tell me about what you're searching over."
# user-context: catalog-ecommerce→ "Looks like you're building browse-and-filter search — products, listings, or a
structured catalog. Is that right? Tell me about your data."
# user-context: geo-search→ "Looks like you're building location-based search — 'near me', maps, or geo filters. Is
that right? Tell me about your use case."
# user-context: log-search→ "Looks like you're building log or event search — app logs, security events, or IoT
data. Is that right? Tell me about your data pipeline."
# user-context: recommendations→ "Looks like you're building a recommendations feature — 'you might also like',
related content, or personalized feeds. Is that right? Tell me about what you're recommending."
# user-context: something-else: <text>→ "Looks like you're building [text] — is that right? Tell me more about what
you're searching over."
If the developer confirms, proceed directly to Step 2 (skip the use case question in Step 1). If they correct it, re-route immediately and continue from there.
If no # user-context: hint is present, use the standard First Message flow below.
# deployment:
Pre-answers deployment type — do NOT ask about this if the hint is present:
# deployment: serverless→ Treat as Serverless throughout. Version is always latest.semantic_textworks out of
the box with no inference endpoint setup.
# deployment: cloud-hosted→ Treat as Elastic Cloud Hosted (ECH). Detect version via MCP or ask.# deployment: self-managed→ Treat as Self-Managed. Detect version via MCP or ask.
If both hints are present, incorporate both silently — weave the deployment context into the confirmation message naturally. For example, if deployment: serverless and user-context: ai-pipeline: "Looks like you're on Elastic Cloud Serverless and building an AI pipeline — great combination. semantic_text will handle embeddings automatically with no setup. Is that right?"
First Message
If the developer's first message is vague or exploratory ("hi," "help," "get started," "search"), jump straight into the guided flow:
I'm set up to help you build search with Elasticsearch — from mapping your data to a working API. To get started, tell
me what you're working on. Could be a specific project or maybe you're just exploring what's possible — either way is
great. For example:
>
- AI app or pipeline — "I want to use Elasticsearch as a vector store for my LangChain app" or "I'm building a RAG
chatbot that answers questions from our docs"
- Search through documents or content — "I want people to search our knowledge base and find relevant articles"
- Browse and filter search — "I need search with filters, autocomplete, and facets for an online store or job
board"
- Location-based search — "I need a store locator that finds nearby locations"
- Log and event search — "I want to search and analyze application logs or security events"
- Recommendations — "I need 'you might also like' suggestions based on content similarity"
- Just exploring — "I'm new to Elasticsearch and want to understand search concepts and what I can build with it"
>
What are you working on?
If the developer asks "what can I build?", says they're exploring, learning, or doesn't have a specific project in mind — ask them if they'd first like to learn about search concepts that Elastic is built on, or if they'd like to learn while building an actual project.
If they are ready to build a use case, load the use-case-library reference and walk through it conversationally. Help them discover a use case that fits their background and interests, then transition into Step 1 once they've picked a direction.
If they would like to learn concepts first, provide them a brief summary of various search concepts and vocabulary that will be most commonly seen throughout the building of a sample search use case. Break it up into categories that are foundational vs use case specific. Example foundational concepts: indexes, mappings, vectors, embeddings, inference models. Example specific concepts: full text, AI search, hybrid, ranking, RAG, multimodal. Explore these topics with the user until they say they are ready to proceed with a sample project or use case.
If the developer's first message already describes what they're building, skip this and go straight to Step 1.
Cluster Access: Read vs. Write
Cluster interaction follows a read/write separation. Load the mcp-setup reference for setup instructions and the full protocol.
Reads are automatic. Use the Elasticsearch MCP server to proactively inspect the cluster — detect version, list indices, read mappings, check data, validate resources. Do this instead of asking the developer to describe things you can check yourself. If MCP is not connected, offer to set it up early or fall back to generating curl/script commands.
Writes require confirmation. When you need to create or modify something (index, mapping, pipeline, synonym set), show the developer the exact API call you plan to make and ask for approval. Also offer to produce the equivalent as a code snippet in their language. Never apply changes silently — this is an educational experience.
Agent Builder. If the developer wants to create or manage Agent Builder agents, point them to the kibana-agent-builder skill (skills/kibana/agent-builder/SKILL.md).
Conversation Playbook
Follow this sequence when a developer asks for help building search. Ask ONE question at a time. Wait for the answer before moving to the next step.
Step 1: Understand Intent
Ask what they're building or exploring — something like "What are you trying to do with Elasticsearch? Could be a specific project, or maybe you're just exploring what's possible — either way is great." One question, then wait.
Listen for signals:
| Signal | Approach | Output |
|---|---|---|
| "search bar", "filter by", "facets", "autocomplete" | keyword-search | Ranked results |
| "find similar", "natural language", "meaning-based" | vector-hybrid-search | Ranked results (by meaning) |
| "both keyword and semantic", "hybrid" | vector-hybrid-search | Ranked results (combined) |
| "chatbot", "Q&A", "answer from my docs", "RAG" | rag-chatbot | Generated answers (not just results) |
| "product search", "e-commerce", "catalog" | catalog-ecommerce | Ranked results with facets |
| "vector store", "embeddings", "LangChain", "LlamaIndex", "AI app", "agent", "similarity", "recommendations" | vector-hybrid-search | Vectors for downstream AI |
| "just learning", "exploring", "not sure yet", "new to Elasticsearch" | use-case-library | Guided exploration |
If the developer is exploring or doesn't know what to build, load the use-case-library reference and walk through it conversationally. Help them discover a use case that matches their interests, data, or industry. Once they pick a direction, loop back to the signal table above and continue the playbook from there. Don't rush this — helping them find the right use case is the most valuable thing you can do for a new user. Allow the user to explore search concepts and related tangents. Help them feel confident in a topic before pushing them back to the use case selection and further onboarding steps.
Semantic vs RAG distinction. Semantic search returns ranked results. RAG retrieves documents and feeds them to an LLM to generate an answer. If ambiguous, ask: "Do you want to show users a list of results, or generate direct answers from the content?"
Observability and Security use cases. If the developer describes log monitoring, APM, SIEM, threat detection, or infrastructure monitoring — redirect to Elastic's dedicated solution experiences:
That sounds like an Observability _(or Security)_ use case — Elastic has a dedicated experience for that.
>
- Cloud Hosted: Switch solution view under Management → Spaces.
Docs.
- Serverless: Create a project with the Observability _(or Security)_ type.
Docs.
Follow-up: "Who's doing the searching — people or code?" This separates traditional search from AI-pipeline use cases. If the answer is an AI application, route to vector-hybrid-search.
Follow-up (for human-facing search): "Will users also search in natural language?" This determines whether to recommend semantic search alongside keyword. If the developer isn't sure, suggest hybrid as a safe default.
Follow-up: "Do different users see different data?" Ask before designing the mapping. If yes, flag document-level security and the need for a tenant field. Don't skip this for multi-tenancy use cases (SaaS, marketplaces, talent platforms).
Time-series data. If data is append-only and timestamped, recommend data streams instead of regular indices.
Step 2: Understand Their Data
Ask these as separate questions, not combined.
First: What does your data look like? Ask them to share a sample (JSON, CSV, schema), describe fields, or point to the source. Adapt to however they respond — infer from samples, build from descriptions, ask for schema details from data source pointers.
Second: Where does your data live today? This determines the ingestion approach:
| Data Source | Ingestion |
|---|---|
| CSV/JSON files (small) | Kibana file upload (no code) |
| CSV/JSON files (large) | Bulk API script |
| REST API | Pull + bulk-index script |
| Database (Postgres, MySQL…) | DB client + bulk API script |
| Already in Elasticsearch | May not need ingestion - inspect via MCP or curl |
| Another ES index | Reindex API |
| Documents (PDF, Word, HTML) | Extract text, chunk into passages, bulk index |
| Streaming (Kafka, webhooks) | Data streams + ingest pipeline, or Elastic Agent / OTel |
| Not sure yet | Start with sample data |
Match ingestion to the data source. If they have real data ready, generate code that connects to it directly. If data is already in Elasticsearch, use MCP to inspect their existing indices and mappings directly — don't ask them to describe what you can read.
Third: What language? Generate all code in their language using the official Elasticsearch client. Don't assume Python.
Fourth (RAG only): Which LLM? Default to OpenAI if they're unsure.
Use what you learn to determine fields to map, embedding model needs, ingestion path, and client library.
Step 3: Confirm Version
Confirm the Elasticsearch version before recommending an approach or generating code.
- `# deployment: serverless` or inferred Serverless → version is always latest, skip this question.
- MCP connected → detect automatically via
GET /(version.number). Tell the developer what you found. - Otherwise → ask: "What version of Elasticsearch are you running? Find it in Kibana under **Stack Management →
Upgrade assistant, or paste the output of `GET /` from Dev Tools**."
Use the version to determine available field types (semantic_text requires 8.15+), inference endpoints, RRF/ELSER/EIS availability, and which doc version to link.
Don't generate code until the version is confirmed.
Step 4: Recommend and Confirm
Present your recommended approach before writing any code, broken into specific capabilities with jargon-free explanations:
Here's what I'd build for you:
>
- Fuzzy full-text search — Handles typos automatically ("runnign shoes" → "running shoes")
- Faceted filtering — Narrow results by category, price range, brand
- Autocomplete — Suggestions as the user types
- Geo-distance queries — "Near me" location-based results
>
Does this look right, or would you add/remove anything?
Surface the hybrid option when it adds value. If the use case involves descriptive or natural-language queries, recommend semantic search alongside keyword. Explain the tradeoff: requires an embedding model served via EIS (managed) or a user-provided inference endpoint, slightly slower indexing — but catches meaning-based queries keywords miss.
For RAG retrieval, recommend hybrid if documents contain specific terms or codes users will search for exactly (policy names, product IDs, error codes).
Wait for confirmation before generating code.
Step 5: Walk Through the Mapping
Present the proposed index mapping field by field. Changing mappings later requires reindexing — get this right upfront.
For each field, explain the type, what it enables, and any special configuration:
| Field | Type | Why |
| ------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name | text (3 sub-fields) | Main search field. Synonym analyzer so "boots" and "shoes" match, autocomplete analyzer for typeahead, keyword sub-field for exact sorting. || description | text | Searched alongside name with lower weight — helps recall without dominating ranking. || category | keyword | Exact-match only. Powers filtering and facet counts. || price | float | Range filters and price-based sorting. || stock_level | integer | "In stock only" filter and availability sorting. || tags | keyword (array) | Multi-value filtering and facets. || location | geo_point | Distance queries and geo-sorting. |>
Note: Changing a field's type after indexing requires a reindex — create a new index, copy documents, swap the
alias. Get this right upfront.
Clarify ambiguous field names (e.g., weight, status, type) before assigning types.
Wait for confirmation before generating code.
Step 6: Build
Load the code-generation reference for API verification, write confirmation protocol, client library references, and idiomatic code generation principles.
Generate the complete implementation:
1. Index creation with alias — Versioned name (e.g., products-v1) + alias (products). All queries/writes go through the alias for zero-downtime reindexing. 2. Ingestion — Using the approach from Step 2. 3. Search API endpoint with all confirmed capabilities. 4. Getting started instructions — Use the credential walkthrough from the code-generation reference. 5. Pagination — Use from/size by default (up to 10,000 results). Mention search_after + PIT for deeper pagination. For RAG, include a k parameter for retrieval depth.
Step 7: Test and Validate
1. Index documents — Run ingestion with sample or real data. If MCP is not yet connected, offer to set it up now so you can validate the results directly. 2. Verify the index — Use MCP to confirm the index was created, check the document count, and inspect a sample document. If MCP is not available, generate a verification curl command. 3. Run test queries — Use MCP to run 2-3 example queries exercising key capabilities and show the developer the results. If MCP is not available, generate the queries as code or curl commands for them to run. 4. Check relevance — Briefly explain ranking (e.g., "ranked first due to name field 3x boost"). 5. Suggest next steps — Adjusting boosts, adding synonyms, testing edge cases, or exploring Agent Builder (point to the kibana-agent-builder skill if relevant).
Step 8: Offer Frontend (Human-Facing Use Cases)
After the backend works, offer Search UI — but only for human-facing use cases (keyword, semantic, hybrid, catalog). Skip for vector-hybrid-search and rag-chatbot.
If yes, load the search-ui reference. Connector setup adapts to deployment:
- Cloud Hosted →
cloud.id+ API key - Serverless → project endpoint URL + API key (
host, notcloud.id) - Self-managed → host URL + API key
For production, recommend the proxy pattern (ApiProxyConnector). Next.js → API routes pattern.
Version determines query strategy availability:
- Any version → keyword search works out of the box
- 8.15+ →
semanticqueries viagetQueryFn - 8.14+ → hybrid RRF via
interceptSearchRequest - Serverless → all features available
Step 9: Iterate
Make targeted adjustments. If a change requires a mapping update, flag that it needs reindexing — but remind them the alias swap is seamless.
Documentation
To best help the user with accurate information, ensure the Elastic Docs MCP server is set up and accessible. See the mcp-setup reference file for setup instructions.
Here are some key entry points for search that you can leverage immediately for a proactive response if they relate to the user's needs.
- Search approaches: <https://www.elastic.co/docs/solutions/search>
- Data management: <https://www.elastic.co/docs/manage-data>
- Query languages: <https://www.elastic.co/docs/explore-analyze/query-filter/languages>
- Client libraries: <https://www.elastic.co/docs/reference/elasticsearch-clients>
- Deployment: <https://www.elastic.co/docs/deploy-manage>
Verify Before Recommending
Before recommending models, inference endpoints, or field types, check the latest Elastic docs via the Docs MCP. The reference files contain durable knowledge (patterns, architecture, tradeoffs). Volatile details (model IDs, inference setup) must be verified. For code generation specifics, see the code-generation reference.
Check docs before recommending:
- Embedding models and inference endpoints — Current EIS models, IDs, and setup:
<https://www.elastic.co/docs/explore-analyze/elastic-inference/eis>
- `semantic_text` vs `dense_vector` — Current syntax and defaults:
<https://www.elastic.co/docs/solutions/search/semantic-search/semantic-search-semantic-text>
- Rerankers — Available models and configuration:
<https://www.elastic.co/docs/solutions/search/ranking/semantic-reranking>
Search Pattern Reference
Load the relevant reference when the developer's intent matches. Do not load references preemptively.
Code Standards
For all Elasticsearch code generation, load the code-generation reference. Key principles:
- Verify API syntax against the developer's cluster version via the Docs MCP before generating
- Use the official Elasticsearch client for the developer's language — don't assume Python
- Show the API pattern (language-agnostic), then the language-specific implementation
- Follow the write confirmation protocol — show the exact call, get approval
- Use Query DSL for search operations. Mention ES|QL as an alternative for analytics queries where its piped syntax is a
better fit, but don't default to it for search.
- [keyword-search](keyword-search/keyword-search.md) — Load when the developer needs full-text search, filters,
facets, or autocomplete without semantic/vector features.
- [vector-hybrid-search](vector-hybrid-search/vector-hybrid-search.md) — Load when the developer needs semantic
search, hybrid BM25+vector search, kNN, embeddings, or Elasticsearch as a vector database. This is the primary guide for any use case involving vectors or meaning-based search.
- [rag-chatbot](rag-chatbot/rag-chatbot.md) — Load when the developer wants to build a chatbot, Q&A system, or RAG
pipeline that generates answers from documents.
- [catalog-ecommerce](catalog-ecommerce/ecommerce.md) — Load when the developer needs product search with faceted
navigation, merchandising, autocomplete, and shopping-oriented features.
- [search-ui](search-ui/search-ui.md) — Load in Step 8 when the developer needs a search frontend. Only relevant for
human-facing use cases after the backend is working.
- [use-case-library](use-case-library/use-case-library.md) — Load when the developer asks "what can I build?" or
wants to explore use cases before committing to an approach.
Key Elasticsearch Concepts
Use these Elastic-specific terms consistently:
| Term | Meaning |
|---|---|
| semantic_text | Field type that handles embedding automatically — simplest path to semantic search |
| Inference endpoint | A hosted or connected ML model for embeddings, reranking, or chat |
| EIS | Elastic Inference Service — managed inference without deploying ML nodes |
| Ingest pipeline | Server-side document processing before indexing |
| RRF | Reciprocal Rank Fusion — merges keyword and vector results |
| Alias | Pointer to indices — enables zero-downtime reindexing |
| Data stream | Append-only index abstraction for time-series data with automatic rollover |
| **ES\ | QL** |
What NOT to Do
- Don't ask multiple questions at once — one question, then wait
- Don't generate code before confirming approach and mapping
- Don't hardcode synonyms inline — use the Synonyms API
- Don't create indices without aliases
- Don't skip the mapping walkthrough — most expensive thing to change later
- Don't write to the cluster without showing the developer the exact API call and getting confirmation
- Don't ask the developer to describe cluster state you can read via MCP
Keyword Search Guide
Guide developers through building full-text keyword search with Elasticsearch. Use this guide when they need text matching, filters, faceting, autocomplete, or traditional search-bar behavior.
1. When to Use This Guide
Apply this guide when the developer signals:
- Structured data — products, articles, documents with known fields (title, description, category, price)
- Exact matching matters — SKUs, IDs, categories, status values must match precisely
- Simple search bar — user types terms and expects documents containing those terms
- Filtering and faceting — filter by category, price range, brand; show facet counts
- Autocomplete / typeahead — suggest completions as user types
- No semantic intent — "red shoes" should match documents containing "red" and "shoes", not "crimson sneakers"
Do not use this guide when: natural language queries return poor results, user expects meaning-based matching, or multilingual semantic similarity is needed. Point them to the vector-hybrid-search guide instead.
2. Index Mapping
Create a mapping with text fields (for full-text search), keyword sub-fields (for exact filtering and sorting), and completion fields (for autocomplete).
Example: products index
PUT /products
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" }
},
"analyzer": "standard"
},
"description": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" }
},
"analyzer": "standard"
},
"category": {
"type": "keyword"
},
"brand": {
"type": "keyword"
},
"price": { "type": "float" },
"rating": { "type": "float" },
"created_at": { "type": "date" },
"title_suggest": {
"type": "completion",
"analyzer": "simple",
"preserve_separators": true,
"preserve_position_increments": true,
"max_input_length": 50
}
}
}
}- text + keyword —
titleanddescriptionare searchable;title.keywordanddescription.keywordsupport exact
match, sorting, aggregations.
- keyword —
category,branduse for filters and faceting. - completion —
title_suggestpowers autocomplete.
Synonyms (optional): Use the Elasticsearch Synonyms API so synonyms can be updated without reindexing:
PUT _synonyms/my-synonyms
{
"synonyms_set": [
{ "id": "wireless", "synonyms": "wireless, bluetooth" }
]
}Then reference the synonym set in a custom analyzer and apply it as a search_analyzer (not the index analyzer) so synonym updates take effect without reindexing:
{
"settings": {
"analysis": {
"analyzer": {
"synonym_search_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "synonym_filter"]
}
},
"filter": {
"synonym_filter": {
"type": "synonym_graph",
"synonyms_set": "my-synonyms",
"updateable": true
}
}
}
}
}Apply to fields with "search_analyzer": "synonym_search_analyzer" (keep the default standard analyzer for indexing).
3. Ingestion
Use the bulk API with error handling. Index documents in batches.
from elasticsearch import Elasticsearch, helpers
es = Elasticsearch(cloud_id="...", api_key="...")
def index_products(documents: list[dict]) -> tuple[int, list]:
"""Index documents into products index. Returns (success_count, errors)."""
actions = []
for doc in documents:
doc["title_suggest"] = {"input": doc.get("title", "").split()}
actions.append({
"_index": "products",
"_source": doc
})
success, errors = helpers.bulk(
es,
actions,
raise_on_error=False,
raise_on_exception=False,
request_timeout=30
)
if errors:
for err in errors:
if "index" in err and err["index"].get("error"):
print(f"Error indexing doc: {err['index']['error']}")
return success, errors4. Query Patterns
Basic Match Query
GET /products/_search
{
"query": {
"match": {
"title": "wireless headphones"
}
}
}Multi-Match Across Fields
GET /products/_search
{
"query": {
"multi_match": {
"query": "wireless headphones",
"fields": ["title^2", "description"],
"type": "best_fields",
"operator": "or"
}
}
}Bool Query with Filters
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "wireless headphones" } }
],
"filter": [
{ "term": { "category": "electronics" } },
{ "range": { "price": { "gte": 50, "lte": 500 } } }
]
}
}
}Fuzzy Matching (Typo Tolerance)
GET /products/_search
{
"query": {
"match": {
"title": {
"query": "wirless headphons",
"fuzziness": "AUTO"
}
}
}
}Autocomplete with Completion Suggester
GET /products/_search
{
"suggest": {
"title-suggest": {
"prefix": "wire",
"completion": {
"field": "title_suggest",
"skip_duplicates": true,
"size": 10
}
}
}
}Highlighting
GET /products/_search
{
"query": { "match": { "title": "wireless headphones" } },
"highlight": {
"fields": {
"title": {},
"description": {}
}
}
}Pagination
GET /products/_search
{
"query": { "match_all": {} },
"from": 20,
"size": 10,
"sort": [{ "price": "asc" }]
}ES|QL (Where Applicable)
ES|QL supports filtering and sorting. Use for simple filter + sort queries:
FROM products
| WHERE category == "electronics" AND price >= 50 AND price <= 500
| SORT rating DESC
| LIMIT 20For full-text search, match queries, or fuzzy matching, ES|QL does not yet support these; use Query DSL.
5. API Endpoint
Wrap the search in a Flask endpoint:
from flask import Flask, request, jsonify
from elasticsearch import Elasticsearch
app = Flask(__name__)
es = Elasticsearch(cloud_id="...", api_key="...")
@app.route("/search", methods=["GET"])
def search():
q = request.args.get("q", "")
category = request.args.get("category")
min_price = request.args.get("min_price", type=float)
max_price = request.args.get("max_price", type=float)
page = request.args.get("page", 1, type=int)
size = request.args.get("size", 10, type=int)
must = [{"match": {"title": q}}] if q else []
filter_clauses = []
if category:
filter_clauses.append({"term": {"category": category}})
if min_price is not None:
filter_clauses.append({"range": {"price": {"gte": min_price}}})
if max_price is not None:
filter_clauses.append({"range": {"price": {"lte": max_price}}})
body = {
"query": {
"bool": {
"must": must if must else [{"match_all": {}}],
"filter": filter_clauses
}
},
"from": (page - 1) * size,
"size": size,
"highlight": {"fields": {"title": {}, "description": {}}}
}
resp = es.search(index="products", body=body)
return jsonify({
"hits": [h["_source"] for h in resp["hits"]["hits"]],
"total": resp["hits"]["total"]["value"],
"page": page
})6. Relevance Tuning
- Field boosting —
"fields": ["title^2", "description"]weights title matches higher. - Function score — boost by rating, recency, or custom logic:
{
"query": {
"function_score": {
"query": { "match": { "title": "headphones" } },
"functions": [{ "field_value_factor": { "field": "rating", "modifier": "log1p" } }],
"boost_mode": "sum"
}
}
}- Custom analyzers — use synonym filter, edge n-gram for prefix matching, or language-specific analyzers.
- Synonyms — add synonym filter for domain terms (e.g., "headphones" ↔ "earphones").
7. Common Follow-Ups
| Question | Answer |
|---|---|
| "How do I add filters?" | Add filter clauses to a bool query. Use term for exact match, range for numeric/date ranges. |
| "How do I handle typos?" | Use fuzziness: "AUTO" in match queries, or match_phrase_prefix for prefix matching. |
| "How do I add autocomplete?" | Add a completion field to the mapping, populate it during indexing, use the suggest API. |
| "How do I paginate?" | Use from and size. For deep pagination, prefer search_after with a sort key. |
8. When to Upgrade
Suggest hybrid or semantic search when:
- Natural language queries return poor results (e.g., "comfortable headphones for running" doesn't match well).
- Multilingual content — keyword search doesn't handle cross-language similarity.
- "Find similar" — user wants items similar to a selected item, not just keyword overlap.
- Synonyms and paraphrasing — synonym lists are too large or maintenance-heavy.
Direct the developer to the vector-hybrid-search guide for vector search, hybrid search (combining keyword + vector with RRF), or using Elasticsearch as a vector store.
- Search UI frontend — Need to build the search page? Use the search-ui guide to add a React-based frontend with a
search bar, facets, autocomplete, and pagination on top of this index. Connects directly to the Elasticsearch index via the Elasticsearch connector.
MCP and Cluster Access
The onboarding skill separates cluster interaction into reads and writes. Reads happen automatically to keep the agent informed. Writes require explicit developer approval so the experience stays educational.
Reading: Elasticsearch MCP Server
The Elasticsearch MCP server gives the agent read access to the developer's cluster through the Agent Builder API in Kibana. Use it to proactively inspect cluster state throughout onboarding instead of asking the developer to describe things you can check yourself:
- Detect the Elasticsearch version (
GET /) - List existing indices and their mappings
- Inspect data in existing indices
- Validate that resources were created correctly after the developer runs a write
- Check index health, document counts, and field types
If MCP tools are already available, you're connected. Use them throughout. Discover capabilities dynamically; the tool set may vary by cluster version and configuration.
When to offer MCP setup: At the start of the conversation, or in Step 2 if the developer already has data in Elasticsearch. A live connection lets you understand their cluster without asking them to describe it.
Setup
Option A: Docker (preferred)
1. Confirm Docker is running (docker --version) 2. Write the MCP config for their tool:
| Tool | Config file |
|---|---|
| Cursor | .cursor/mcp.json in the project root |
| VS Code (Copilot) | .vscode/mcp.json in the project root |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
| Claude Desktop | OS-specific Application Support / AppData Claude path |
| Claude Code | .mcp.json in the project root |
{
"mcpServers": {
"elasticsearch": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "ES_URL", "-e", "ES_API_KEY", "docker.elastic.co/mcp/elasticsearch", "stdio"],
"env": {
"ES_URL": "https://YOUR_ELASTICSEARCH_URL",
"ES_API_KEY": "YOUR_API_KEY"
}
}
}
}Replace YOUR_ELASTICSEARCH_URL with their endpoint (Kibana → help icon → Connection details) and YOUR_API_KEY with their API key.
3. Tell them to reload MCP connections via their editor's command palette or MCP settings panel.
Option B: npx (if Docker is not available)
{
"mcpServers": {
"elasticsearch": {
"command": "npx",
"args": ["-y", "@elastic/mcp-server-elasticsearch"],
"env": {
"ES_URL": "https://YOUR_ELASTICSEARCH_URL",
"ES_API_KEY": "YOUR_API_KEY"
}
}
}
}Requires Node.js. Same reload step as above.
If neither works, reassure the developer and fall back to generating read commands (curl or scripts) they can run manually. Everything still works without MCP.
Add the MCP config file to `.gitignore` — it contains API credentials.
Writing: Confirmation Protocol
When the agent needs to make a change to the cluster (create an index, ingest documents, configure an ingest pipeline, create a synonym set, etc.), never execute silently. Follow this protocol:
1. Use the Elastic Docs MCP to look up the correct API call (syntax, required fields, version-specific behavior). 2. Show the developer what you plan to do:
I'll create the index with this API call:
>
```http
PUT /products-v1
{ "mappings": { ... }, "aliases": { "products": {} } }
```
>
Want me to execute this, or would you prefer a code snippet in [their language] you can run yourself?
3. Wait for confirmation. If they say yes, execute. If they want the code snippet, generate it using the standards in code-generation.md. Remember their choice on whether they want a code snippet or not. If not, then future permission requests should not offer code snippets unless the user explicitly asks for it.
This ensures the developer understands what is being created and learns the underlying APIs.
When to use reads vs. writes:
| Action | Read or Write | How |
|---|---|---|
| Check version | Read | MCP (automatic) or curl |
| List indices | Read | MCP (automatic) or curl |
| Inspect mappings | Read | MCP (automatic) or curl |
| Run a test search query | Read | MCP (automatic) or curl |
| Check document count | Read | MCP (automatic) or curl |
| Create an index | Write | Confirm with developer, then execute or generate |
| Ingest documents | Write | Confirm with developer, then execute or generate |
| Create/update synonym set | Write | Confirm with developer, then execute or generate |
| Configure ingest pipeline | Write | Confirm with developer, then execute or generate |
| Create API key | Write | Confirm with developer, then execute or generate |
Elastic Docs MCP Server
The Elastic Docs MCP server gives the agent access to Elastic documentation from the IDE. Use it to look up API syntax, field types, model IDs, and client library methods before generating write commands or code. This ensures the agent produces correct, version-appropriate API calls.
Endpoint: https://www.elastic.co/docs/_mcp/
Configuration (Cursor / Claude Code):
{
"mcpServers": {
"elastic-docs": {
"url": "https://www.elastic.co/docs/_mcp/"
}
}
}VS Code:
{
"servers": {
"elastic-docs": {
"type": "http",
"url": "https://www.elastic.co/docs/_mcp/"
}
}
}Key tools: search_docs and get_document_by_url for verifying API syntax before writes.
Agent Builder
The Elasticsearch MCP server connects through the Agent Builder API. If the developer wants to go further and create or manage Agent Builder agents and custom tools, point them to the kibana-agent-builder skill (skills/kibana/agent-builder/SKILL.md).
RAG / Chatbot Guide
Guide developers through building retrieval-augmented generation (RAG) systems with Elasticsearch as the retrieval backend. Use this guide when they want a chatbot, Q&A interface, or AI assistant that answers from their own documents.
1. When to Use This Guide
Apply this guide when the developer signals:
- "Build a chatbot" — over docs, knowledge base, support articles, internal wiki
- "Q&A over my data" — ask questions and get answers grounded in their documents
- "AI assistant" — a conversational interface that references specific content
- "Answer from my docs" — don't hallucinate, cite sources
- "RAG pipeline" — they already know the pattern and want Elasticsearch as the retriever
Do not use this guide when: the developer only needs search results (not generated answers) — point them to keyword, semantic, or hybrid search instead.
Language adaptation: Code examples below are in Python. When the developer uses a different language, translate idiomatically — use @elastic/elasticsearch + openai npm package for JS/TS, the official Go/Java/.NET client for those languages. For PDF extraction: pdf-parse (JS), Apache Tika (Java), pdfplumber (Python). For chunking: LangChain has JS, Python, and Java SDKs.
Verify models before recommending: Check the latest Elastic docs before recommending embedding models, inference endpoints, or LLMs. Elastic offers managed models via EIS (Elastic Inference Service) — the developer may not need an external OpenAI/Anthropic API key. Jina v3 is the current default embedding model for semantic_text on EIS; Jina v5-small is available for high-throughput / cost-sensitive workloads. ELSER remains available for English-only sparse retrieval but must be explicitly specified. EIS also provides managed rerankers (Jina Reranker v2/v3). Check the EIS documentation for current model IDs. For new projects, prefer semantic_text with EIS-managed embeddings as the default because it handles embedding automatically without manual inference pipelines. However, the concrete examples in this guide use the more explicit dense_vector + ingest inference + external LLM client path so the workflow is transparent and works even when the developer is not using EIS.
2. Architecture
RAG has four stages:
1. Chunk — Split documents into passages small enough for embedding and context windows 2. Embed & Index — Store chunks with vector embeddings in Elasticsearch 3. Retrieve — Given a user question, find the most relevant chunks 4. Generate — Pass retrieved chunks + question to an LLM to produce a grounded answer
User Question
│
▼
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Embed query │────▶│ Elasticsearch │────▶│ LLM (GPT, │
│ │ │ kNN retrieval │ │ Claude, etc)│
└─────────────┘ └──────────────────┘ └─────────────┘
│ │
Top-k chunks Answer + sources3. Document Chunking
Chunking strategy depends on document structure. Ask the developer about their content.
| Strategy | When to Use | Chunk Size |
|---|---|---|
| Fixed-size | Uniform text, no clear sections | 500-1000 tokens |
| Paragraph-based | Well-structured docs with natural breaks | 1 paragraph per chunk |
| Section-based | Documents with headers (H1/H2/H3) | 1 section per chunk |
| Recursive | Mixed content, need flexibility | LangChain's RecursiveCharacterTextSplitter |
Important considerations:
- Overlap — Add 50-200 token overlap between chunks so context isn't lost at boundaries
- Metadata — Preserve source document title, URL, section header, page number with each chunk
- Parent document — Store the parent doc ID so you can retrieve surrounding context if needed
Python chunking example:
def chunk_documents(documents: list[dict], chunk_size: int = 500, overlap: int = 100) -> list[dict]:
"""Split documents into overlapping chunks with metadata."""
chunks = []
for doc in documents:
text = doc["content"]
words = text.split()
for i in range(0, len(words), chunk_size - overlap):
chunk_text = " ".join(words[i:i + chunk_size])
if not chunk_text.strip():
continue
chunks.append({
"content": chunk_text,
"source_title": doc.get("title", ""),
"source_url": doc.get("url", ""),
"chunk_index": len(chunks),
"parent_doc_id": doc.get("id", ""),
})
return chunksLangChain chunking:
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " "]
)
chunks = splitter.split_documents(documents)4. Index Mapping
Store chunk text, embedding, and metadata for retrieval and source citation.
PUT /knowledge-base
{
"mappings": {
"properties": {
"content": { "type": "text" },
"embedding": {
"type": "dense_vector",
"dims": 1024,
"index": true,
"similarity": "cosine"
},
"source_title": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },
"source_url": { "type": "keyword" },
"section_header": { "type": "text" },
"parent_doc_id": { "type": "keyword" },
"chunk_index": { "type": "integer" },
"created_at": { "type": "date" }
}
}
}`dims` must match your embedding model's output dimension. The example uses 1024 (Jina v3 default on EIS). If the
developer selects a different model, adjust dims accordingly — a mismatch causes indexing failures. Withsemantic_text this coupling is handled automatically.5. Ingestion Pipeline
Use an ingest pipeline to embed chunks at index time.
PUT _ingest/pipeline/embed-knowledge-base
{
"processors": [
{
"inference": {
"model_id": "<YOUR_EMBEDDING_MODEL>",
"input_output": [
{
"input_field": "content",
"output_field": "embedding"
}
]
}
}
]
}Replace `<YOUR_EMBEDDING_MODEL>` with your inference endpoint ID. The default EIS embedding model is Jina v3 (1024
dims); Jina v5-small is available for cost-sensitive workloads. Check
EIS models for current model IDs — no API key or
ML nodes needed.
Bulk index chunks:
from elasticsearch import Elasticsearch, helpers
es = Elasticsearch(cloud_id="...", api_key="...")
def index_chunks(chunks: list[dict]) -> tuple[int, list]:
actions = [
{"_index": "knowledge-base", "_source": chunk, "pipeline": "embed-knowledge-base"}
for chunk in chunks
]
return helpers.bulk(es, actions, raise_on_error=False, raise_on_exception=False)6. Retrieval Patterns
Semantic Retrieval (Default for RAG)
GET /knowledge-base/_search
{
"knn": {
"field": "embedding",
"query_vector_builder": {
"text_embedding": {
"model_id": "<YOUR_EMBEDDING_MODEL>",
"model_text": "How do I configure index mappings?"
}
},
"k": 5,
"num_candidates": 50
},
"_source": ["content", "source_title", "source_url", "section_header"]
}Hybrid Retrieval (Better for Mixed Queries)
Combine keyword and semantic for more robust retrieval:
POST /knowledge-base/_search
{
"size": 5,
"query": {
"bool": {
"should": [
{ "match": { "content": "configure index mappings" } },
{
"knn": {
"field": "embedding",
"query_vector_builder": {
"text_embedding": {
"model_id": "<YOUR_EMBEDDING_MODEL>",
"model_text": "How do I configure index mappings?"
}
},
"k": 5,
"num_candidates": 50
}
}
]
}
},
"rank": { "rrf": {} },
"_source": ["content", "source_title", "source_url"]
}Filtered Retrieval (Scope to Specific Sources)
GET /knowledge-base/_search
{
"knn": {
"field": "embedding",
"query_vector_builder": {
"text_embedding": {
"model_id": "<YOUR_EMBEDDING_MODEL>",
"model_text": "How do I configure mappings?"
}
},
"k": 5,
"num_candidates": 50,
"filter": {
"term": { "source_title.keyword": "Elasticsearch Guide" }
}
}
}7. Answer Generation
Pass retrieved chunks to an LLM with a grounded prompt.
from openai import OpenAI # or the SDK for the developer's chosen LLM
from elasticsearch import Elasticsearch
es = Elasticsearch(cloud_id="...", api_key="...")
llm = OpenAI() # replace with the developer's LLM provider
def ask(question: str, k: int = 5) -> dict:
# 1. Retrieve relevant chunks
resp = es.search(
index="knowledge-base",
knn={
"field": "embedding",
"query_vector_builder": {
"text_embedding": {
"model_id": "<YOUR_EMBEDDING_MODEL>",
"model_text": question
}
},
"k": k,
"num_candidates": k * 10
},
source=["content", "source_title", "source_url"]
)
chunks = resp["hits"]["hits"]
# 2. Build context from retrieved chunks
context_parts = []
sources = []
for i, hit in enumerate(chunks):
src = hit["_source"]
context_parts.append(f"[{i+1}] {src['content']}")
sources.append({"title": src.get("source_title", ""), "url": src.get("source_url", "")})
context = "\n\n".join(context_parts)
# 3. Generate answer — replace model with the developer's choice from Step 2
completion = llm.chat.completions.create(
model="<LLM_MODEL>",
messages=[
{"role": "system", "content": (
"Answer the user's question using ONLY the provided context. "
"Cite sources using [1], [2], etc. "
"If the context doesn't contain enough information, say so."
)},
{"role": "user", "content": f"Context:\n{context}\n\nQuestion: {question}"}
],
temperature=0.2
)
return {
"answer": completion.choices[0].message.content,
"sources": sources
}8. Conversational Memory
For multi-turn conversations, include chat history in the prompt and optionally reformulate the question.
def ask_with_history(question: str, history: list[dict], k: int = 5) -> dict:
# Reformulate question using chat history for better retrieval
if history:
reformulation = llm.chat.completions.create(
model="<LLM_MODEL>",
messages=[
{"role": "system", "content": (
"Rewrite the user's question as a standalone search query, "
"incorporating context from the conversation history."
)},
*history,
{"role": "user", "content": question}
],
temperature=0
)
search_query = reformulation.choices[0].message.content
else:
search_query = question
# Retrieve using reformulated query
result = ask(search_query, k=k)
# Generate with full conversation context
messages = [
{"role": "system", "content": (
"Answer the user's question using the provided context. "
"Cite sources using [1], [2], etc. "
"Consider the conversation history for context."
)},
*history,
{"role": "user", "content": f"Context:\n{result['context']}\n\nQuestion: {question}"}
]
completion = llm.chat.completions.create(
model="<LLM_MODEL>",
messages=messages,
temperature=0.2
)
return {
"answer": completion.choices[0].message.content,
"sources": result["sources"]
}9. API Endpoint
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/chat", methods=["POST"])
def chat():
body = request.json
question = body.get("question", "")
history = body.get("history", [])
if not question:
return jsonify({"error": "Missing question"}), 400
result = ask_with_history(question, history)
return jsonify(result)10. Relevance Tuning for RAG
| Lever | Effect |
|---|---|
| Chunk size | Smaller = more precise retrieval, less context per chunk. Larger = more context but noisier. |
| k (num results) | More chunks = more context for the LLM but risks dilution. Start with 3-5. |
| Hybrid retrieval | Adds keyword matching; helps when questions contain specific terms or identifiers. |
| Reranking | Retrieve 20, rerank to top 5 with a managed reranker (Jina Reranker on EIS) for best precision. |
| Metadata filtering | Scope retrieval to relevant sources, time ranges, or categories. |
11. Common Follow-Ups
| Question | Answer |
|---|---|
| "The chatbot hallucinates" | Strengthen the system prompt ("only use provided context"), reduce temperature, add "I don't know" instructions. |
| "Answers are too vague" | Reduce chunk size for more precise passages; increase k for more context. |
| "How do I cite sources?" | Include source metadata in retrieval, reference in prompt with numbered citations. |
| "How do I handle long documents?" | Chunk with overlap; consider hierarchical retrieval (retrieve chunk, then fetch parent section). |
| "How do I update the knowledge base?" | Re-chunk and re-index changed documents. Use parent_doc_id to delete old chunks before re-indexing. |
| "Which LLM should I use?" | Use the LLM the developer chose in Step 2. EIS provides managed LLMs; OpenAI, Anthropic, or similar also work. |
12. When to Upgrade
- Agentic RAG — When the chatbot needs to take actions (create tickets, update records), not just answer questions.
Consider Elastic's Agent Builder.
- Multi-index RAG — When answers span multiple data sources. Use multiple kNN queries or a unified index.
- Streaming — For real-time chat UX, stream LLM responses token by token.
Search UI Frontend Guide
Guide developers through building a search frontend with Elastic's Search UI library (@elastic/search-ui). Use this guide after a backend recipe (keyword-search, vector-hybrid-search, catalog-ecommerce) has produced a working index and API, and the developer asks "now how do I build the search page?"
1. When to Use This Guide
Apply this guide when the developer signals:
- "How do I build the search page?" — they have an Elasticsearch index/API and need a frontend
- "I need a search bar with filters" — they want pre-built UI components, not raw HTML
- "I want faceted search" — sidebar filters with counts, like any e-commerce site
- "I need autocomplete" — suggestions as the user types
- "What's the fastest way to get a search UI?" — they want a library, not to build from scratch
Do not use this guide when:
- The developer is building an AI pipeline where code (not humans) consumes search results — point them to
vector-hybrid-search
- They only need a backend API with no frontend — they're done after the backend recipe
- They want to use a completely different frontend framework (Vue, Angular, Svelte) without React — Search UI's
pre-built components are React-only, though the headless core works with any framework. Explain the tradeoff: with React they get pre-built components out of the box; without React they get state management and query orchestration but must build their own UI components
2. Prerequisites
Before starting this guide, the developer should have:
1. A working Elasticsearch index with data (from any backend recipe) 2. Connection details — one of:
- Cloud ID (Elastic Cloud Hosted)
- Elasticsearch project endpoint URL (Elastic Cloud Serverless)
- Host URL (self-managed)
3. An API key with at least read access to the index 4. A React project — Next.js is also supported with a dedicated integration pattern.
3. Installation
npm install @elastic/search-ui @elastic/react-search-ui @elastic/react-search-ui-views @elastic/search-ui-elasticsearch-connectorFour packages:
@elastic/search-ui— headless core (state, actions, query orchestration)@elastic/react-search-ui— React bindings (SearchProvider,useSearchhook)@elastic/react-search-ui-views— pre-built React components with default styling@elastic/search-ui-elasticsearch-connector— connects to Elasticsearch directly
4. Connector Setup
The connector is how Search UI talks to Elasticsearch. Setup depends on the deployment model.
Development: Direct Connection
For local development and prototyping, connect directly. Never use this in production — it exposes API credentials to the browser.
import ElasticsearchAPIConnector from "@elastic/search-ui-elasticsearch-connector";
const connector = new ElasticsearchAPIConnector({
// Elastic Cloud Hosted — use cloud.id:
cloud: { id: "<your-cloud-id>" },
// Elastic Cloud Serverless or self-managed — use host instead of cloud:
// host: "https://<your-elasticsearch-endpoint>",
index: "<your-index-name>",
apiKey: "<read-only-api-key>",
});CORS for direct browser connections: If connecting directly from the browser (development only), Elasticsearch needs CORS headers. In Elastic Cloud Hosted, go to deployment settings → Edit user settings and add:
http.cors.allow-origin: "*"
http.cors.enabled: true
http.cors.allow-credentials: true
http.cors.allow-methods: OPTIONS, HEAD, GET, POST, PUT, DELETE
http.cors.allow-headers:
X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept,
x-elastic-client-metaServerless projects handle CORS automatically. Self-managed clusters need the same settings in elasticsearch.yml.
Production: Proxy Connector
In production, proxy all requests through your backend. This avoids exposing credentials, lets you add caching, logging, and access control.
Frontend (browser):
import { ApiProxyConnector } from "@elastic/search-ui-elasticsearch-connector/api-proxy";
const connector = new ApiProxyConnector({
basePath: "/api",
});Backend (Express server):
import express from "express";
import ElasticsearchAPIConnector from "@elastic/search-ui-elasticsearch-connector";
const app = express();
app.use(express.json());
const connector = new ElasticsearchAPIConnector({
host: "<your-elasticsearch-endpoint>",
index: "<your-index-name>",
apiKey: "<your-api-key>",
});
app.post("/api/search", async (req, res) => {
const { state, queryConfig } = req.body;
const response = await connector.onSearch(state, queryConfig);
res.json(response);
});
app.post("/api/autocomplete", async (req, res) => {
const { state, queryConfig } = req.body;
const response = await connector.onAutocomplete(state, queryConfig);
res.json(response);
});
app.listen(3001);This pattern works identically for Hosted, Serverless, and self-managed — the only difference is how you configure the server-side connector (cloud.id vs host).
For Next.js, use API routes instead of a separate Express server — same connector on the server, same ApiProxyConnector on the client.
5. Configuration
The configuration object tells Search UI which fields to search, which fields to show, and how to build facets. This must match the index mapping created in the backend recipe.
Mapping Config to Your Index
The configuration fields map directly to the Elasticsearch index mapping. Here's how to translate:
| Index Mapping | Search UI Config |
|---|---|
text fields you want searchable | search_fields with optional weight |
| Fields to display in results | result_fields with raw or snippet |
keyword fields for filtering | facets with type: "value" |
| Numeric fields for range filters | facets with type: "range" and ranges array |
geo_point fields | facets with type: "range", center, and unit |
completion or search_as_you_type fields | autocompleteQuery.suggestions |
Example: Product Search Config
This matches the index mapping from the catalog-ecommerce recipe:
const config = {
apiConnector: connector,
alwaysSearchOnInitialLoad: true,
searchQuery: {
search_fields: {
title: { weight: 3 },
description: {},
brand: { weight: 2 },
tags: {},
},
result_fields: {
title: { snippet: { size: 100, fallback: true } },
description: { snippet: { size: 200, fallback: true } },
brand: { raw: {} },
price: { raw: {} },
rating: { raw: {} },
image_url: { raw: {} },
category: { raw: {} },
},
fuzziness: true,
disjunctiveFacets: ["category", "brand"],
facets: {
category: { type: "value", size: 20 },
brand: { type: "value", size: 20 },
price: {
type: "range",
ranges: [
{ from: 0, to: 25, name: "Under $25" },
{ from: 25, to: 50, name: "$25–$50" },
{ from: 50, to: 100, name: "$50–$100" },
{ from: 100, to: 200, name: "$100–$200" },
{ from: 200, name: "$200+" },
],
},
rating: {
type: "range",
ranges: [
{ from: 4, name: "4+ stars" },
{ from: 3, to: 4, name: "3–4 stars" },
{ from: 0, to: 3, name: "Under 3 stars" },
],
},
},
},
autocompleteQuery: {
results: {
resultsPerPage: 5,
search_fields: {
"title.autocomplete": { weight: 3 },
},
result_fields: {
title: { snippet: { size: 100, fallback: true } },
price: { raw: {} },
image_url: { raw: {} },
},
},
suggestions: {
types: {
documents: { fields: ["title_suggest"] },
},
size: 4,
},
},
};`disjunctiveFacets` — list fields here if you want facet counts to stay visible after a selection. Without this, selecting "Electronics" as a category would hide all other category options. With it, the user can see counts for other categories and add more selections.
`fuzziness: true` — enables typo tolerance. Internally maps to Elasticsearch's fuzziness: "AUTO".
Autocomplete Field Requirements
Autocomplete has two modes that require different field types in the mapping:
| Mode | What It Does | Required Mapping |
|---|---|---|
results | Shows matching documents as you type | search_as_you_type field (best) or any text field |
suggestions | Shows suggested query terms | completion field |
If the backend recipe already created completion or search_as_you_type fields, reference them here. If not, the developer will need to update the mapping and reindex.
6. Components
Minimal Working Search Page
import React from "react";
import {
SearchProvider,
SearchBox,
Results,
PagingInfo,
ResultsPerPage,
Paging,
Facet,
Sorting,
ErrorBoundary,
} from "@elastic/react-search-ui";
import { Layout } from "@elastic/react-search-ui-views";
import "@elastic/react-search-ui-views/lib/styles/styles.css";
export default function SearchPage() {
return (
<SearchProvider config={config}>
<div className="App">
<ErrorBoundary>
<Layout
header={
<SearchBox
autocompleteResults={{
titleField: "title",
urlField: "url",
sectionTitle: "Results",
}}
autocompleteSuggestions={true}
debounceLength={300}
/>
}
sideContent={
<div>
<Facet field="category" label="Category" />
<Facet field="brand" label="Brand" />
<Facet field="price" label="Price" />
<Facet field="rating" label="Rating" />
</div>
}
bodyContent={<Results shouldTrackClickThrough />}
bodyHeader={
<>
<PagingInfo />
<ResultsPerPage options={[10, 20, 50]} />
<Sorting
label="Sort by"
sortOptions={[
{ name: "Relevance", value: [] },
{ name: "Price: Low to High", value: [{ field: "price", direction: "asc" }] },
{ name: "Price: High to Low", value: [{ field: "price", direction: "desc" }] },
{ name: "Rating", value: [{ field: "rating", direction: "desc" }] },
]}
/>
</>
}
bodyFooter={<Paging />}
/>
</ErrorBoundary>
</div>
</SearchProvider>
);
}Available Components
| Component | Purpose | Key Props |
|---|---|---|
SearchBox | Search input with autocomplete | autocompleteResults, autocompleteSuggestions, searchAsYouType, debounceLength |
Results | Render search result list | shouldTrackClickThrough, custom view |
Result | Single result card | titleField, urlField, custom view |
Facet | Sidebar filter | field, label, filterType ("any", "all", "none") |
Sorting | Sort dropdown | sortOptions array |
Paging | Page navigation | - |
PagingInfo | "Showing 1-10 of 250 results" | - |
ResultsPerPage | Results per page selector | options array |
ErrorBoundary | Catches and displays errors | - |
Layout | Pre-built page layout | header, sideContent, bodyContent, bodyHeader, bodyFooter |
Customizing Result Display
Pass a custom resultView function to <Results> to render product cards, images, or any layout. The function receives the full result object — access fields via result.<field>.raw (exact value) or result.<field>.snippet (highlighted HTML).
7. Custom Query Strategies
Search UI's default query works for keyword search. For semantic, hybrid, or advanced queries, use getQueryFn to override the query generation.
Semantic Search (requires ES 8.15+ with semantic_text field)
const connector = new ElasticsearchAPIConnector({
// ... connection config ...
getQueryFn: (state, config) => ({
semantic: {
field: "content_semantic",
query: state.searchTerm,
},
}),
});Hybrid Search (keyword + semantic via RRF)
For hybrid search, use interceptSearchRequest to inject a retriever-based query:
const connector = new ElasticsearchAPIConnector({
// ... connection config ...
interceptSearchRequest: async ({ requestBody, requestState, queryConfig }, next) => {
if (!requestState.searchTerm) return next(requestBody);
const modifiedBody = {
...requestBody,
query: undefined,
retriever: {
rrf: {
retrievers: [
{
standard: {
query: {
multi_match: {
query: requestState.searchTerm,
fields: ["title^3", "description"],
},
},
},
},
{
standard: {
query: {
semantic: {
field: "content_semantic",
query: requestState.searchTerm,
},
},
},
},
],
},
},
};
return next(modifiedBody);
},
});Sparse Vector
For most use cases, the Semantic Search example above is the recommended starting point — it uses semantic_text with Jina v3 (the default EIS dense embedding model, multilingual). Use the sparse_vector query below when you specifically need sparse-vector retrieval. ELSER (.elser-2-elasticsearch) is available as an English-only sparse alternative, but must be explicitly specified via inference_id.
const connector = new ElasticsearchAPIConnector({
// ... connection config ...
getQueryFn: (state, config) => ({
sparse_vector: {
field: "content_embedding",
inference_id: ".jina-embeddings-v3",
query: state.searchTerm,
},
}),
});Version Considerations for Query Strategies
| Strategy | Minimum ES Version | Notes |
|---|---|---|
| Keyword (multi_match, bool) | Any modern version | Works everywhere |
Semantic (semantic query) | 8.15+ | Requires semantic_text field; default EIS model is Jina v3 (multilingual dense) |
| kNN | 8.0+ | Use dense_vector field |
| Sparse vector / ELSER | 8.11+ | English-only sparse vectors; must be explicitly configured (Jina v3 is default on EIS) |
| Hybrid with RRF retrievers | 8.14+ | Retriever syntax |
| Serverless | Always latest | All features available |
When generating code, check the developer's Elasticsearch version (confirmed in the main playbook's Step 3) and only recommend query strategies their version supports.
8. Known Limitations
- Nested objects don't render — Search UI's Elasticsearch connector cannot display nested object fields. Use
flattened or keyword fields for facets instead of nested.
- React-only components — The pre-built components (
@elastic/react-search-ui-views) are React-only. The headless
core works with any framework, but you must build your own UI components.
- Query DSL only — Search UI generates Query DSL, not ES|QL. ES|QL is not supported for search queries through
Search UI.
- No built-in auth — Search UI doesn't handle user authentication. If different users see different data
(multi-tenancy), implement document-level security via the proxy layer.
- Facet counts with filters — Without
disjunctiveFacets, selecting a facet value hides other options. Always list
filterable facets in disjunctiveFacets for a good UX.
9. Common Follow-Ups
| Question | Answer |
|---|---|
| "How do I make facets stay open after selection?" | Add the field name to the disjunctiveFacets array. |
| "How do I deploy this?" | Switch to ApiProxyConnector on the frontend. Run the proxy server alongside your app. |
| "Can I use this with Vue/Angular?" | The headless core works with any framework. Build your own components via SearchDriver. |
| "How do I add semantic search?" | Use getQueryFn with a semantic query (requires ES 8.15+ and semantic_text field). See section 7. |
10. Connecting to Backend Recipes
This guide is designed to plug into any backend recipe. Here's how the handoff works:
| Backend Pattern | What It Provides | Search UI Connects Via |
|---|---|---|
| keyword-search | Text fields, keyword filters, completion field | Default query config — map search_fields and facets to the index |
| catalog-ecommerce | Product mapping with synonyms, nested attributes, autocomplete | Full config example in section 5 above |
| vector-hybrid-search | BM25 + semantic fields, semantic_text or dense_vector | getQueryFn or interceptSearchRequest with RRF retriever (section 7) |
The backend recipe builds the index, mapping, ingestion, and API. This recipe builds the frontend on top. If the developer followed a backend recipe that produced a Flask/Express API, they have two choices:
1. Use Search UI's connector to query Elasticsearch directly (through a proxy) — replaces the backend API for search 2. Keep the backend API and build a custom connector that calls it — useful when the API does more than search (auth, logging, business logic)
For option 2, implement a custom connector class with onSearch(requestState, queryConfig), onAutocomplete(requestState, queryConfig), onResultClick(), and onAutocompleteResultClick(). Each method receives Search UI state and must return { results, totalResults, facets }.
References
Elasticsearch Use Case Library
Present this library when a developer asks what they can build with Elasticsearch, wants to explore use cases, or needs help figuring out which category their project falls into. Walk through the relevant use cases conversationally — don't dump the entire list. Ask what resonates, then route to the appropriate implementation guide.
How to Use This Library
1. If the developer is exploring — summarize the 8 use cases with one-line descriptions and ask which sounds closest to what they're building. 2. If the developer describes something specific — match it to a use case below and confirm: "That sounds like [use case] — here's what that typically involves. Sound right?" 3. Once a use case is confirmed — return to the playbook and continue the conversation.
The Use Cases
1. Product & Catalog Search
Help users find and filter items from a structured catalog.
Industries: E-commerce, marketplace, retail, real estate, automotive, job boards
Examples:
- Online store product search with filters and facets
- Marketplace listing search (Airbnb, Etsy-style)
- Auto parts lookup by make, model, year
- Job search with location, salary, and role filters
- Real estate property search with price and amenity filters
What Elasticsearch does:
- Full-text search (BM25) for keyword matching on titles and descriptions
- Faceted filtering for price ranges, categories, brands, ratings
- Fuzzy matching for typo tolerance ("runnign shoes" still works)
- Synonyms API for domain-specific equivalents (sneakers = trainers)
- Completion suggester for search-as-you-type autocomplete
- Semantic reranking (optional) to boost results that match intent, not just words
---
2. Knowledge Base & Document Search
Let people search long-form content and find relevant passages.
Industries: SaaS, publishing, education, government, legal, healthcare
Examples:
- Internal wiki or documentation search (Confluence, Notion-style)
- Legal case law research across thousands of rulings
- Medical literature and clinical guideline search
- University course catalog and academic paper search
- Government policy and regulation search
What Elasticsearch does:
- Hybrid search (BM25 + kNN via RRF) for best of exact match + meaning
- Semantic search (dense vectors) to find relevant content even when words don't match
- Highlighting to show matching snippets in context
- Nested objects for searching within structured document sections
- Jina v3 (default EIS embedding model, multilingual) or ELSER (English-only sparse) for NLP-powered retrieval
---
3. AI-Powered Assistant / Chatbot
Build a conversational agent that answers questions using your data.
Industries: Customer support, SaaS, healthcare, financial services, education
Examples:
- "ChatGPT over your docs" — answer questions from company knowledge
- Internal IT helpdesk bot that resolves common issues
- Patient FAQ bot for healthcare providers
- Financial advisor assistant that references product documentation
- Student Q&A bot trained on course materials
What Elasticsearch does:
- RAG pipeline — retrieve relevant chunks, feed to LLM for answer generation
- Vector search (kNN) for semantically similar content retrieval
- Embedding models (Jina v3 via EIS by default, or OpenAI, Cohere, ELSER) to convert text to vectors
- LangChain / LlamaIndex integration for orchestrating retrieval + generation
- Chunking strategy guidance — how to split documents for effective retrieval
---
4. Recommendations & Discovery
Suggest relevant content users didn't explicitly search for — "you might also like".
Industries: Media, streaming, e-commerce, news, social platforms, music
Examples:
- "You might also like" product suggestions
- Related articles or blog posts
- Content personalization based on reading history
- "Customers also bought" cross-sell recommendations
- Music or video playlist suggestions based on similarity
What Elasticsearch does:
- Vector similarity (kNN) to find items "close" in embedding space
- More Like This queries to find similar documents based on content
- Semantic embeddings to represent items as vectors for comparison
- Filtering + boosting to constrain by category, recency, availability
- Script scoring to blend similarity with business rules (margin, inventory)
---
5. Customer Support Search
Help agents find solutions faster and customers help themselves.
Industries: SaaS, telecom, financial services, insurance, utilities
Examples:
- Agent assist — find similar resolved tickets to suggest resolutions
- Self-service portal — customers search for answers before filing a ticket
- Knowledge deflection — suggest articles when a user starts typing a ticket
- Escalation routing — classify and route tickets based on content
- Trend detection — surface emerging issues across support volume
What Elasticsearch does:
- Hybrid search for exact match on error codes + semantic match on symptom descriptions
- Semantic similarity to find tickets with similar problems regardless of wording
- Synonyms API for domain terminology ("can't log in" = "authentication failure" = "password issue")
- Highlighting to surface relevant resolution steps for agents
- Aggregations to detect support trends and cluster related issues
---
6. Location-Based Search
Find things near a place — stores, restaurants, properties, services.
Industries: Retail, food delivery, real estate, travel, logistics, healthcare
Examples:
- Store locator — find nearest retail locations
- "Restaurants near me" with cuisine filters
- Property search within a neighborhood or school district
- Nearest hospital or pharmacy finder
- Delivery radius calculation for logistics
What Elasticsearch does:
- Geo-point / geo-shape fields to store coordinates and boundaries
- Distance sorting to rank results by proximity to user
- Bounding box / polygon filters to search within a specific area
- Combined with full-text — "pizza near me" = geo filter + keyword search
- Geo-aggregations to cluster results on a map
---
7. Log & Event Search
Search, explore, and analyze machine-generated data.
Industries: DevOps, security operations, IoT, financial services, telecom
Examples:
- Application log search and troubleshooting
- Security event investigation (SIEM)
- IoT sensor data exploration
- Audit trail and compliance search
- Transaction monitoring and anomaly detection
What Elasticsearch does:
- Data streams for append-only, time-partitioned storage
- Index Lifecycle Management (ILM) for hot/warm/cold/frozen data tiers
- ES|QL for piped analytics queries
- Aggregations for histograms, percentiles, cardinality, and trends
- Runtime fields to extract structure from unstructured logs at query time
Note: Log and event search is typically handled by Elastic's Observability or Security solutions, which provide purpose-built UIs (Discover, Dashboards, SIEM). If the developer describes this use case, redirect them: on Hosted, they can change the solution view in Kibana; on Serverless, they should create an Observability or Security project.
---
8. Vector Database (for AI/ML Pipelines)
Store and retrieve embeddings programmatically — code searches, not people.
Industries: AI/ML companies, any organization building with LLMs, research labs
Examples:
- Embedding storage and retrieval for RAG pipelines
- Image similarity search (reverse image lookup)
- Code search across repositories by semantic meaning
- Duplicate detection across large document sets
- Anomaly detection using vector distance from normal patterns
What Elasticsearch does:
- Dense vector fields to store high-dimensional embeddings
- kNN / ANN (HNSW) for approximate nearest neighbor search at scale
- Scalar and product quantization to compress vectors for cost/performance
- LangChain / LlamaIndex vector store as a drop-in integration
- Metadata filtering to combine vector similarity with structured filters
---
Quick Reference: Use Case to Technology Map
| Use Case | Primary Tech | Optional Additions |
|---|---|---|
| Product & catalog search | Full-text (BM25), facets, fuzzy, synonyms | Semantic reranking, autocomplete |
| Knowledge base search | Hybrid (BM25 + kNN via RRF) | Highlighting, nested objects |
| AI assistant / chatbot | Vector search (kNN), RAG pipeline | LangChain/LlamaIndex, Jina Reranker, ELSER |
| Recommendations | Vector similarity (kNN), More Like This | Script scoring, behavioral signals |
| Customer support search | Hybrid search, synonyms | Aggregations for trend detection |
| Location-based search | Geo-point, distance sort, geo filters | Combined with full-text |
| Log & event search | Data streams, ILM, ES\ | QL, aggregations |
| Vector database | Dense vectors, kNN/ANN (HNSW) | Quantization, metadata filtering |
Non-Search Use Cases
If the developer describes something that isn't search, acknowledge it and redirect:
- Monitoring infrastructure or applications — That's Elastic Observability. On Hosted, change the solution view in
Kibana. On Serverless, create an Observability project. Docs
- Detecting threats or investigating security events — That's Elastic Security. On Hosted, change the solution view.
On Serverless, create a Security project. Docs
- Building dashboards and visualizations — Kibana has built-in dashboards, Lens, and Maps. Point them to Kibana's
visualization tools rather than building from scratch.
Related skills
How it compares
Choose elasticsearch-onboarding over generic Elasticsearch setup skills when the use case is storefront product discovery with facets and autocomplete, not log or observability indexing.
FAQ
Who is elasticsearch-onboarding for?
Developers and software engineers working with elasticsearch-onboarding patterns from the skill documentation.
When should I use elasticsearch-onboarding?
Help developers new to Elasticsearch get from zero to a working search experience. Guide them through understanding their intent, mapping their data, and building a search experience with best practices baked in. Use this when the user shows intent to build search-related functio
Is elasticsearch-onboarding safe to install?
Review the Security Audits panel on this page before installing in production.