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 keyword cannot be queried as text later (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

Use 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

Use 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)

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

Type
Use for

integer

Whole numbers within ±2 billion

long

Whole numbers outside integer range

float

Approximate decimals (scores, ratings)

double

High-precision decimals

date

Elasticsearch stores dates as milliseconds since epoch internally. You can use ISO8601 strings in documents:

boolean

object

A 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

Use 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: 0 on 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:

  1. Create articles_v2 with the updated mapping.

  2. Run _reindex from articles to articles_v2.

  3. Update the alias articles_alias to point to articles_v2.

  4. 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) and keyword (exact match, sorting, aggregation) intentionally.

  • Use multi-fields (title + title.keyword) when you need both behaviors on the same field.

  • nested type 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