Understanding mapping: dynamic vs explicit, mapping parameters like analyzer and index; core data types (text vs keyword, numeric, date, boolean, range); as well as complex types (object, nested, geo, IP, join) with the risk of mapping explosion.

In episode 4 you created indexes with simple mappings. Now we'll dissect mapping thoroughly — because mapping is the most important design decision in Elasticsearch. Choosing the wrong data type can't be easily fixed: a field that's already text must be reindexed to become keyword.
Episode 5 covers: the dynamic vs explicit mapping concepts, the mapping parameters that govern field behavior, core data types (text, keyword, numeric, date, boolean, binary, range), and complex types (object, nested, array, geo, IP, join) — plus the danger of mapping explosion you must avoid from the start.
Dynamic mapping makes Elasticsearch guess the type from the first value that comes in. Long text strings become text; short strings like codes or statuses become text + a keyword sub-field. Guessing is convenient but often misses the mark. The rule of thumb:
"dynamic": "strict" so unknown fields are rejected rather than inferred.{
"mappings": {
"dynamic": "strict",
"properties": {
"name": { "type": "text" },
"status": { "type": "keyword" }
}
}
}| Parameter | Purpose |
|---|---|
index | Whether the field is searchable. false means stored but not searchable — saves resources |
analyzer | The analyzer used for indexing and searching text |
doc_values | Column structure for aggregations/sorting. Disable if aggregation isn't needed |
store | Whether the field value is stored separately from _source |
coerce | Whether values are automatically converted (for example the string "5" to integer) |
ignore_above | Ignores strings longer than this value on keyword fields |
null_value | Replacement value when a field is null |
Note index: false: this field can still be read from _source, but can't be queried. This parameter saves disk and speeds up indexing for fields never searched.
GET /produk/_mappingIf a field was accidentally created with the wrong type, your fix options are limited — there's no ALTER TABLE like SQL. The options: delete and recreate the index, or reindex (episode 13). That's why writing the mapping correctly from the start is so important.
This is the most crucial difference in Elasticsearch:
text — broken down by an analyzer into tokens (words), then indexed for full-text search. Searching "kaos polos" can match documents with the field "name": "Kaos Polos Premium". Supports partial matching, relevance, and stemming.keyword — stored as a single whole value, unanalyzed. Only suitable for exact match, sorting, and aggregations. Great for statuses, codes, IDs, emails.{
"mappings": {
"properties": {
"category": {
"type": "text",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
}
}
}
}With this pattern, category is used for full-text search, and category.keyword for exact match, aggregation, and sorting. This is the multi-field pattern you'll encounter most often.
byte, short, integer, long, float, double — choose the smallest one that can still hold your values, because smaller types save disk and speed up sort/aggregation. Numeric IDs only used for comparison should be keyword, not long, to avoid precision errors.
The date type stores dates as millisecond timestamps (epoch_millis). Elasticsearch accepts ISO 8601 strings like "2026-08-03T10:00:00Z" and converts them automatically. date_nanos adds nanosecond precision for cases that need it:
{
"mappings": {
"properties": {
"@timestamp": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}
}
}
}boolean — true/false values, used for flags.binary — base64-encoded binary data, not searchable.range — integer_range, float_range, date_range for storing value ranges, then queries asking "is value X within this range". Useful for discounted prices, schedules, or age ranges.JSON fields can contain objects — Elasticsearch stores them as object (a name field inside a user object automatically becomes user.name). Problems arise with arrays of objects: because Lucene doesn't store array structure, relations between objects in an array can get mixed up. The solution is nested:
{
"mappings": {
"properties": {
"order_items": {
"type": "nested",
"properties": {
"product": { "type": "keyword" },
"qty": { "type": "integer" }
}
}
}
}
}With nested, each object in the array is indexed as a separate hidden document, so a query like "product X with qty greater than Y" produces the correct answer.
All field types can hold arrays — a keyword field can contain ["red", "blue"]. What to remember: arrays don't need to be declared in the mapping, and their elements must be the same type as the field.
geo_point stores coordinates (lat, lon) and enables "nearest store" queries with geo_distance; geo_shape stores geometric shapes (polygons, lines) for spatial queries like "is this point inside the DKI Jakarta area".
The ip type stores IPv4/IPv6 addresses with automatic validation, and supports range queries like "find all logs from the 10.0.0.0/24 subnet".
join enables parent-child relationships within a single index — for example questions and answers. Unlike nested, this relationship doesn't weigh down searches the way nested does, but it demands attention to routing: child documents must have the same routing as their parent.
Warning
Mapping explosion is a silent enemy. If mappings are dynamically generated from uncontrolled data — for example log fields with dynamic names like user_12345, app_xyz — the field count can explode to hundreds of thousands, exhausting cluster memory and causing crashes. Protect yourself with dynamic: strict, ignore_dynamic_beyond_one_hundred (default), or the index.mapping.total_fields.limit cap (default 1000). Episode 25 will cover this prevention in an application integration context.
In episode 5 you understood mapping as the data blueprint: dynamic vs explicit, important parameters like index, analyzer, doc_values, and ignore_above; core data types — text vs keyword, numeric, date, boolean, binary, range; and complex types — object, nested, array, geo, IP, and join — with an awareness of the mapping explosion risk.
Key takeaways:
text for full-text search, keyword for exact match, sort, aggregation.text + .keyword multi-field is the most common pattern.nested type for accurate queries.dynamic wisely to prevent mapping explosion.Now your data is stored with correct mappings — time to search. In episode 6 we'll start Search Fundamentals with the Query DSL: the difference between URI search vs request body, query context vs filter context, the _source filter, pagination with from/size and search_after, and the basic queries — match, term, match_phrase, multi_match, query_string, exists, and range. See you there!