| 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.
Conversation flow — return to onboarding
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.
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-search or vector-hybrid-search. If they need meaning-based "find similar products," combine this with the vector-hybrid-search approach.
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.
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":
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"
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 }
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":
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":
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 = [{: {: in_stock}}]
category:
filters.append({: {: category}})
brand:
filters.append({: {: brand}})
min_price :
filters.append({: {: {: min_price}}})
max_price :
filters.append({: {: {: max_price}}})
sort_options = {
: [{: }, {: }],
: [{: }],
: [{: }],
: [{: }, {: }],
: [{: }],
}
body = {
: {
: {
: must must [{: {}}],
: filters
}
},
: (page - ) * size,
: size,
: sort_options.get(sort_by, sort_options[]),
: {: {: {}, : {}}},
: {
: {: {: , : }},
: {: {: , : }},
: {: {: }},
: {
: {
: ,
: [
{: , : },
{: , : , : },
{: , : , : },
{: , : , : },
{: , : }
]
}
}
}
}
resp = es.search(index=, body=body)
jsonify({
: [{
: h[],
: h[],
: h.get(, {})
} h resp[][]],
: resp[][][],
: {
: [{: b[], : b[]} b resp[][][]],
: [{: b[], : b[]} b resp[][][]],
: [{: b[], : b[]} b resp[][][]],
: resp[][]
},
: page,
: (resp[][][] + size - ) // size
})
():
q = request.args.get(, )
resp = es.search(
index=,
body={
: {
: {
: q,
: {
: ,
: ,
: ,
: {: }
}
}
}
}
)
suggestions = resp[][][][]
jsonify({
: [{: s[], : s[]} s 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 approach.
- 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.