Dynamic and Explicit Mapping in Elasticsearch – Differences, Examples, Best Practices

|

If you’ve ever indexed a document into Elasticsearch without defining anything upfront, you’ve already used dynamic mapping — you just didn’t know about explicit mapping. Elasticsearch quietly inspected your data, made a series of judgment calls about field types, and built a schema on your behalf. Sometimes that works perfectly. Other times, it silently creates a text field where you needed a keyword, and you don’t discover the problem until a query returns nothing.

Elasticsearch
Mapping
Schema Design

What Is Mapping in Elasticsearch

Mapping is the schema definition for an Elasticsearch index. It tells Elasticsearch what fields a document contains, what data type each field holds, and how each field should be indexed and stored.

Every field in a document — a string, a number, a date, a nested object — has a mapping. That mapping controls everything: whether a string field is full-text searchable, whether a date can be used in range queries, whether a number can be aggregated, whether a field is stored at all.

Think of mapping as a contract

A field mapped as keyword is matched exactly. A field mapped as text is tokenized and analyzed. A field mapped as date supports gte and lte range syntax. The mapping is the contract between your data and your queries.

There are two ways to establish that contract: let Elasticsearch figure it out automatically (dynamic mapping), or define it yourself (explicit mapping).


What Is Dynamic Mapping in Elasticsearch

Dynamic mapping means Elasticsearch automatically creates field mappings when it encounters new fields in a document — no schema definition required from you.

The first time you index a document with a field Elasticsearch hasn’t seen before, it inspects the value, infers a type, and adds the field to the index mapping. From that point on, the field is defined, and all future documents must be compatible with that definition.

Works

This is dynamic mapping in its purest form: index a document, get a mapping for free. Zero upfront schema work.

How Dynamic Mapping Works

When Elasticsearch encounters a new field, it applies type detection rules based on the JSON value type:

JSON ValueElasticsearch TypeNotes
true / falsebooleanStraightforward.
Whole number (e.g. 42)longAlways long, never integer.
Decimal number (e.g. 3.14)floatPrecision may surprise you.
String matching date formatdateISO 8601 strings detected automatically.
String (everything else)text + keyword sub-field⚠ Double-mapped — uses 2× storage.
Object {}objectFields nested inside are also mapped dynamically.
Array []Type of first elementMixed-type arrays will fail on subsequent docs.
The string row is the most dangerous one. Any string that doesn’t look like a date becomes text + keyword — even fields you’ll never do full-text search on. This silently doubles index size for every string field.

Here’s what happens under the hood when you index a document for the first time:

Request — PUT /orders/_doc/1
PUT /orders/_doc/1
{
  "customer":  "Alice",
  "total":     149.99,
  "placed_at": "2024-03-15T10:30:00Z",
  "shipped":   false
}

Elasticsearch creates the index and generates this mapping automatically:

Elasticsearch Response — GET /orders/_mapping
{
  "orders": {
    "mappings": {
      "properties": {
        "customer": {
          "type": "text",       // ← also gets .keyword sub-field
          "fields": {
            "keyword": { "type": "keyword", "ignore_above": 256 }
          }
        },
        "total":     { "type": "float"   },
        "placed_at": { "type": "date"    },
        "shipped":   { "type": "boolean" }
      }
    }
  }
}

Elasticsearch made four decisions on your behalf. Three are probably fine. The fourth — mapping customer as text — might not be what you wanted if you only ever need exact matching or aggregations, where keyword alone would suffice.


Advantages of Dynamic Mapping

Pros

+ Zero upfront schema work — start indexing immediately
+ New fields added automatically as your data evolves
+ Perfect for prototyping and exploratory analysis
+ Good enough for log ingestion pipelines
+ No friction during early development

When to rely on it

− Local development environments
− Ad-hoc data exploration
− Schemas that change frequently
− Early stage of a project

Disadvantages of Dynamic Mapping

Cons

− Type inference can be wrong — the first document locks in the type for all future documents
− All strings are double-mapped (text + keyword) by default, using 2× storage
− Field types cannot be changed after creation — requires full reindex
− Uncontrolled data can cause mapping explosions with thousands of fields

You can’t change a field type after the fact.

Once Elasticsearch maps a field as long, you can’t re-map it as keyword on the same index. The only path is to create a new index with the correct mapping and reindex all your data. Discovering this in production is painful.

What Is Explicit Mapping in Elasticsearch

Explicit mapping means you define the index schema before indexing any documents. You specify every field, its type, and any additional configuration — analyzers, index options, doc_values, and so on.

You create an explicit mapping using the mappings block when creating an index:

