What this is about
With PATCH {basis}/update/{id} you change part of an object. You send only the fields that should change. Everything else stays as it is.
PATCH /api/rest/crm/customer/update/5a2b…
{
"data": { "id": "5a2b…", "email": "neu@muster.de" },
"response": ["+"]
}{
"data": {
"id": "5a2b…",
"_createdOn": "2026-03-02 09:14:00",
"_updatedOn": "2026-09-21 10:25:07",
"name": "Muster GmbH",
"email": "neu@muster.de",
"phone": "+49 611 123456",
"note": "Stammkunde"
},
"meta": { "error": false }
}name, phone and note are missing in the PATCH, so they are kept. The response is 200. As with every write, response is required.
How CDMS tells “missing” from “null”
With PATCH, CDMS does not read data into a fixed object with all fields. It reads it as a simple list of keys and values. Then it goes through the fields of the model and asks for each one:
-
1CDMSIs the key in
data? -
2CDMSno → the field is skipped, not changed, not checked
-
3CDMSyes, with
null→ the field is cleared -
4CDMSyes, with a value → the value is converted to the field type, checked and set
CDMS ignores keys that the model does not know. Values come in the usual JSON way: a date as "2026-09-21", a timestamp as "2026-09-21 10:25:07", an enum as the name of the value.
The matrix: field kind × state
| Simple field | Single reference | List | |
|---|---|---|---|
| Key is missing | unchanged | unchanged | unchanged |
| Value is null | is cleared | is detached; a dependent child is deleted | all members removed, exactly like [] |
| Value is set | is set | points to the named object | becomes exactly this list (target state) |
So for lists, the target state applies in PATCH too: if you name a list, you name all of it. You cannot add single entries without naming the others. See Lists as target state.
Before, payload, after
A contact with a first name, a last name, a main phone (dependent child) and two more phones (dependent children in a list).
| Field | before | in the PATCH | after |
|---|---|---|---|
firstname | “Daniel” | missing | “Daniel” |
lastname | “X” | "Mertins" | “Mertins” |
birthday | 1980-04-01 | null | null |
mainPhone | Phone P1 | missing | Phone P1 |
phones | P2, P3 | [{ "id": P2, "number": "0611-17277000" }] | only P2, with the new number; P3 is deleted |
PATCH /api/rest/crm/contact/update/c052…
{
"data": {
"id": "c052…",
"lastname": "Mertins",
"birthday": null,
"phones": [
{ "id": "p2…", "number": "0611-17277000" }
]
},
"response": ["+", { "field": "phones", "response": ["+"] }]
}Child objects in a PATCH
When: You name an existing child, e.g. in a list or as a single reference.
-
1Client→CDMSsends
"mainPhone": { "id": "p1…", "lastContact": null } -
2CDMS→Databaselooks up the object
p1… -
3CDMS→Clientdoes not exist → 404
missing-object-for-field|p1…|mainPhone -
4CDMSexists → the child is handled with PATCH rules as well: only
lastContactis cleared,numberstays
Result: For the child, only what you name changes. { "id": "p1…" } alone changes nothing on the child. If the relation does not allow changing the children (flag UPDATE missing), the child is only linked and its fields stay untouched, as a single reference and in a list alike.
When: You want to create a new child.
-
1Client→CDMSsends
"phones": [{ "id": "p2…" }, { "number": "0611-555" }] -
2CDMSdoes the relation allow creating (flag CREATE)? → creates the new phone, with default values and CREATE rules
-
3CDMS→Clientwithout the flag CREATE → 400
recursive-create-not-allowed|phones, nothing is saved -
4CDMSremoves all phones that are not in the list
Result: With the flag CREATE, there are exactly two phones afterwards: P2 and the new one.
When: You want to detach a reference.
-
1Client→CDMSsends
"mainPhone": null -
2CDMSdependent child? → is deleted, with its DELETE hooks
-
3CDMSotherwise → only the link is detached
Result: mainPhone is empty. Whether the phone still exists is defined by the relation.
When a relation is allowed to create or change at all is described in The four cases in nested writing.
The flow
The stations are the same as for PUT: visibility (404), role to change (403), transfer fields, before hooks, validation (422), save, read back. There are two differences:
- First, CDMS checks whether
datahas anidkey at all. If it is missing, you get 400missing-idright away. - Validation only sees the fields you sent. A required field that you do not send is not checked. A required field that you set to
nullreturns 422cannot-be-null.
Decision table
| id in data | object visible | role to change | sent fields valid | Response |
|---|---|---|---|---|
| no | – | – | – | 400 missing-id |
| yes | no | – | – | 404 not-found |
| yes | yes | no | – | 403 missing-permission|<rolle> |
| yes | yes | yes | no | 422 validation-failed |
| yes | yes | yes | yes | 200, only the named fields are changed |
As with PUT, the id in data counts, not the one in the path. _updatedOn is set to now. Fields starting with _ and fields with the rule @noUpdate stay unchanged.
Pitfalls
Where to go next
- Replace the whole object: Replacing with PUT
- Which verb when: PUT or PATCH? The null trap
- What gets checked: Validation