Updating parts of documents
This guide shows you how to update specific fields, nested object values, and locale variants in existing documents without resending the entire document. You can change documents in a batch, or change one document by its key.
The single rule that governs all partial updates is that the path replaces exactly what it names and leaves everything around it unchanged. How deeply you name decides how much you replace.
Prerequisites
Section titled “Prerequisites”Before you update parts of documents, ensure you have:
- An index definition that declares a primary key.
- An index configured to store document sources (
sourceis not set tonone), unless you update only signal fields. - The
documents.writepermission for the index.
The examples below use a products index whose variants object field declares "key": "sku". Where a field declares no key, use the field=value selector form shown alongside each example.
-
Replace a whole field:
To replace a field or clear its value, send a
POSTrequest to/v1alpha1/indexes/{name}/documents/actions/updatewith the document primary key and the field name. Setting a field value tonullempties that field.POST /v1alpha1/indexes/products/documents/actions/updateContent-Type: application/json{"documents": [{"id": "1","price": 34.50,"discount": null}]} -
Change a field inside a specific nested object:
To update a field inside one object in a list of objects, use a selector path. When a field definition declares a
key, use the key on its own without a field name or=, asfield[key_value].inner_field(such asvariants[V-2].price). Otherwise, use thefield[match_field=match_value].inner_fieldform. For how to declare a key on an object field, see Object in the field types reference.POST /v1alpha1/indexes/products/documents/actions/updateContent-Type: application/json{"documents": [{"id": "1","variants[V-2].price": 29.0}]}Paths use selectors instead of array positions so that updates do not depend on array order or break when items are added, removed, or reordered. A caller that sends only what changed does not know the positions in the first place.
Because keys are unique inside a document, a key path names at most one value. The
field=valueform can match several values and updates all of them. Thefield=valueform keeps working on a keyed field and can name any child field, not only the key. Using a key path on a field that declares nokeyreturnsdocument:patch:key_unsupported.For the escaping rules and how to write them in JSON, see Change paths.
-
Replace or remove an entire nested object:
To replace all fields of a specific nested object in a list, name the object using a selector without specifying an inner field. To remove the matched object from the list, set the selector path to
null.POST /v1alpha1/indexes/products/documents/actions/updateContent-Type: application/json{"documents": [{"id": "1","variants[V-2]": { "sku": "V-2", "price": 29.0 },"variants[V-3]": null}]}Replacing an object whole removes any fields omitted in the new object value. The replaced object retains its original position in the list.
-
Add a value to a multi-value field:
To append an item to a field declared as
multiplewithout replacing existing entries, use empty brackets[].POST /v1alpha1/indexes/products/documents/actions/updateContent-Type: application/json{"documents": [{"id": "1","variants[]": { "sku": "V-4", "price": 40.0, "color": "red" }}]}Added values are placed at the end of the existing list.
-
Update or remove a locale variant:
To change the value of a single language in a locale-specific field, specify the BCP 47 tag in brackets. To remove a language variant, set the path to
null.POST /v1alpha1/indexes/products/documents/actions/updateContent-Type: application/json{"documents": [{ "id": "1", "title[sv]": "Blåbärssylt II" },{ "id": "2", "title[sv]": null }]}Locale tags resolve against the variants declared in the field definition, so
title[nb-NO]changes a field that holdsno. A tag the field holds no variant for returnsdocument:locale_unknown. -
Update a field inside a single object:
For fields that hold a single (non-multiple) object rather than an array of objects, use dot notation without a selector.
POST /v1alpha1/indexes/products/documents/actions/updateContent-Type: application/json{"documents": [{"id": "1","dimensions.width": 12.0}]}This updates the specified inner field while leaving the remaining fields of the object intact.
-
Batch multiple updates with NDJSON:
To stream large update batches, use newline-delimited JSON (
application/x-ndjson) with one JSON object per line. To prevent the request from failing if some document keys do not exist, append?missing=skip.POST /v1alpha1/indexes/products/documents/actions/update?missing=skipContent-Type: application/x-ndjson{"id": "1", "price": 34.50}{"id": "2", "variants[W-1].price": 15.00}{"id": "999", "price": 10.00}When
missing=skipis enabled, the server updates existing documents, skips missing documents, and lists the skipped keys in the response:{"updated": 2,"missing": ["999"],"failed": []}The
missingandonErrorparameters are separate controls. By default, a refused change stops the batch and returns an error. The errorargumentsprovideposition(the entry index counted from zero) andprocessed(the number of updates applied before the failure). Use these values to fix the entry and resume the batch from that position. To process the remaining entries instead of stopping at a failure, append?onError=skipand inspect the refused changes in thefailedresponse field. For the full rules, see Skipping refused entries. -
Change one document by its key:
To change a single document, send a
PATCHrequest to/v1alpha1/indexes/{name}/documents/{key}. The body is one change object holding paths only, because the URL names the document.PATCH /v1alpha1/indexes/products/documents/1Content-Type: application/json{"price": 34.50,"variants[V-2].price": 29.0}The request returns
204 No Contentwhen the document is changed. A key nothing is indexed under returns404withdocument:not_foundinstead of creating a document. Every path means what it means in a batch, so you can move between the two forms without rewriting the change. -
Refresh a ranking signal across the catalogue:
To update numeric ranking scores without rewriting entire documents, declare a number field with
"signal": {}in your index definition. When a score already lies in a known range, configure alinearranking signal:{"fields": {"popularity": { "type": "double", "signal": {}, "validation": { "min": 0, "max": 1 } }},"ranking": {"signals": [ { "field": "popularity", "linear": { "ceiling": 1 } } ]}}To refresh the scores, send an update request that names only the primary key and the signal field for each document. The engine replaces the doc values in place without reading or rewriting the document, even on an index where
sourceis set tonone:POST /v1alpha1/indexes/products/documents/actions/update?missing=skipContent-Type: application/x-ndjson{"id": "1", "popularity": 0.82}{"id": "2", "popularity": 0.07}{"id": "3", "popularity": null}Setting the field to
nullclears the score, after which the signal contributes nothing to the document ranking. Send refresh requests as a few large batches rather than many small requests, because each batch rewrites the values of the field for every segment it touches. Documents indexed whole without the signal field retain their current value. For more information, see Signal fields.
Confirming the result
Section titled “Confirming the result”To verify your updates, read the documents back with GET /v1alpha1/indexes/{name}/documents, which answers in primary key order. A change is searchable only once the index commits. For more information, see Make a write visible to search.
GET /v1alpha1/indexes/products/documents?limit=1The response returns the first document in full, with your changes applied:
{ "documents": [ { "id": "1", "title": { "sv": "Blåbärssylt II", "en": "Blueberry jam" }, "price": 34.50, "dimensions": { "width": 12.0, "height": 4.0 }, "variants": [ { "sku": "V-1", "price": 10.0, "color": "blue" }, { "sku": "V-2", "price": 29.0 }, { "sku": "V-4", "price": 40.0, "color": "red" } ] } ]}Limits
Section titled “Limits”- Full document rewrites: Partial updates save network payload size and client-side bookkeeping, but they do not reduce index write cost. The underlying index rewrites the entire Lucene document block when applying updates. For more information, see How sub-documents are stored.
- Selector matches: A selector that matches no value returns the error
document:patch:no_matchand does not create a new object. When a key path is refused, the error names the path the way it was written (for example,variants[V-404].price), not the resolvedfield=valueform. A key path names at most one value, whereas thefield=valueform updates all matching objects if multiple objects match. - Document source requirement: The index must store document sources. If
sourceis set tonone, update requests fail withdocument:source_not_kept.
Troubleshooting
Section titled “Troubleshooting”The update endpoint validates paths and returns specific error codes when a path cannot be applied:
| Error code | Cause | Action |
|---|---|---|
document:patch:no_match | The selector matched no value in the document. | Ensure the document contains an object with the specified field and value before updating, or append a new item using []. |
document:patch:key_unsupported | A key path was used on a field that does not declare a key. | Declare a key in the object field definition, or use the field=value selector syntax (for example, variants[sku=V-2].price). |
document:patch:selector_required | A dot path reached into a list of objects without a selector. | Add a selector to identify which object in the list to update (for example, variants[sku=V-2].price). |
document:patch:path_invalid | The path string is malformed or has unclosed brackets. | Check the syntax of brackets, dots, and backslash escape characters in the path. |
document:patch:field_unknown | The top-level field name does not exist in the index definition. | Verify the field name against the index schema. |
document:patch:selector_unsupported | A selector was used on a field that does not hold objects or locale variants. | Remove the selector and update the field value directly. |
document:patch:match_not_an_object | A selector matched against inner fields of a non-object field. | Use selectors only on fields containing objects. |
document:locale_unknown | The locale tag in brackets is not defined for the field. | Check the allowed locales in the index schema definition. |
document:patch:add_unsupported | The [] syntax was used on a field not declared as multiple. | Remove [] to replace the single field value. |
document:patch:add_reaches_inside | A path attempted to set a sub-field on an appended item (for example, variants[].price). | Provide the complete object when appending with []. |
document:patch:not_an_object | A dot path reached into a field that does not contain objects. | Check the field type definition in the index schema. |
document:source_not_kept | The index does not store document sources (source: none). | Reindex with source storage enabled to use partial updates. |
document:not_found | A key nothing is indexed under. A PATCH returns it with 404; a batch returns it with 400 in the errors array when missing is fail. | Index the document whole first, check the key, or set missing to skip. |
document:key_conflicting | The body of a PATCH gave the primary key field a value other than the key in the URL. | Remove the primary key from the body, or give it the key the URL names. |
Related
Section titled “Related”- Documents API - Path syntax, request schemas, response formats, and error codes.
- Field types - Field type definitions, settings, and declaring a key on object fields.
- Indexing documents - Sending whole documents, loading a dataset, and removing documents.
- Using sub-documents - Holding a list of values that are documents of their own, and searching inside them.
- Localizing fields - Holding values in several languages and searching them by locale.
- How sub-documents are stored - Why a change to one sub-document rewrites the whole document.
Exofind is built by Level Four AB and is available under the Apache License 2.0.