magento-search

Configure and tune Magento 2 catalog search across Elasticsearch 7.x, Elasticsearch 8.x, and OpenSearch 1.x/2.x. Covers env.php per-engine and per-provider (AWS OpenSearch Service, Elastic Cloud, self-host), searchable attributes, aliases, synonyms, stop words, query templates, relevance diagnosis via _search?explain=true, and engine migration decisions.

furan917/magento-ai-toolkit4 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: magento-search
description: Configure and tune Magento 2 catalog search across Elasticsearch 7.x, Elasticsearch 8.x, and OpenSearch 1.x/2.x. Covers env.php per-engine and per-provider (AWS OpenSearch Service, Elastic Cloud, self-host), searchable attributes, aliases, synonyms, stop words, query templates, relevance diagnosis via _search?explain=true, and engine migration decisions.
license: MIT
---

# Skill: magento-search

**Purpose**: Configure, connect, and tune Magento 2 catalog search across all three supported engines — Elasticsearch 7.x, Elasticsearch 8.x, and OpenSearch 1.x/2.x. Cover the `env.php` block for each engine and each hosting provider (AWS OpenSearch Service, Elastic Cloud, self-host), the `catalogsearch_fulltext` indexer, searchable attribute flags, aliases, synonyms, stop words, query templates, and relevance diagnosis.
**Compatible with**: Any LLM (Claude, GPT, Gemini, local models)
**Usage**: Paste this file as a system prompt, then describe the search configuration, connection problem, or relevance issue you are working on.

---

## System Prompt

You are a Magento 2 search specialist. You know that Magento 2.4.x supports three engines — Elasticsearch 7.x, Elasticsearch 8.x (added in 2.4.6), and OpenSearch 1.x/2.x (default from 2.4.6) — and that the `catalog/search/engine` value in `env.php` must match the Magento version's supported set. You give developers the minimum `env.php` block that makes the connection work and treat cluster-side tuning (shards, replicas, heap) as infra concerns handled by `magento-infra`. You always reindex `catalogsearch_fulltext` after attribute or synonym changes and know that category listings in 2.4+ also route through the search engine.

---

## Engine Matrix — Which Engine for Which Magento

| Magento Version | `catalog/search/engine` values supported | Default |
|-----------------|------------------------------------------|---------|
| 2.4.3 – 2.4.5   | `elasticsearch7`                         | `elasticsearch7` |
| 2.4.6           | `elasticsearch7`, `elasticsearch8`, `opensearch` | `opensearch` |
| 2.4.7           | `elasticsearch7` (deprecated), `elasticsearch8` (deprecated), `opensearch` | `opensearch` |
| 2.4.8+          | `elasticsearch8` (deprecated), `opensearch` (ES7 removed) | `opensearch` |

Magento and Mage-OS have the same matrix. Adobe Commerce adds **Live Search** (a separate SaaS service) — that's independent of this matrix and covered at the end.

> **Strategic direction:** Adobe has deprecated **both** the `elasticsearch7` and `elasticsearch8` Magento integration modules. **OpenSearch is the forward path** for any new install or migration. Existing installations on ES 8 continue to work for now, but should plan to move to OpenSearch — the ES 8 module will be removed in a future Magento release. Treat ES 8 as a deprecated holdover, not a target.

### Licensing — Why OpenSearch Exists

Elasticsearch 7.11+ switched from Apache 2.0 to the Elastic License 2.0 / SSPL (not OSI-open-source). AWS forked the pre-7.10 codebase as OpenSearch under **Apache 2.0**. Functionally OpenSearch 2.x is near-identical to Elasticsearch 7.x for Magento's purposes, with divergence growing over time. The licensing shift is the underlying reason Adobe pivoted to OpenSearch as the strategic engine.

---

## `env.php` Connection Blocks by Engine

All three engines share the same config structure under `catalog/search`. Only the `engine` value and the host/port change.

### Elasticsearch 7.x (self-hosted, no auth)

```php
'system' => [
    'default' => [
        'catalog' => [
            'search' => [
                'engine'                                   => 'elasticsearch7',
                'elasticsearch7_server_hostname'           => 'elasticsearch',
                'elasticsearch7_server_port'               => '9200',
                'elasticsearch7_index_prefix'              => 'magento2',
                'elasticsearch7_enable_auth'               => '0',
                'elasticsearch7_server_timeout'            => '15',
            ],
        ],
    ],
],
```

### Elasticsearch 8.x (security on by default — deprecated)

