How synonyms work in Elasticsearch and OpenSearch: the index-time vs query-time trade-off, the synonym_graph filter for multi-word terms, hot-reloading synonym sets without reindexing, and managing large synonym sets in production.
A user searches your catalog for "tv" and gets nothing, because every product is indexed as "television." Another searches "ny" and misses every document that says "New York." Synonyms close those recall gaps. They are also one of the easiest features to misconfigure in a way that quietly corrupts ranking, breaks phrase queries, or forces a full reindex every time the merchandising team adds a term.
This guide covers the decisions that actually matter when you wire synonyms into Elasticsearch or OpenSearch: where in the analysis chain they belong, why synonym_graph exists, how to push updates without downtime, and how the two engines diverge once you move past the basics. Elasticsearch and OpenSearch share the same Lucene foundation here, so the token filters look identical. The management APIs around them do not.
What a Synonym Filter Actually Does
A synonym token filter rewrites the token stream during text analysis. When a document or query passes through the analyzer, the filter matches configured terms and injects their equivalents as additional tokens, usually at the same position as the original. The inverted index, or the query, then carries both the original word and its synonyms.
There are two rule shapes, and confusing them is the most common synonym bug we see:
- Equivalent synonyms list interchangeable terms separated by commas:
laptop, notebook, computer. Every term expands to all the others. A query for any one matches documents containing any of the rest. - Explicit synonyms use the
=>operator to map one direction only:i-pod, i pod => ipod. The left-hand terms are replaced by the right-hand terms and do not match each other.
An equivalent synonym rule makes all listed terms mutually searchable; an explicit
=>rule rewrites the left-hand terms into the right-hand terms without making the inputs interchangeable. Choosing the wrong form is the difference between "tv finds television" and "tv silently rewrites every match into an unrelated bucket."
Get this backwards and ranking drifts in ways that are hard to trace. If you write cheap => affordable thinking it is bidirectional, a search for "affordable" will not find documents that only said "cheap." The arrow points one way on purpose.
Here is a minimal inline filter. Inline synonyms are fine for testing and for a handful of stable rules, but Elastic explicitly warns against large inline lists because they bloat the cluster state and hurt performance.
PUT /products
{
"settings": {
"analysis": {
"filter": {
"my_synonyms": {
"type": "synonym_graph",
"synonyms": [
"laptop, notebook, computer",
"tv, television",
"i-pod, i pod => ipod"
]
}
},
"analyzer": {
"synonym_search": {
"tokenizer": "standard",
"filter": ["lowercase", "my_synonyms"]
}
}
}
}
}
Index-Time vs Query-Time: The Core Trade-Off
You can apply synonyms when documents are indexed, when queries are parsed, or both. This single choice drives reindex cost, storage, explainability, and how fast new terms go live. Pick deliberately.
Index-time synonyms bake the expanded tokens into the inverted index. The synonym for "television" is physically stored alongside "tv," so query-time work is cheap and term statistics (the document frequencies BM25 uses for scoring) reflect the expanded vocabulary. The cost: every synonym change requires reindexing the affected documents, and the index grows.
Query-time synonyms leave the index untouched and expand the query instead. Updating a rule is a settings change, not a reindex. The trade-off is scoring distortion. Because the synonyms were never indexed, BM25 statistics see only the original terms, and rarer synonyms can score unexpectedly. Query-time expansion also does more work per request.
| Dimension | Index-time synonyms | Query-time synonyms |
|---|---|---|
| Updating a rule | Requires reindex of affected docs | Settings update + analyzer reload, no reindex |
| Index size | Larger (expanded tokens stored) | Unchanged |
| Scoring / term stats | Reflect expanded vocabulary, more stable | Based on original terms, can skew for rare synonyms |
| Query cost | Lower | Higher (expansion per query) |
| Explainability | Harder (index hides the original term) | Easier (query shows the expansion) |
| Hot reload | Not supported | Supported via updateable: true |
| Best when | Stable taxonomy, fixed mappings | Frequently changing terms, large evolving sets |
For most production search, query-time synonyms are the right default. Merchandising and taxonomy terms change often, and avoiding a reindex on every edit is worth the scoring nuance. Reserve index-time synonyms for stable linguistic rules you rarely touch. One hard constraint: in both engines, a synonym set managed through the Synonyms API or a synonym file with updateable: true can only be used in a search analyzer, never an index analyzer. The hot-reload machinery is query-time only.
Multi-Word Synonyms and Why synonym_graph Exists
Single-word synonyms are easy. Multi-word synonyms break the older synonym filter, and the fix is the synonym_graph filter.
The problem is token positions. When "ny" maps to "new york," the replacement is two tokens where the original was one. The legacy synonym filter cannot represent that overlap cleanly, so it stacks tokens on top of each other and corrupts the positional data that phrase and proximity queries rely on. A match_phrase query against a multi-word synonym can return wrong results or none.
The
synonym_graphtoken filter handles multi-word synonyms correctly by producing a token graph that preserves positions across terms of different lengths. Elastic and OpenSearch both recommend it over the oldersynonymfilter for any set that contains multi-word rules.
The catch: synonym_graph produces a graph that the query parser can consume, but the standard indexing chain cannot. That makes synonym_graph a query-time filter. If you genuinely need multi-word synonyms applied at index time, you wrap a plain synonym filter with flatten_graph, accepting its phrase-query limitations. In practice this reinforces the earlier recommendation: keep synonyms query-time, use synonym_graph, and you sidestep the whole class of position bugs.
PUT /products
{
"settings": {
"analysis": {
"filter": {
"synonyms_filter": {
"type": "synonym_graph",
"synonyms_path": "analysis/synonyms.txt",
"updateable": true
}
},
"analyzer": {
"text_index": {
"tokenizer": "standard",
"filter": ["lowercase"]
},
"text_search": {
"tokenizer": "standard",
"filter": ["lowercase", "synonyms_filter"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "text_index",
"search_analyzer": "text_search"
}
}
}
}
The pattern above is the one to memorize: a plain analyzer for indexing and a separate search_analyzer that adds the updateable synonym filter. The field is analyzed without synonyms at write time and with synonyms at read time. This is also where synonyms collide with stemming. If your search analyzer stems before it applies synonyms, "running" becomes "run" and your running => jogging rule never fires. Order the filters so synonyms see the words your rules were written against, and keep stemming consistent across the index and search analyzers. Our guide to stemming in hybrid search digs into where light stemming belongs in this chain.
Hot-Reloading Synonyms Without Reindexing
The reason to keep synonyms query-time is that you can update them on a live cluster without touching documents. The mechanism is the updateable: true flag plus a reload call. Here Elasticsearch and OpenSearch diverge, so know which engine you are on.
Elasticsearch with a synonyms file. Set updateable: true on the filter and point synonyms_path at a file in the cluster config directory. After editing the file on every node, call the reload API:
POST /products/_reload_search_analyzers
The reload picks up the new file contents without closing the index. The file has to be present on all nodes, which is the operational wrinkle that pushes teams toward the API.
Elasticsearch with the Synonyms API. Since Elasticsearch 8.10, the Synonyms API manages synonym sets through a _synonyms endpoint, no file distribution required. You create a set, reference it from the filter with synonyms_set instead of synonyms_path, and updating the set automatically reloads every analyzer that uses it.
PUT /_synonyms/ecommerce-synonyms
{
"synonyms_set": [
{ "id": "tv-rule", "synonyms": "tv, television" },
{ "id": "ny-rule", "synonyms": "ny => new york" }
]
}
"synonyms_filter": {
"type": "synonym_graph",
"synonyms_set": "ecommerce-synonyms",
"updateable": true
}
A PUT to update that set triggers a reload equivalent to calling the reload search analyzers API across all indices that reference it. Sets created through the API or the UI are search-time only, consistent with the rule above.
OpenSearch. OpenSearch has the same synonym_graph filter, the same updateable: true flag, and the same file-based pattern, but no Synonyms API equivalent to _synonyms. Reloading uses a different endpoint that requires the Index State Management plugin:
POST /_plugins/_refresh_search_analyzers/products
If you are weighing the two engines on this and other relevance features, our Elasticsearch vs OpenSearch comparison covers where the platforms have drifted apart.
Managing Synonyms at Scale and on AWS
A list of fifty hand-written rules is one thing. A few thousand synonyms sourced from a product catalog, a marketing glossary, and a third-party taxonomy is a different operational problem.
On Amazon OpenSearch Service you cannot drop files into the config directory, so synonym files ship as custom packages. You upload the file to S3, associate the package with the domain, then reference it from the filter using the package identifier as the synonyms_path. AWS supports hot reload of these dictionary files: update the package and refresh the search analyzer, and a domain configured with updateable: true picks up the change without a reindex. The common automation pattern wires an S3 upload to a Lambda that re-associates the package and triggers the refresh, giving non-engineers a path to publish synonym edits. The standard caveat applies. Custom packages used as index analyzers, or as search analyzers without the updateable field, still force a manual index update, so confirm the flag is set.
A few practices keep large sets sane in production:
- Treat the synonym set as governed data, not config. Version it, review changes, and give a business owner the edit path. The Synonyms API and AWS custom packages both make publishing accessible without handing out cluster credentials.
- Watch for transitive surprises. Equivalent rules chain. If "jaguar" links to "cat" and "cat" links to "feline," a search for the car brand can pull in zoology. Keep ambiguous brand terms out of broad equivalence groups, and prefer explicit
=>rules for one-directional intent. - Guard against brand-name leaks. A rule that expands a generic term into a competitor's brand, or vice versa, is a relevance and legal problem. Audit imported taxonomies before they go live.
- Evaluate every change. Synonyms move both recall and precision, often in opposite directions. Measure before and after with a judged query set or an A/B test rather than eyeballing a few searches. Our detailed guide to Elasticsearch relevance tuning covers the metrics worth tracking.
Key Takeaways
- Synonyms rewrite the token stream. Equivalent rules (
a, b, c) make terms mutually searchable; explicit rules (a => b) rewrite one direction only. Mixing them up corrupts ranking. - Prefer query-time synonyms for anything that changes. They avoid reindexing on every edit, at the cost of some BM25 scoring nuance. Reserve index-time synonyms for stable linguistic rules.
- Use the
synonym_graphfilter, not the legacysynonymfilter, whenever multi-word synonyms are involved. It preserves token positions so phrase queries keep working. - The hot-reload pattern is
updateable: trueon the filter inside a dedicatedsearch_analyzer, then a reload call. This only works for search-time synonyms. - Elasticsearch 8.10+ offers a Synonyms API (
_synonymswithsynonyms_set) that auto-reloads analyzers. OpenSearch has no API equivalent and reloads viaPOST /_plugins/_refresh_search_analyzers/<index>with ISM. On Amazon OpenSearch Service, ship synonyms as S3-backed custom packages. - Order synonyms relative to stemming carefully, watch for transitive and brand-name leaks, and measure relevance before and after every change.
Synonyms are deceptively simple to turn on and easy to get subtly wrong. If you want a second set of eyes on a synonym strategy, a relevance regression, or a migration between Elasticsearch and OpenSearch, BigData Boutique does this work every day.