Explicit mapping — PUT /orders
PUT /orders
{
  "mappings": {
    "properties": {
      "customer":  { "type": "keyword" },  // exact match only
      "total":     { "type": "double"  },  // decimal precision
      "placed_at": { "type": "date"    },
      "shipped":   { "type": "boolean" }
    }
  }
}

From this point, Elasticsearch knows exactly what to expect. Any document with a field not in this mapping will either be rejected or ignored, depending on your dynamic setting.


Advantages of Explicit Mapping

Pros

+ Every field type is intentional — nothing is left to inference
+ Queries work as expected from day one, no type surprises
+ Set dynamic: "strict" to reject unexpected fields and enforce data quality
+ Prevents mapping explosions — you control what gets indexed
+ Lower storage footprint — no accidental double-mapping

Dynamic vs Explicit Mapping in Elasticsearch

DimensionDynamic MappingExplicit Mapping
Setup timeNoneRequired upfront
Schema flexibilityHigh — new fields auto-addedLow — requires mapping update
Type safetyInference-based, can be wrongGuaranteed correct
Best forPrototyping, log ingestion, explorationProduction search, analytics, known schemas
Mapping explosion riskHigh with uncontrolled dataLow — you control what’s accepted
Query reliabilityDepends on inference accuracyHigh — types match query expectations
Changing field typesRequires reindex if wrongRarely needed if designed correctly

The short version

Dynamic mapping is great for getting started, explicit mapping is essential for production.

Dynamic Mapping Example

Let’s walk through a realistic scenario where dynamic mapping creates a silent, production-breaking problem.

The event_id type conflict

You’re building a log ingestion pipeline. Each log line has an event_id that looks like "EVT-00123". Dynamic mapping creates this field as text + keyword. Fine — for now.
Three months later, a new service starts sending "event_id": 123 — a bare integer. Documents start failing to index. Payment failure events are silently lost. You don’t find out until someone notices the payment dashboard is missing data.

Here’s the exact sequence that causes the problem:

Step 1 — First document (string event_id)
PUT /logs/_doc/1
{
  "event_id": "EVT-00123",
  "level":    "info",
  "message":  "User login successful"
}

// Dynamic mapping creates: event_id → text + keyword
Step 2 — New service sends integer event_id
PUT /logs/_doc/2
{
  "event_id": 123,          // ← integer, not string
  "level":    "error",
  "message":  "Payment failed"
}
Elasticsearch Response — Indexing Failed
{
  "error": {
    "type":   "mapper_parsing_exception",
    "reason": "failed to parse field [event_id] of type [text]"
  }
}

The second document is rejected entirely

The payment failure event is lost. Dynamic mapping created a brittle, implicit contract from the first document it saw. An explicit mapping would have forced a deliberate choice about event_id before any documents were indexed.

Explicit Mapping Example

Here’s the same log index rebuilt with explicit mapping, plus a practical example of using dynamic: "strict" to enforce your schema.

PUT /logs — Explicit mapping with strict mode
PUT /logs
{
  "mappings": {
    "dynamic": "strict",    // unknown fields → rejected
    "properties": {
      "event_id":    { "type": "keyword" },  // exact lookups only
      "level":       { "type": "keyword" },  // "info", "error", "warn"
      "message":     { "type": "text"    },  // full-text search
      "timestamp":   { "type": "date"    },
      "service":     { "type": "keyword" },  // aggregations
      "duration_ms": { "type": "long"    }   // percentile queries
    }
  }
}

A few intentional decisions worth unpacking:

event_id → keyword
You’ll never do full-text search on it — only exact lookups and aggregations. No need for the double mapping.
level → keyword
Log levels like "info""error""warn" are always matched exactly. Full-text analysis would be wasted work.
message → text
The only field where you actually want tokenization and analysis — searching for words inside log messages.

Now watch what happens when an unexpected field arrives:

Document with unmapped field
PUT /logs/_doc/1
{
  "event_id":         "EVT-00123",
  "level":            "info",
  "message":          "User login successful",
  "timestamp":        "2024-03-15T10:30:00Z",
  "service":          "auth",
  "duration_ms":      42,
  "unexpected_field": "oops"   // ← not in mapping
}
Elasticsearch Response — Strict Mode Rejection
{
  "error": {
    "type":   "strict_dynamic_mapping_exception",
    "reason": "mapping set to strict, dynamic introduction of
              [unexpected_field] within [_doc] is not allowed"
  }
}

Clear, immediate, actionable

You know exactly what went wrong and where to fix it — either add the field to the mapping intentionally, or fix the document producer that’s sending unexpected data.

