A mapping in Elasticsearch is the schema definition for an index. It declares each field's data type, how it's analyzed, whether it's stored, and any per-field options that control indexing and search behavior. Mappings determine whether "2024-01-15" is treated as a date, whether "alice@example.com" is tokenized or kept whole, and whether a numeric field can be aggregated. Once a field is mapped, its type is fixed - changing it requires reindexing.
How Elasticsearch Mappings Work
Every index has a mapping. You either define it explicitly when creating the index, let Elasticsearch infer it from the first document via dynamic mapping, or combine the two with dynamic templates. The mapping API exposes the current shape:
GET /my-index/_mapping
A mapping is a tree of properties. Each leaf is a field with a type and optional parameters. Nested objects and the nested field type let you express hierarchical structures.
PUT /products
{
"mappings": {
"properties": {
"name": { "type": "text", "analyzer": "english" },
"sku": { "type": "keyword" },
"price": { "type": "scaled_float", "scaling_factor": 100 },
"in_stock": { "type": "boolean" },
"created_at": { "type": "date" },
"tags": { "type": "keyword" },
"location": { "type": "geo_point" },
"description": {
"type": "text",
"fields": {
"raw": { "type": "keyword", "ignore_above": 256 }
}
}
}
}
}
Field Types
Elasticsearch groups field types into a few families. Pick the narrowest type that fits the data; broader types waste storage and slow queries.
| Category | Types | Notes |
|---|---|---|
| Core | keyword, text, boolean, binary |
keyword for exact match/aggs; text for full-text search |
| Numeric | long, integer, short, byte, double, float, half_float, scaled_float, unsigned_long |
Pick the smallest range that fits |
| Date | date, date_nanos |
date_nanos for sub-millisecond precision |
| Object | object, nested, flattened |
nested for queryable arrays of objects |
| Specialized | geo_point, geo_shape, ip, completion, search_as_you_type, version, range types |
Use-case-specific |
| Vector | dense_vector, sparse_vector |
KNN search, ML embeddings |
The most common mistake: using text for an identifier or status field. Identifiers and status values should be keyword - they need exact match, not full-text analysis.
Dynamic vs Explicit Mapping
| Aspect | Dynamic mapping | Explicit mapping |
|---|---|---|
| Trigger | Auto-detected from first doc with that field | Defined upfront in mapping |
| Control | Limited (Elasticsearch guesses type) | Full |
| Risk | Mapping explosion, wrong type inference | None |
| Default behavior | Strings -> text + keyword sub-field, numbers -> long/float, ISO dates -> date |
As declared |
| Recommended | Dev / prototyping | Production |
You control the dynamic behavior per-index via the dynamic setting:
true(default): new fields added automatically.false: new fields are stored in_sourcebut not indexed - they won't appear in queries.strict: indexing a document with an unmapped field fails with an error.runtime: new fields become runtime fields, computed at query time.
For production, set dynamic: strict or dynamic: false and add fields explicitly when needed via the Update Mapping API.
Multi-fields: Index the Same Data Multiple Ways
A field can be mapped under multiple types via the fields parameter. This is the standard pattern for text that also needs exact-match or sorting:
"description": {
"type": "text",
"fields": {
"raw": { "type": "keyword", "ignore_above": 256 },
"ngram": { "type": "text", "analyzer": "edge_ngram_analyzer" }
}
}
You then query description (full-text), aggregate on description.raw (keyword), and autocomplete against description.ngram (edge ngrams). Multi-fields avoid the fielddata trap.
Common Mapping Pitfalls
- Using
textfor IDs, status, or category fields. These should bekeyword. Search relevance scoring on a status enum is meaningless and slow. - Letting dynamic mapping run wild in production and hitting the default limit of 1000 fields per index (mapping explosion).
- Forgetting
ignore_aboveonkeywordfields - a single 64KB value will trip indexing errors. - Mapping numeric IDs as
long. If you never do range queries on them,keywordis faster for exact match (term lookup is cheaper than numeric block lookup). - Setting
index: falseto save space and then trying to query the field. The setting disables indexing entirely. - Enabling
_source: falseto save storage without realizing it breaks reindex, update, and many highlighting features.
Operating Mappings in Production
Mapping mistakes show up as a slow trickle of indexing errors, an unexpected mapping explosion, or queries that quietly return wrong results. Watch:
- Field count per index (
_cluster/stats->indices.mappings.field_types). - Indexing errors with
mapper_parsing_exception. - The
dynamic_mapping_updatescount in cluster stats - a leading indicator of mapping explosion.
Pulse continuously monitors mapping evolution, field-count growth, and dynamic mapping events across your Elasticsearch cluster. When a new field accidentally lands with the wrong type or a mapping update threatens stability, Pulse's automated root-cause analysis flags it before it cascades into search failures.
Frequently Asked Questions
Q: Can I change the mapping of an existing field in Elasticsearch?
A: No. Once a field is mapped, its type is fixed. To change it, create a new index with the desired mapping and reindex the data using the reindex API. You can, however, add new fields to an existing mapping via the Update Mapping API.
Q: What is the difference between text and keyword in Elasticsearch?
A: text fields are analyzed (tokenized, lowercased, stemmed) and used for full-text search with relevance scoring. keyword fields are stored as a single token, exact-match only, and support aggregations and sorting via doc values. Use keyword for IDs, status, tags, emails; use text for body content, descriptions, comments.
Q: What is dynamic mapping in Elasticsearch?
A: Dynamic mapping is Elasticsearch's automatic field detection: when a document contains a field not yet in the mapping, Elasticsearch infers a type (string -> text+keyword, ISO date -> date, etc.) and adds it. Controlled via the index-level dynamic setting (true, false, strict, runtime).
Q: How do I view an index mapping?
A: GET /my-index/_mapping returns the full mapping. GET /my-index/_mapping/field/my_field returns just one field. For a specific data type usage, query _cluster/stats and look at indices.mappings.field_types.
Q: What are multi-fields in Elasticsearch?
A: Multi-fields let you index the same source value under multiple types/analyzers via the fields parameter. The classic pattern is "type": "text" with a "raw": {"type": "keyword"} sub-field so you can full-text-search the main field and aggregate/sort on the sub-field.
Q: How do I prevent mapping explosion in Elasticsearch?
A: Set index.mapping.total_fields.limit (default 1000), use dynamic: strict to reject new fields outright, prefer flattened for high-cardinality JSON blobs, and audit mappings periodically. See mapping explosion.
Related Reading
- Elasticsearch Dynamic Mapping: dynamic setting in depth
- What is an Elasticsearch Analyzer: how text fields get tokenized
- What is Elasticsearch Fielddata: why text fields need keyword sub-fields for sorting
- What is Elasticsearch Index: how mappings attach to indices
- Elasticsearch Tokenizer vs Analyzer: analyzer pipeline
- Elasticsearch Too Many Fields - Mapping Explosion: preventing field-count problems