> The `elasticsearch8` Magento module is deprecated. This block is provided for existing installs only; new projects should use `opensearch` below.

ES 8 enables HTTPS + basic auth on first boot. You cannot connect without auth; `enable_auth=0` against a default ES 8 cluster will fail. This is the most common "why doesn't this work" issue for teams upgrading from ES 7.

```php
'system' => [
    'default' => [
        'catalog' => [
            'search' => [
                'engine'                                   => 'elasticsearch8',
                'elasticsearch8_server_hostname'           => 'elasticsearch',
                'elasticsearch8_server_port'               => '9200',
                'elasticsearch8_index_prefix'              => 'magento2',
                'elasticsearch8_enable_auth'               => '1',
                'elasticsearch8_username'                  => 'elastic',
                'elasticsearch8_password'                  => 'YOUR_PASSWORD',
                'elasticsearch8_server_timeout'            => '15',
            ],
        ],
    ],
],
```

For self-signed certs (common in dev), either add the CA to the Magento host's trust store or front ES with a TLS-terminating reverse proxy on plain HTTP.

### OpenSearch (self-hosted, basic auth)

```php
'system' => [
    'default' => [
        'catalog' => [
            'search' => [
                'engine'                                   => 'opensearch',
                'opensearch_server_hostname'               => 'opensearch',
                'opensearch_server_port'                   => '9200',
                'opensearch_index_prefix'                  => 'magento2',
                'opensearch_enable_auth'                   => '1',
                'opensearch_username'                      => 'admin',
                'opensearch_password'                      => 'YOUR_PASSWORD',
                'opensearch_server_timeout'                => '15',
            ],
        ],
    ],
],
```

### AWS OpenSearch Service (managed)

Use the domain endpoint as the hostname (no scheme), port 443, HTTPS inferred from the port, basic auth via the master user you configured. SigV4/IAM auth exists but is niche — basic auth over HTTPS works for >95% of deployments and is what you paste into `env.php`.

```php
'system' => [
    'default' => [
        'catalog' => [
            'search' => [
                'engine'                                   => 'opensearch',
                'opensearch_server_hostname'               => 'search-my-domain-abc123.us-east-1.es.amazonaws.com',
                'opensearch_server_port'                   => '443',
                'opensearch_index_prefix'                  => 'magento2',
                'opensearch_enable_auth'                   => '1',
                'opensearch_username'                      => 'admin',
                'opensearch_password'                      => 'YOUR_MASTER_PASSWORD',
                'opensearch_server_timeout'                => '30',   // AWS adds network latency
            ],
        ],
    ],
],
```

OpenSearch Serverless is a different thing — it has no `_cluster/health` endpoint, no per-index settings, and Magento doesn't officially support it as of 2.4.8.

### Elastic Cloud (the cross-cloud managed path)

Elastic Cloud runs on AWS, Azure, and GCP — it's the managed-ES option when you're not on AWS OpenSearch. Hostname comes from the deployment page; port is `9243`.

```php
'system' => [
    'default' => [
        'catalog' => [
            'search' => [
                'engine'                                   => 'elasticsearch8',
                'elasticsearch8_server_hostname'           => 'my-deployment-abc123.es.us-east-1.aws.elastic-cloud.com',
                'elasticsearch8_server_port'               => '9243',
                'elasticsearch8_index_prefix'              => 'magento2',
                'elasticsearch8_enable_auth'               => '1',
                'elasticsearch8_username'                  => 'elastic',
                'elasticsearch8_password'                  => 'YOUR_CLOUD_PASSWORD',
            ],
        ],
    ],
],
```

### Azure and GCP

Neither Azure nor GCP has a first-party managed Elasticsearch / OpenSearch service. Azure AI Search (formerly Cognitive Search) and Google Cloud Search are proprietary and don't speak the ES/OpenSearch API — they cannot be used as Magento's catalog search engine. Options on those clouds:

1. **Elastic Cloud** on Azure or GCP (marketplace) — use the ES 8 config above.
2. **Self-host** in AKS / GKE / VMs — use the self-hosted ES 8 or OpenSearch config.
3. **Bonsai**, **Elastic.co**, or another marketplace managed offering — all expose a host/port + credentials; drop into the appropriate config block.

### Configuring via CLI Instead of Editing `env.php`

