Elasticsearch Onboarding
ElasticOfficialCreated by elastic
- Skill
- Workflow
- Software development
- Apache-2.0
- Low risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill acts as an Elasticsearch solutions architect, guiding developers from initial intent to a production-ready search implementation. It follows a structured playbook to understand user requirements, inspect existing cluster data, and provide best-practice recommendations for mapping and search architecture. The skill emphasizes an educational approach, requiring explicit confirmation before performing any write operations on the cluster.
What it does
- Guides developers through the end-to-end process of building a search experience.
- Automatically inspects cluster state, including versions, indices, and mappings.
- Provides best-practice advice on data modeling and search relevance.
- Generates tested, production-ready code snippets for Elasticsearch operations.
- Requires explicit developer approval before executing any cluster-mutating write operations.
- Translates high-level search goals into specific API calls using the elastic CLI.
When to use it
- When a developer is new to Elasticsearch and needs help getting started.
- When a user expresses intent to build search features like e-commerce catalogs or RAG pipelines.
- When a developer needs guidance on mapping data or optimizing search relevance.
- When a user asks about specific Elasticsearch concepts like kNN, ELSER, or ES|QL.
Advantages
- Ensures best practices are applied from the start, reducing the need for future reindexing.
- Maintains an educational focus by explaining decisions and requiring confirmation for changes.
- Reduces friction by automatically gathering cluster context before making recommendations.
- Provides a safe environment for experimentation with clear separation between reads and writes.
Usage examples
- “I want to build a search experience for my e-commerce site.”
- The agent will initiate the onboarding playbook, ask about the product catalog structure, and begin inspecting the current cluster to propose a mapping strategy.
- “How do I build a RAG pipeline with Elasticsearch?”
- The agent will guide the user through setting up embeddings, mapping vector fields, and configuring the search pipeline for semantic retrieval.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/elastic/elasticsearch-onboarding/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip elasticsearch-onboarding.zip -d .agents/skills
Documentation by elastic
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.
<!-- begin-partial: preamble -->
Environment Configuration
This skill executes Elasticsearch operations through the elastic CLI. If the
elastic CLI is not installed, tell the user what it is needed for. Do
not guess credentials, call the HTTP API directly, or attempt other workarounds.
This skill references operations in HTTP-shorthand form (e.g., GET /, GET /_cat/indices, GET /{index}/_mapping,
GET /{index}/_settings/index.mode, POST /_query). The Operations table at the end of this document
maps each shorthand to the equivalent elastic CLI command — always use the CLI rather than calling the HTTP API
directly.
<!-- end-partial: preamble -->
Cluster Access
Cluster interaction splits into reads and writes. Load references/cluster-access/cluster-access.md for the full protocol.
Reads happen automatically. Ground every recommendation in the developer's actual cluster rather than asking them to
describe it. Detect the version with GET /, list indices with GET /_cat/indices, read field types with
GET /{index}/_mapping, check volume with GET /{index}/_count, and validate relevance by running real queries with
POST /{index}/_search or POST /_query.
Writes require confirmation. Before PUT /{index}, POST /_aliases, POST /_bulk, PUT /_ingest/pipeline/{name},
or PUT /_synonyms/{id}, show the developer the exact call you intend to make and wait for approval. Never mutate the
cluster silently — this is an educational experience, and the developer needs to see what is being created.
Don't volunteer connection state; do answer connection questions. Two different situations, and the distinction matters:
Unprompted — never open on your own connection or credential status. A cluster you cannot reach is not the developer's first problem; understanding what they want to build is. Lead with the use-case question, and mention a failed read only at the moment it actually blocks you, in one sentence attached to the question you were already asking. Onboarding works fine before any cluster exists.
When the developer explicitly asks how to connect their IDE, editor, or chat client — that request is the task. Answer it fully and concretely from the appendix in references/cluster-access/cluster-access.md, then ask the use-case question at the end. Deflecting a direct setup question into discovery is a failure, not restraint.
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.
Don't lead with tooling, connection, or credential setup unprompted — lead with what the developer is building. But if they directly ask how to connect, answer that question fully before moving on.
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.
Operations
| HTTP API (shorthand) | elastic CLI command |
|---|---|
GET / | elastic es info |
GET /_cat/indices | elastic es cat indices --index '<pattern>' |
GET /{index}/_mapping | elastic es indices get-mapping --index '<index>' |
GET /{index}/_count | elastic es count --index '<index>' |
POST /{index}/_search | elastic es search --index '<index>' --input-file '<search-body.json>' |
POST /_query | elastic es esql query --format tsv --query "<esql>" |
PUT /{index} | elastic es indices create --index '<index>' --mappings '<json>' --aliases '<json>' |
POST /_aliases | elastic es indices update-aliases --actions '<json>' |
POST /_bulk | elastic es bulk --index '<index>' --input-file '<ndjson-path>' |
PUT /_ingest/pipeline/{name} | elastic es ingest put-pipeline --id '<name>' --input-file '<json-path>' |
PUT /_synonyms/{id} | elastic es synonyms put-synonym --id '<id>' --synonyms-set '<json>' |
Files
- ATTRIBUTION.mdAdded by AutoGineer995 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md7.1 KiB
- references/catalog-ecommerce/ecommerce.md15.7 KiB
- references/cluster-access/cluster-access.md10.2 KiB
- references/code-generation/code-generation.md6.7 KiB
- references/elasticsearch-onboarding-playbook.md24.0 KiB
- references/keyword-search/keyword-search.md9.9 KiB
- references/rag-chatbot/rag-chatbot.md15.7 KiB
- references/search-ui/search-ui.md21.1 KiB
- references/use-case-library/use-case-library.md10.2 KiB
- references/vector-hybrid-search/vector-hybrid-search.md20.8 KiB
references/catalog-ecommerce/ecommerce.md
name: catalog-ecommerce description: Guide for building catalog and e-commerce search with Elasticsearch. Use when a developer wants product search, faceted navigation, autocomplete, "did you mean" suggestions, or shopping-oriented search experiences.
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.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
The author says: Elasticsearch 8.x or 9.x, self-managed, Elastic Cloud Hosted, or Elastic Cloud Serverless. Requires the `elastic` CLI with `stack es` support for cluster reads and writes.
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- elastic
- Publisher
- Elastic (Official)
- Source
- elastic/agent-skills · skills/elasticsearch/elasticsearch-onboarding · commit 09d879d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Also distributed in
- elastic/agent-skills
AutoGineer is not affiliated with or endorsed by elastic.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download elasticsearch-onboarding.zip