Indexing Documents and Mappings
Series: Elasticsearch 101 | Article: 03
Overview
Mappings define the schema of documents in an Elasticsearch index. Get them right before you ingest data — changing field types requires a full reindex, which for large datasets is expensive and requires careful planning.
This article covers the essential field types, how to define explicit mappings, and the full CRUD lifecycle for documents.
Dynamic vs Explicit Mappings
By default, Elasticsearch infers types when you index a new field — this is called dynamic mapping. Index a document with a date string and Elasticsearch guesses it is a date. Index a number and it creates an integer or float field.
Dynamic mapping is convenient for getting started but problematic in production:
A field that starts as
keywordcannot be queried astextlater (and vice versa).Dynamic mapping can create hundreds of fields from uncontrolled JSON (the "mapping explosion" problem).
It does not give you control over analyzers, normalizers, or field options.
Always use explicit mappings for production indexes. Disable dynamic mapping for indexes where the field set is known:
{
"mappings": {
"dynamic": "strict"
}
}With "dynamic": "strict", indexing a document that contains an unknown field will throw an error immediately — which is the correct behavior for catching schema drift early.
Essential Field Types
text
textUse for full-text content that needs to be searched. The value is analyzed (tokenized, lowercased, stemmed) and added to the inverted index.
A text field cannot be used for sorting, aggregations, or exact-match lookups.
keyword
keywordUse for exact-match values: IDs, status codes, tags, category names, email addresses. The value is stored as-is, not analyzed.
Use keyword for any field you plan to aggregate on, sort by, or filter with a term query.
text + keyword (multi-field)
text + keyword (multi-field)A common pattern is to index the same field as both text (for full-text search) and keyword (for sorting/aggregations):
Query with title for full-text, title.keyword for exact match or sort.
Numeric Types
integer
Whole numbers within ±2 billion
long
Whole numbers outside integer range
float
Approximate decimals (scores, ratings)
double
High-precision decimals
date
dateElasticsearch stores dates as milliseconds since epoch internally. You can use ISO8601 strings in documents:
boolean
booleanobject
objectA JSON object embedded in the document. Object fields are flattened internally — there is no isolation between array elements. For array-of-objects queries to work correctly across fields, use nested instead.
nested
nestedUse when you have an array of objects and need to query across fields within the same object:
nested fields have a performance cost — each nested object is stored as a separate hidden document. Use them deliberately and only when needed.
Creating an Index with an Explicit Mapping
All examples below use the Kibana Dev Tools console or curl. The index models a blog article.
number_of_replicas: 0on a single-node cluster prevents yellow status for this index. Set it to 1 for production with multiple nodes.
Document CRUD
Index (Create) a Document
Use a specific ID to match the primary key from your source-of-truth database:
Using PUT /<index>/_doc/<id> is idempotent — it creates or replaces. To create only (fail if exists), use POST /<index>/_create/<id>.
Retrieve a Document
Returns the document plus metadata (_index, _id, _version, _source).
To retrieve only the _source body:
Update a Document (Partial)
The _update API merges the provided fields with the existing document. Only the changed fields need to be sent. Under the hood, this is still a delete + rewrite in Lucene.
Delete a Document
Bulk Operations
For inserting or updating multiple documents at once, use the Bulk API. It is significantly more efficient than individual requests:
The bulk body alternates: one action line, one document line, one action line, one document line... Each line is a separate JSON object (newline-delimited, not an array).
Verify the Mapping
After index creation:
Adding Fields to an Existing Mapping
You can add new fields to a strict mapping, but you cannot change the type of an existing field:
To change an existing field's type — for example, changing author_name from text to a text + keyword multi-field — you need to reindex.
Reindexing
Reindexing copies documents from one index to another, applying the new mapping as documents are written:
The common pattern I use in production:
Create
articles_v2with the updated mapping.Run
_reindexfromarticlestoarticles_v2.Update the alias
articles_aliasto point toarticles_v2.Delete the old index.
Using aliases instead of hardcoded index names from the start makes this operation zero-downtime.
Index Aliases
An alias is a named pointer to one or more indexes. Applications query the alias, not the index directly:
At reindex time:
These two actions are atomic from the client's perspective.
Summary
Use
"dynamic": "strict"to prevent unexpected field creation.Choose between
text(full-text search) andkeyword(exact match, sorting, aggregation) intentionally.Use multi-fields (
title+title.keyword) when you need both behaviors on the same field.nestedtype enables cross-field queries on array-of-object fields, but adds storage overhead.Use controlled IDs matching your source database primary key.
Always use aliases rather than direct index names — it simplifies reindexing.
Previous: Setting Up Elasticsearch with Docker | Next: Search Queries Deep Dive
Last updated