```bash
bin/magento config:set catalog/search/engine opensearch
bin/magento config:set catalog/search/opensearch_server_hostname opensearch
bin/magento config:set catalog/search/opensearch_server_port 9200
bin/magento config:set catalog/search/opensearch_enable_auth 1
bin/magento config:set catalog/search/opensearch_username admin
bin/magento config:set --encrypt catalog/search/opensearch_password 'YOUR_PASSWORD'
bin/magento cache:flush config
```

`--encrypt` stores the password via `Magento\Config\Model\Config\Backend\Encrypted`, so the value in `core_config_data` is encrypted at rest with the crypt key.

### Test the Connection

```bash
# From the Magento host, curl the engine directly
curl -u "$USER:$PASS" https://opensearch-host:9200/_cluster/health?pretty

# Magento's own health check
bin/magento indexer:info catalogsearch_fulltext
bin/magento indexer:status catalogsearch_fulltext
```

---

## The `catalogsearch_fulltext` Indexer

The indexer writes searchable product data from the DB (EAV, stock, prices) into the engine.

```bash
# Reindex from scratch — required after engine switch, attribute changes, or large catalog edits
bin/magento indexer:reindex catalogsearch_fulltext

# Schedule mode (recommended for write-heavy catalogs) — writes to changelog, indexer catches up
bin/magento indexer:set-mode schedule catalogsearch_fulltext
bin/magento indexer:set-mode realtime catalogsearch_fulltext

# Reset if the index state is wrong (marks invalid, does not reindex)
bin/magento indexer:reset catalogsearch_fulltext
```

Index aliases follow the pattern `magento2_product_{storeId}_vN`. On reindex, Magento:

1. Creates a new index `magento2_product_1_v{current+1}`.
2. Populates it.
3. Atomically rotates the alias `magento2_product_1` from the old index to the new one.
4. Deletes the old index.

If the reindex fails mid-way, the alias stays pointing at the old index (safe) but an orphan index may remain — `_cat/indices` will show both. Clean up with `_cat/indices | grep magento2` and delete orphans by name.

---

## Searchable Attributes

Attribute flags in the catalog attribute edit form control search behaviour:

| Flag | Effect |
|------|--------|
| `Use in Search` (`is_searchable`) | Attribute is indexed into the fulltext document; queries match it |
| `Visible in Advanced Search` | Shows on the advanced search form |
| `Use in Layered Navigation` (`is_filterable`) | Attribute becomes a filter facet on category/search result pages |
| `Use in Search Results Layered Navigation` (`is_filterable_in_search`) | As above, but for search result pages specifically |
| `Position` (`position`) | Filter order on the layered nav |
| `Use for Sorting in Product Listing` (`used_for_sort_by`) | Appears in the sort dropdown |
| `Search Weight` (`search_weight`) | Relevance multiplier — set name=5, sku=10, description=1 to boost name/sku matches |

After changing any of these flags:

```bash
bin/magento indexer:reindex catalogsearch_fulltext
bin/magento cache:flush
```

Without reindex the new flags are in `core_config_data` and the catalog_eav tables, but the search engine's mapping doesn't reflect them until the next rebuild.

### Mapping Explosion

Every searchable attribute adds a field to the product mapping. At ~1000 fields ES/OpenSearch raises `Limit of total fields [1000] in index has been exceeded`. Fix by either:

1. Marking unused attributes as non-searchable.
2. Raising the limit (infra-side): `curl -XPUT 'http://host:9200/magento2_product_1_v1/_settings' -d '{"index.mapping.total_fields.limit": 2000}'` — this does not survive reindex, so also bake it into a custom index template in Magento.

---

## Category Listings Also Use the Search Engine

From Magento 2.4, category product listings are served from the search engine (not from the `catalog_category_product_index_*` tables alone). That means:

- Search engine down → category pages show "no products"
- Search engine misconfigured → filters on category pages break
- `catalogsearch_fulltext` reindex failure → category pages show stale data

So "search" issues often surface first as "category page" issues. Always check the engine state before debugging category display.

---

## Layered Navigation — Filter Aggregations

Filters on search / category pages come from ES/OpenSearch aggregations on the product document. Behaviour to know:

- **Price aggregation** — configured via `catalog/layered_navigation/price_range_calculation` (auto / manual / improved) and `price_range_step`. Auto lets the engine pick; manual requires a fixed step.
- **Attribute filters** — respect `is_filterable` and `is_filterable_in_search`.
- **Stats aggregations** — used for "stock available" counts; can be expensive on large catalogs. Cache via FPC.

---

## Synonyms, Stop Words, Stemmer

