---
question: How do I upsert a document in a collection?
kind: task
via_web: available
via_cli: na
via_python: available
via_api: available
via_config: na
ref_web: System › Data › Documents
ref_python: documents.put
ref_api: PUT /api/admin/system/resources/document/entry
prereq: For the Python steps, an in-engine module that lists `DocumentService` in its dependencies. [Create a module](/user-guide/build/modules/creating-a-module) first.
next:
  - /capabilities/build/platform/query-documents
  - /capabilities/build/platform/documents-overview
---

# Store a document

A document is a JSON-like record under an id, in a collection of the module's namespace. The module declares the collection, then writes documents into it. A super admin can add or fix one by hand in Nexus or over the Nexus API, in a collection that already exists.

## Web UI

Super admins only. Nexus cannot create a collection: the module that owns it does.

### Open the collection

In Nexus, open **System** in the sidebar, then the **Data** tab, then **Documents**. Open the module's namespace, then the collection. The table shows the declared fields.

![System app, Data tab, on Documents](/abi/Screenshot_System_Documents.png)

*System › Data › Documents, filtered to the core namespaces. Each card is a namespace with its collections.*

### Add a document

Choose **New document**. Enter the id and the JSON:

```json title="New document"
{
  "title": "Hello",
  "status": "draft"
}
```

Datetimes and bytes are tagged: `{"$t": "datetime", "$v": "2026-10-02T08:00:00+00:00"}`. Choose **Create**.

### Edit or delete it

Select the document. Edit replaces the whole document; Nexus asks you to type the id back first. Delete asks the same.

## CLI

Not applicable. There is no `abi` command for the document service. Use another tab on this page. The command list is the [CLI reference](/user-guide/reference/cli).

## Python

### Declare the service

```python title="src/my_module/__init__.py"
from naas_abi_core.module.Module import BaseModule, ModuleDependencies
from naas_abi_core.services.document.DocumentService import DocumentService


class ABIModule(BaseModule):
    dependencies: ModuleDependencies = ModuleDependencies(
        modules=[],
        services=[DocumentService],
    )
```

### Ensure the collection

```python
from naas_abi_core.services.document.DocumentPort import CollectionSpec, FieldSpec

documents = self.engine.services.document
documents.ensure_collection(
    CollectionSpec(
        name="notes",
        fields=(
            FieldSpec(name="title", type="string", indexed=True),
            FieldSpec(name="created", type="datetime"),
        ),
    )
)
```

Run it before the first write, for example in `on_initialized`. Writing to a collection that was never ensured raises `CollectionNotFound`. Field types are `string`, `int`, `float`, `bool`, `datetime`, `bytes` and `json`. `unique=True` on a field, or `unique_together`, adds a uniqueness rule.

### Write the document

```python
doc = documents.put("notes", "n1", {"title": "Hello"})
doc.version   # 1
```

`put` replaces the whole document. `if_version` is keyword-only:

| `if_version` | Effect |
|---|---|
| `None` (default) | Upsert |
| `0` | Create only. An existing id raises `VersionConflict`. |
| `n > 0` | Write only when the stored version is `n`, then `n + 1`. Otherwise `VersionConflict`. |

`put_many("notes", {"n2": {...}, "n3": {...}})` writes several documents, but not in one transaction.

### Read and delete

```python
doc = documents.get("notes", "n1")   # Document(id, data, created_at, updated_at, version)
documents.exists("notes", "n1")
documents.delete("notes", "n1", if_version=doc.version)
```

`get` raises `DocumentNotFound`. `delete` without `if_version` does nothing when the id is missing.

## REST API

The System routes of the Nexus API. Auth: a Nexus session of a super admin (`POST /api/auth/login` returns the access token). Every change is audited.

### Write the document

The `id` is `<namespace>/<collection>/<document id>`. The body is the document:

```bash
curl -X PUT "http://localhost:9879/api/admin/system/resources/document/entry?id=my_module/notes/n1" \
  -H "Authorization: Bearer $NEXUS_TOKEN" \
  --data-binary '{"title": "Hello"}'
```

With `abi dev up`, use the `api` port that `abi dev ports` prints. A collection that does not exist answers 400. An id that exists answers 409: add `&confirm=my_module/notes/n1` to replace it. The write is an upsert, without `if_version`.

### Delete it

```http
DELETE /api/admin/system/resources/document/entry?id=my_module/notes/n1&confirm=my_module/notes/n1
```

## Config

Not applicable. `services.document` in `config.yaml` picks the backend, not the documents. See [Document and Dataset](/user-guide/get-started/configuration#document-and-dataset).
