# 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.

- **Type:** Skill
- **Install:** `agentstack add skill-furan917-magento-ai-toolkit-magento-search`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [furan917](https://agentstack.voostack.com/s/furan917)
- **Installs:** 0
- **Category:** [Cloud & Infrastructure](https://agentstack.voostack.com/c/cloud-infrastructure)
- **Latest version:** 0.1.0
- **License:** MPL-2.0
- **Upstream author:** [furan917](https://github.com/furan917)
- **Source:** https://github.com/furan917/magento-ai-toolkit/tree/main/skills/magento-search

## Install

```sh
agentstack add skill-furan917-magento-ai-toolkit-magento-search
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 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

    
        Vendor\Module\SearchAdapter\Query\Builder\CustomContainerFactory
    

```

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

```xml

    
        
            
                
                
            
            
                
                
                
            
        
    

```

---

## 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/?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

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [furan917](https://github.com/furan917)
- **Source:** [furan917/magento-ai-toolkit](https://github.com/furan917/magento-ai-toolkit)
- **License:** MPL-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-furan917-magento-ai-toolkit-magento-search
- Seller: https://agentstack.voostack.com/s/furan917
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