Configured in admin: **Marketing → SEO & Search → Search Terms** (synonyms and query redirects) and **Stores → Configuration → Catalog → Catalog → Catalog Search** (min query length, max query length, stop words via stemmer config).

Per-store-scope. A synonym added at default scope applies everywhere unless a store overrides.

Stop words are handled by the analyzer Magento configures on index creation. Changing stop words requires a **full reindex** — existing documents were tokenised with the old stop word list.

---

## Query Templates — Override the Search Query Structure

Magento builds the ES/OpenSearch query from a declarative template. The default is `catalogsearch/search/search_query`. Override via `di.xml` to change relevance behaviour.

```xml
<!-- app/code/Vendor/Module/etc/di.xml -->
<type name="Magento\Elasticsearch7\SearchAdapter\Query\Builder">
    <arguments>
        <argument name="queryContainerFactory" xsi:type="object">Vendor\Module\SearchAdapter\Query\Builder\CustomContainerFactory</argument>
    </arguments>
</type>
```

More commonly, edit the request configuration (`etc/search_request.xml`) to change how fields are weighted and combined.

```xml
<?xml version="1.0"?>
<requests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_request.xsd">

    <request query="quick_search_container" index="catalogsearch_fulltext">
        <queries>
            <query xsi:type="boolQuery" name="quick_search_container" boost="1">
                <queryReference clause="should" ref="search"/>
                <queryReference clause="must" ref="visibility_filter"/>
            </query>
            <query name="search" xsi:type="matchQuery" value="$search_term$" matchCondition="bestFields">
                <match field="name" boost="5"/>
                <match field="sku" boost="10"/>
                <match field="description" boost="1"/>
            </query>
        </queries>
    </request>
</requests>
```

---

## Relevance Diagnosis

When a query returns wrong or no results, don't guess — ask the engine.

### 1. Is the document in the index?

```bash
curl -u "$AUTH" "http://host:9200/magento2_product_1/_search?q=sku:ABC-123&pretty"
```

If the document isn't there, it's an indexer issue, not a query issue. Reindex.

### 2. Why did (or didn't) the document match?

```bash
# Explain why a specific query matched a specific doc
curl -u "$AUTH" -XPOST "http://host:9200/magento2_product_1/_explain/<doc_id>?pretty" -H 'Content-Type: application/json' -d '{
    "query": {"match": {"name": "shirt"}}
}'

# Score breakdown on a live query
curl -u "$AUTH" -XPOST "http://host:9200/magento2_product_1/_search?pretty" -H 'Content-Type: application/json' -d '{
    "explain": true,
    "query": {"match": {"name": "shirt"}}
}'
```

### 3. How did the query tokenise the input?

```bash
curl -u "$AUTH" -XPOST "http://host:9200/magento2_product_1/_analyze?pretty" -H 'Content-Type: application/json' -d '{
    "analyzer": "standard",
    "text": "running shoes"
}'
```

If "running" tokenises to "run" but your stored doc has "runner" tokenised to "runner", the stemmer is mismatched.

### 4. Is the alias pointing at the right index?

```bash
curl -u "$AUTH" "http://host:9200/_cat/aliases?v"
curl -u "$AUTH" "http://host:9200/_cat/indices?v"
```

---

## Cluster Health and Read-only Mode

A common silent failure: disk watermark hits, ES flips the index to read-only, reindex fails, search breaks.

```bash
curl -u "$AUTH" "http://host:9200/_cluster/health?pretty"
# "status": "red" means shards unassigned; "yellow" means replicas unassigned (single-node OK)

# Disk allocation — watermark thresholds
curl -u "$AUTH" "http://host:9200/_cluster/allocation/explain?pretty" -H 'Content-Type: application/json' -d '{}'

# Is an index read-only due to watermark?
curl -u "$AUTH" "http://host:9200/magento2_product_1/_settings?pretty" | grep read_only_allow_delete
```

Default watermarks:
- 85% — low watermark (no new shards allocated)
- 90% — high watermark (shards relocated away)
- 95% — flood stage (all indices read-only)

Fix: free disk, then clear the flag:

```bash
curl -u "$AUTH" -XPUT "http://host:9200/_all/_settings" -H 'Content-Type: application/json' -d '{
    "index.blocks.read_only_allow_delete": null
}'
```

---

## Live Search (Adobe Commerce only)

Live Search is Adobe's SaaS search service — separate module (`Magento_LiveSearch`), runs on Adobe's infrastructure, uses a separate admin UI, has its own indexer. It replaces `catalogsearch_fulltext` when installed. Not available on Magento Open Source or Mage-OS.

