JSON to Elasticsearch Bulk
The _bulk API does not take a JSON array. It takes newline-delimited pairs — an action line, then the document — and it is strict about every part of that: no pretty-printing, no missing trailing newline. This builds the body from a plain array, pulls the document id out of a field you name, and adds routing or an ingest pipeline when you need them.
How to build a bulk request
- Paste a JSON array of documents. A single object works too and produces one pair.
- Name the index. It goes into every action line as
_index, so one body can only target one index here. - Name the id field if your documents carry their own identifier — without it the cluster generates one, and re-running the import creates duplicates rather than replacing.
- Download and send it:
curl -H 'Content-Type: application/x-ndjson' --data-binary @bulk.ndjson http://localhost:9200/_bulk. The--data-binarymatters — plain-dstrips newlines and the request fails.
Which action to use
index creates the document or replaces it entirely — idempotent as long as the ids are stable,
and the right default for a re-runnable import. create fails the individual item if the id
already exists, which is how you detect accidental re-imports rather than silently overwriting.
update emits {"doc": …, "doc_as_upsert": true}, merging your fields into whatever
is already there and creating the document if it is absent — use it when your array holds partial records and
the index has fields you must not clobber.
The format rules that bite
Every line must be on one line. A pretty-printed document breaks the parser, because the API
splits on newlines to find the next action. The output here is always compact.
The body must end with a newline — a missing final newline is the single most common cause of
a bulk request being rejected, and it is easy to lose when copying by hand rather than downloading.
Partial failure is normal. The response has a errors flag and a per-item status;
a 200 does not mean every document indexed. Always check the item statuses.
Batch size is the other practical limit. Somewhere between 5 and 15 MB per request is the usual sweet spot — split larger files rather than sending one enormous body. This format is identical for OpenSearch, which forked from Elasticsearch and kept the bulk API unchanged.
Privacy
Nothing is uploaded. The conversion runs in this tab using your own browser's JavaScript engine — no server sees your data, and this page never contacts your cluster.
FAQ
Why is the id also inside the document?
Because most mappings want it queryable. Tick remove the id field if you would rather rely on the metadata id alone — Elasticsearch stores _id either way, but it is not part of _source unless you keep it.
Can I target several indices in one body?
The API allows it, this page does not — one index goes into every action line. For a multi-index import, split the array by target with a filter and run one pass per index, then concatenate the outputs.
What about delete actions?
Delete lines have no document body, so they do not fit the pair-per-record shape here. They are a single line each — {"delete":{"_index":"i","_id":"1"}} — and are easy to generate from a list of ids with a template.
Is this the same as NDJSON generally?
The file format is, and JSON to NDJSON produces plain one-object-per-line output. Bulk adds the interleaved action metadata lines, which is what makes it a request rather than a data file.