com um clique
catalog-ecommerce
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.
Menu
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.
Baseado na classificação ocupacional SOC
Register and implement custom workflow steps from an external Kibana plugin using `@kbn/workflows-extensions`. Use when adding or modifying a step type with `registerStepDefinition`, designing input/output/config Zod schemas, implementing `createServerStepDefinition` / `createPublicStepDefinition`, choosing `StepCategory`, building `editorHandlers` (selection / dynamicSchema), wiring `callKibanaApi` / `onCancel`, deciding sync vs async loader registration, updating `APPROVED_STEP_DEFINITIONS`, or reviewing PRs that touch any of these.
Register and implement custom workflow triggers from an external Kibana plugin using `@kbn/workflows-extensions`. Use when adding or modifying an event-driven trigger with `registerTriggerDefinition`, designing `eventSchema` Zod schemas, writing `documentation` and KQL `snippets`, wiring `emitEvent` via request context or `getClient`, choosing sync vs async public loader registration, updating `APPROVED_TRIGGER_DEFINITIONS`, or reviewing PRs that touch any of these. Always ask for the user's plugin id first to locate the correct plugin and file paths.
Register and roll out managed workflows from a Kibana plugin using `@kbn/workflows-extensions` and `@kbn/workflows/managed`. Use when adding or modifying a code-owned workflow definition, `registerManagedWorkflowOwner`, `initManagedWorkflowsClient`, `install` / `uninstall` / `ready`, choosing `lifecycle` / `versionStrategy` / `enablement`, authoring `yaml` vs `yamlTemplate`, space-scoped vs global installs, `getWorkflowStatus`, or `execute`, or reviewing PRs that touch managed workflow definitions or rollout. Always ask for the user's plugin id first to locate the correct plugin and definition file paths.
Implement and quality-check OpenTelemetry metric instrumentation in Kibana code that uses `@kbn/metrics`. Use whenever the user wants to add, change, or review OTel metrics — including any call to `metrics.getMeter`, `meter.createCounter`/`createUpDownCounter`/`createGauge`/`createHistogram`/`createObservable*`/`addBatchObservableCallback`, edits to `kibana.yml` `telemetry.metrics` config, or questions like "is this metric well-designed?", "what should I name this counter?", or "which instrument type is right here?". Trigger this skill even when the user does not say "OTel" or "OpenTelemetry" but is clearly adding observability to Kibana server code and already knows what they want to measure.
Primary guided playbook for Elasticsearch search in Kibana Agent Builder: intent → data → mapping → Dev Tools API snippets (SENSE), with one question at a time. Load this skill whenever the user wants to learn Elasticsearch search, get started, begin building, take first steps, onboard, follow a walkthrough or tutorial, go from zero to a working query, or get structured help setting up indices and search — including casual openers like hi, help, getting started, new to Elasticsearch, how do I build search, or I want to try search. Use when they need end-to-end onboarding, not a single narrow API answer. If they only ask what they can build with Elastic (exploration without the full playbook), prefer invoking /use-case-library first; you can still load this skill afterward for the guided build.
Topic-driven, hands-on Elasticsearch tutorial flow that runs in Kibana Dev Console. Use whenever the user says "walk me through", "give me a tutorial for", "teach me", "show me how X works", "tutorial on", or similar topical learning intent — and they are NOT asking you to build their real, specific use case. Topics are open-ended: any Elasticsearch / Kibana search concept the user names (e.g. mappings, analyzers, bool queries, semantic_text, kNN, RRF, aggregations, ingest pipelines, reranking, data streams, ES|QL). Tutorials use sample data on isolated resources, present every step as a SENSE snippet to run in Dev Tools, and end with cleanup plus pointers to docs and the onboarding / pattern skills.
| 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. |
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.
This skill provides deep implementation detail for catalog and e-commerce search. It is not the main conversation driver.
After applying the guidance here, re-read /elasticsearch-onboarding to resume the structured onboarding playbook (Steps 1–7: intent → data → mapping → build → test → iterate). That playbook controls sequencing, the one-question-at-a-time rule, and the Dev Tools API-snippet workflow. If /elasticsearch-onboarding has not been loaded yet in this conversation, load it now — it is the primary conversation flow for all Elasticsearch search onboarding.
Apply this guide when the developer signals:
Do not use this guide when: the developer only needs document search without structured attributes — point them to keyword-search or vector-hybrid-search. If they need meaning-based "find similar products," combine this with the vector-hybrid-search approach.
E-commerce indices need text fields for search, keyword fields for filtering/faceting, numeric fields for sorting/range filters, and nested fields for variants.
PUT /products
{
"settings": {
"analysis": {
"analyzer": {
"autocomplete_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "autocomplete_filter"]
},
"synonym_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",
"synonyms": [
"laptop, notebook => laptop",
"phone, mobile, cell phone => phone",
"tv, television => tv",
"headphones, earphones, earbuds => headphones"
]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "synonym_analyzer",
"fields": {
"keyword": { "type": "keyword" },
"autocomplete": { "type": "text", "analyzer": "autocomplete_analyzer", "search_analyzer": "standard" }
}
},
"description": { "type": "text", "analyzer": "synonym_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"
}
}
}
}
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.
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
}
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 }
}
}
}
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"]
}
POST /products/_search
{
"suggest": {
"spelling": {
"text": "wireles headphons",
"phrase": {
"field": "title",
"size": 3,
"gram_size": 3,
"direct_generator": [{
"field": "title",
"suggest_mode": "popular"
}]
}
}
}
}
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"
}
}
}
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" } }
]
}
}
}
}
]
}
}
}
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]
})
| 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 |
| 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. |