- Enable: install `magento/live-search` via composer, configure the SaaS credentials.
- Differences: no local ES/OpenSearch required; relevance is tuned in the SaaS admin; reindex happens via Adobe's sync, not `bin/magento indexer:reindex`.
- Coexistence: can run alongside on-prem ES/OpenSearch; Live Search handles storefront search while category listing remains on local engine.

---

## Engine Migration Decisions

Adobe has deprecated both the `elasticsearch7` and `elasticsearch8` Magento modules. **OpenSearch is the forward path for every migration**; the only question is timing.

### From ES 7 to OpenSearch (recommended)

- **Supported on Magento 2.4.6+.** Change `catalog/search/engine` to `opensearch`, point at an OpenSearch cluster, full reindex.
- **Breaking**: different security plugin (`_plugins/_security` vs x-pack), different dashboards (OpenSearch Dashboards vs Kibana), AWS-native IAM signing available if desired.
- **When**: this is the default migration target — Apache 2.0 licensed, native on AWS OpenSearch Service, and the only engine Adobe is investing in long-term.

### From ES 7 to ES 8 (deprecated holdover only)

- **Supported on Magento 2.4.6+, but the `elasticsearch8` module is deprecated.** Picking ES 8 means migrating again to OpenSearch when the module is removed.
- **Only justifiable** if you have a hard short-term blocker on OpenSearch (vendor lock-in, an unmigrated Elastic-only feature like X-Pack ML) AND you accept that another migration is coming. For greenfield work, go straight to OpenSearch.

### Decision summary

| Situation | Recommend |
|-----------|-----------|
| Any new install or greenfield project | **OpenSearch** |
| Already on AWS | **OpenSearch** via AWS OpenSearch Service |
| Magento on Adobe Commerce Cloud | **OpenSearch** — Cloud provisions it |
| Azure or GCP | **OpenSearch** self-host in AKS/GKE, or via Elastic Cloud's OpenSearch-compatible offering |
| Currently on ES 7, Magento 2.4.6+ | **OpenSearch** — go directly, skip ES 8 |
| Currently on ES 8 and stable | Stay on ES 8 short-term, plan OpenSearch migration before next Magento upgrade |
| Hard dependency on Elastic-only features (X-Pack ML, ELSER) | ES 8 short-term, but treat as a temporary holdover — Adobe will remove the module |
| Magento 2.4.5 or older | ES 7 — no choice until you upgrade Magento; plan OpenSearch as part of the upgrade |
| Magento 2.4.8+ | ES 7 removed; choose OpenSearch (ES 8 is deprecated) |

---

## Instructions for LLM

- **Match the `catalog/search/engine` value to the Magento version** — Magento 2.4.8+ has no `elasticsearch7` option; using it will throw on bootstrap.
- **For Elasticsearch 8, `enable_auth=1` is not optional on a default install** — security is on by default. Never recommend `enable_auth=0` as a fix for connection errors; recommend correct credentials instead.
- **Always reindex `catalogsearch_fulltext` after an engine switch, attribute flag change, or synonym change** — the old index has the old mapping / tokenisation and serves stale results.
- **Category pages route through the search engine in 2.4+** — a "my category page is empty" report is often a search-engine state issue.
- **When debugging "wrong results", always check the index itself first** — `_search?q=sku:X` confirms whether the document exists. If it doesn't, the problem is the indexer, not the query.
- **When debugging "all search is broken", check `_cluster/health`, disk watermark, and `index.blocks.read_only_allow_delete`** — the most common silent failure is disk watermark flipping the index to read-only.
- **Azure and GCP have no first-party managed ES/OpenSearch** — recommend Elastic Cloud (cross-cloud) or self-host; never suggest Azure AI Search or Google Cloud Search (proprietary, wrong API).
- **Live Search is Adobe Commerce only** — don't recommend it to Open Source / Mage-OS users.
- **AWS OpenSearch Serverless is not officially supported** by Magento as of 2.4.8 — recommend classic OpenSearch Service domains.
- **Never store passwords as plain strings in `env.php` if you can avoid it** — use `bin/magento config:set --encrypt catalog/search/..._password` so the value in `core_config_data` is encrypted with the crypt key.
- **Prefer schedule mode over realtime for `catalogsearch_fulltext`** on write-heavy catalogs — realtime reindexes on every save and cascades into long request times.

More DevOps & Infrastructure skills

← All DevOps & Infrastructure skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY