The Ultimate Guide to Managing Elasticsearch Indices via API Commands

Master Elasticsearch index management with this ultimate guide to API commands. Learn how to meticulously create indices with custom mappings and settings using `PUT`, comprehensively view their configurations and details with `GET`, and safely delete unnecessary indices using `DELETE`. This article provides practical examples, best practices, and crucial warnings, empowering you to effectively control your data's lifecycle within Elasticsearch for optimal performance and resource management.

The Ultimate Guide to Managing Elasticsearch Indices via API Commands

Managing Elasticsearch indices through the API is ordinary work, but it is also where many expensive mistakes happen. A bad mapping can force a reindex. A wildcard delete can remove more than you meant. A replica setting that made sense in development can leave production yellow after a deployment.

An Elasticsearch index is not just a bucket of JSON documents. It has mappings, settings, aliases, shard counts, replica counts, analyzers, and lifecycle behavior. You do not need to memorize every API to manage it well, but you do need a careful routine: create indices intentionally, inspect what Elasticsearch actually created, update only the settings that can be changed safely, and delete with guardrails.

The examples below use Kibana Dev Tools style requests. If you prefer curl, the same paths and JSON bodies apply against http://host:9200.

What an index contains

An index is a logical namespace for documents. Under the hood, primary shards hold the data, and replica shards copy that data for redundancy and search capacity. The exact default shard and replica behavior can vary by Elasticsearch version and deployment mode, so do not rely on memory. Check the settings in your own cluster.

The three pieces you will inspect most often are:

  • settings, such as number_of_shards, number_of_replicas, refresh_interval, and analysis configuration.
  • mappings, which define field types such as keyword, text, date, long, double, boolean, ip, and nested/object fields.
  • aliases, which let applications use a stable name while you swap the real backing index behind it.

A good index API workflow starts before the first document is indexed. Dynamic mapping is useful for exploration, but production indices deserve explicit mappings for important fields. If user_id is accidentally mapped as text, aggregations and exact filters become awkward. If a timestamp is mapped as text, time-range queries will not behave like date queries. Those mistakes are fixable, but usually by creating a new index and reindexing.

Create a simple index

The smallest create request is:

PUT /my_first_index

Elasticsearch returns an acknowledgement if the index was created:

{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "my_first_index"
}

That is fine for a scratch index. For real data, create the index with the settings and mappings you expect rather than letting the first few documents define the shape.

Create an index with mappings and settings

Here is a practical products index. It supports full-text search on names and descriptions, exact filtering on IDs and categories, date sorting, numeric range filters, and simple availability checks.

PUT /products-v1
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "refresh_interval": "5s"
  },
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "product_id": { "type": "keyword" },
      "sku": { "type": "keyword" },
      "name": {
        "type": "text",
        "fields": {
          "raw": { "type": "keyword", "ignore_above": 256 }
        }
      },
      "description": { "type": "text" },
      "category": { "type": "keyword" },
      "price": { "type": "scaled_float", "scaling_factor": 100 },
      "stock": { "type": "integer" },
      "available": { "type": "boolean" },
      "created_at": { "type": "date" },
      "updated_at": { "type": "date" }
    }
  }
}

A few choices here are deliberate. product_id, sku, and category are keyword because you normally filter, aggregate, or join application behavior around exact values. name is text for search, with a name.raw keyword subfield for sorting and exact matching. price uses scaled_float so prices can be stored as cents-like scaled values instead of binary floating-point approximations. dynamic: strict rejects unknown fields, which is useful when bad producer data should fail loudly.

Do not copy the shard count blindly. Three primary shards may be too many for a small index and too few for a large one. Shard count is hard to change after creation without a shrink, split, or reindex-style migration, so size it from expected data volume, retention, and node count.

Inspect an index after creation

Always verify what Elasticsearch accepted:

GET /products-v1

That returns settings, mappings, and aliases. For focused checks, use narrower endpoints:

GET /products-v1/_mapping
GET /products-v1/_settings
GET /products-v1/_alias

For a compact command-line view of many indices:

GET /_cat/indices/products-*?v

Useful columns include health, status, primary shard count, replica count, document count, deleted document count, and store size. _cat APIs are meant for humans. Use JSON APIs for scripts.

If you are checking whether a field is mapped correctly, use:

GET /products-v1/_mapping/field/price
GET /products-v1/_mapping/field/name.raw

That is faster to read than scrolling through a large mapping.

Update index settings safely

Some settings are dynamic. Replica count is the common one:

PUT /products-v1/_settings
{
  "index": {
    "number_of_replicas": 2
  }
}

This can turn a green cluster yellow if you do not have enough suitable data nodes. For example, one primary plus two replicas needs three separate places to put copies. Allocation awareness rules can make the requirement stricter.

refresh_interval is another setting you may change during bulk loads:

PUT /products-v1/_settings
{
  "index": {
    "refresh_interval": "30s"
  }
}

A longer refresh interval can reduce indexing overhead, but newly indexed documents become visible to search less quickly. For a bulk backfill, teams often increase or temporarily disable refreshes, load data, then restore the normal interval and refresh once:

POST /products-v1/_refresh

Some settings are static. number_of_shards cannot be changed on an open existing index. If you discover the primary shard count is wrong, plan a new index and migration.

Change mappings without lying to yourself

You can add new fields to a mapping:

PUT /products-v1/_mapping
{
  "properties": {
    "brand": { "type": "keyword" }
  }
}

You generally cannot change the type of an existing field in place. If price is already text, this will not turn old values into a useful numeric field. Create a new index with the correct mapping and reindex:

POST /_reindex
{
  "source": { "index": "products-v1" },
  "dest": { "index": "products-v2" }
}

For production migrations, use aliases so the application does not need to know the physical index version.

Use aliases for safer application access

Point an alias at the first version:

POST /_aliases
{
  "actions": [
    { "add": { "index": "products-v1", "alias": "products" } }
  ]
}

Your application reads from products. Later, create products-v2, reindex data, test it, then swap the alias atomically:

POST /_aliases
{
  "actions": [
    { "remove": { "index": "products-v1", "alias": "products" } },
    { "add": { "index": "products-v2", "alias": "products" } }
  ]
}

For write-heavy systems, use a write alias and be explicit about is_write_index:

POST /_aliases
{
  "actions": [
    { "add": { "index": "products-v2", "alias": "products-write", "is_write_index": true } }
  ]
}

Aliases are one of the simplest ways to avoid hard-coding index versions into services.

List and search index names carefully

To inspect multiple indices:

GET /products-v1,products-v2/_settings
GET /products-*/_mapping
GET /_cat/indices/products-*?v&s=index