A valid document indexes cleanly, and your aggregation query works exactly as expected:

Aggregation — errors by service
GET /logs/_search
{
  "size": 0,
  "aggs": {
    "errors_by_service": {
      "filter": { "term": { "level": "error" } },
      "aggs": {
        "by_service": {
          "terms": { "field": "service" }
        }
      }
    }
  }
}
Elasticsearch Response — Works perfectly
{
  "aggregations": {
    "errors_by_service": {
      "doc_count": 142,
      "by_service": {
        "buckets": [
          { "key": "payment", "doc_count": 87 },
          { "key": "auth",    "doc_count": 34 },
          { "key": "search",  "doc_count": 21 }
        ]
      }
    }
  }
}

This works reliably because level and service are both keyword fields — designed for exactly this kind of aggregation.


Best Practices for Elasticsearch Mapping

Start dynamic, finish explicit

Use dynamic mapping while exploring your data. Once your schema stabilizes, create an explicit mapping and reindex. Don’t ship dynamic mapping to production if you can avoid it.

Always inspect what dynamic mapping created

After indexing a few documents dynamically, run GET /your-index/_mapping and review every field. Look for strings that should be keyword only, or numbers mapped as long that should be double.

Use KEYWORD for filter / aggregate / sort fields

Only use text where you genuinely need full-text search. Status codes, identifiers, category names, country codes — these should be keyword, not the default double mapping.

Use index: false for fields you store but don't search

Metadata fields, raw payloads, internal UUIDs — set "index": false. They’ll be stored in _source but won’t consume index resources.

Set ignore_malformed: true for fault-tolerant pipelines

When ingesting data from external sources you don’t fully control, this lets Elasticsearch skip malformed values instead of rejecting the entire document.

Use dynamic: "false" as a middle ground

With "strict", unknown fields are rejected. With "false", they’re silently ignored — stored in _source but not indexed. Good for semi-structured data.

Escape hatch pattern

Use dynamic: "strict" at the root and create a dedicated metadata object with dynamic: true for flexible fields. This gives you schema enforcement where it matters and flexibility where you need it.
The escape hatch pattern
"mappings": {
  "dynamic": "strict",       // enforce schema at root
  "properties": {
    "event_id": { "type": "keyword" },
    "message":  { "type": "text"    },
    "metadata": {              // flexible escape hatch
      "type":    "object",
      "dynamic": true          // dynamic fields allowed here
    }
  }
}

Common Mapping Mistakes in Elasticsearch

Treating the first document as representative

Dynamic mapping is permanently defined by the first document that contains a field. If your first document has "price": "149.99" (a string), price is mapped as text. Every future document sending "price": 149.99 (a number) will fail. Always index a representative sample — or use explicit mapping — before going to production.

 Ignoring the text + keyword double mapping

Dynamic mapping maps all non-date strings as text with a .keyword sub-field. A large index with thousands of string fields, each double-mapped, uses significantly more disk and heap. Audit your mappings for fields that don’t need both.

Thinking you can change a field type by sending new data

You cannot change an existing field’s type by indexing a document with a different value. Elasticsearch rejects the document with a type conflict error. The only path is to create a new index with the correct mapping and run a reindex. Many developers discover this the hard way.

Forgetting that arrays don't have a special type

Any field can hold a single value or an array of values — but dynamic mapping infers the type from the first element. If your first document has "tags": ["search", "elk"], Elasticsearch maps tags as text + keyword. A later document sending "tags": [1, 2, 3] fails. Mixed-type arrays need explicit mapping — and probably a data quality fix upstream.

Using dynamic: "strict" without an escape hatch

Strict mode is powerful, but if you have legitimate reasons to ingest ad-hoc fields — debug data, temporary feature flags — strict mode at the root document level will block you. Use the metadata object pattern above instead.

Conclusion

Dynamic mapping makes Elasticsearch approachable. You can start indexing without writing a single schema definition, and for many use cases — log ingestion, exploratory analysis, local development — that’s genuinely useful. But every decision dynamic mapping makes on your behalf is a decision you didn’t make consciously. Those implicit decisions accumulate and, eventually, one of them breaks something in production.

Explicit mapping is the practice of making those decisions deliberately. You choose which strings are text and which are keyword. You choose what happens when an unexpected field arrives. You choose the exact numeric type your aggregations need. That upfront investment pays off every time a query works exactly as designed.

The practical approach: use dynamic mapping to learn your data, then write an explicit mapping that encodes what you’ve learned. The two aren’t mutually exclusive — they’re stages in the lifecycle of a well-designed Elasticsearch index.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x