Be careful with broad wildcards on large clusters. GET /* or GET /_all can produce a huge response and may include system or hidden indices depending on version, settings, and request options. Prefer a narrow pattern such as logs-prod-* or products-*.

To check whether an index exists from a script, use HEAD:

HEAD /products-v1

A 200 means it exists. A 404 means it does not.

Delete indices with guardrails

Delete is simple:

DELETE /products-v1

The operational risk is not syntax. The risk is deleting the wrong index or deleting before a snapshot completes.

Before deleting, list exactly what the pattern matches:

GET /_cat/indices/products-old-*?v&s=index

If the output is right and the data is recoverable or no longer needed, delete the same pattern:

DELETE /products-old-*

Many clusters restrict destructive wildcard operations with action.destructive_requires_name. That is a good safety setting because it blocks broad deletes such as DELETE /* unless explicit names are required. Even with that protection, treat delete operations as production changes: confirm the index pattern, confirm snapshots, and confirm the caller has the right permissions.

Prefer lifecycle policies for time-series data

Manual deletes are acceptable for one-off cleanup. For logs, metrics, traces, and other time-series data, use Index Lifecycle Management or data streams where appropriate. A lifecycle policy can roll over an index when it reaches an age or size target, move older data to a different tier, and delete it after retention expires.

That matters because manual cleanup tends to fail at the worst time. Someone forgets it, disk fills, flood-stage watermarks make indices read-only, and then the team is cleaning up under pressure. Lifecycle policies turn retention into configuration instead of a calendar reminder.

A simple daily routine

For a production cluster, a practical index management routine looks like this:

Check health:

GET /_cluster/health

List indices by pattern:

GET /_cat/indices/logs-*?v&s=store.size:desc

Inspect suspicious settings:

GET /logs-prod-2026.05.24/_settings

Check mappings before adding new fields from an application release:

GET /logs-prod-2026.05.24/_mapping

Confirm aliases before and after a migration:

GET /_cat/aliases/products*?v

This is not glamorous work, but it catches many problems early: wrong replica counts, accidental dynamic fields, stale aliases, and old indices that should have aged out.

Common mistakes to avoid

Do not let development defaults become production architecture. A single-node cluster, zero replicas, and dynamic mappings may be fine for a demo. They are not a recovery plan.

Do not change mappings in your head and assume Elasticsearch agrees. Inspect the actual mapping.

Do not use wildcard deletes without listing the matching indices first.

Do not set high replica counts without enough data nodes and zones to place them.

Do not skip aliases for indices that applications depend on. The first migration will be much easier if the application already talks to an alias.

Good index management is mostly disciplined repetition. Create with intent, inspect what exists, migrate with aliases, automate retention, and make destructive actions boringly explicit.

Templates matter more than one-off creates

If you create the same kind of index repeatedly, do not rely on manual PUT /index-name calls. Use an index template or composable templates so new indices receive the right mappings, settings, aliases, and lifecycle policy automatically.

A log platform is the classic example. If logs-web-2026.05.24 has a correct @timestamp mapping but tomorrow's index is created dynamically by the first document, one malformed event can map a field incorrectly. The mistake may not show up until dashboards fail or aggregations disappear. Templates prevent that drift.

A simple template might define a pattern, settings, and mappings for future indices:

PUT /_index_template/logs_web_template
{
  "index_patterns": ["logs-web-*"],
  "template": {
    "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 1
    },
    "mappings": {
      "properties": {
        "@timestamp": { "type": "date" },
        "service": { "type": "keyword" },
        "level": { "type": "keyword" },
        "message": { "type": "text" },
        "trace_id": { "type": "keyword" }
      }
    }
  }
}

Templates are also easier to review in code. Put them in version control, deploy them through the same path as application changes, and test them before rollover creates the next backing index.

Reindexing without surprising the application

The cleanest index migration is usually: create a new versioned index, reindex data, compare counts and sample queries, then swap an alias. Do not point the application directly at products-v1 unless you enjoy emergency config changes.

A careful migration looks like this:

PUT /products-v2
{
  "mappings": {
    "properties": {
      "product_id": { "type": "keyword" },
      "name": { "type": "text" },
      "price": { "type": "scaled_float", "scaling_factor": 100 }
    }
  }
}

Then reindex:

POST /_reindex?wait_for_completion=false
{
  "source": { "index": "products-v1" },
  "dest": { "index": "products-v2" }
}

Using wait_for_completion=false returns a task ID so you can monitor a long reindex. After it finishes, compare document counts and run representative searches. Only then swap aliases.

For write-heavy indices, think through dual writes, pause windows, or replay from a source of truth. The API commands are easy; the consistency plan is the real work.

Security and permissions

Index APIs should not be available to every application user. A search service may need read privileges on an alias. A data ingester may need create, index, and write privileges on a write alias. Very few identities should have broad delete or manage privileges.

This matters because index APIs are powerful. A compromised application credential with manage privileges can change replica settings, close indices, or delete data. Keep destructive privileges separate, and make production deletion require an operator workflow rather than an application path.

Naming conventions reduce mistakes

Use names that encode purpose and lifecycle: products-v3, logs-web-2026.05.24, metrics-api-000123, or data-stream names that match the data source. Avoid vague names like newindex, test2, or prod-final. During cleanup, vague names make it hard to know what is safe to delete.

For time-series indices, use consistent date formats and avoid mixing local time and UTC assumptions. For versioned business indices, keep the alias stable and let the physical index version change behind it. A boring naming convention is an operational safety feature.