Write user data
Immediate writes change the data of exactly one owner. You name that owner explicitly with a target, and the write is applied right away. Use them for support tools, data corrections, and integrations that sync data for one user at a time. To change data for many users, use a bulk job.
All three write operations require the Data: Write permission or the Admin permission.
The examples on this page send operations to https://personalization.service.eu.axinom.net/graphql-management with an Authorization: Bearer <token> header.
See Get started for how to obtain a token.
Choose a write operation
| Goal | Operation | Effect |
|---|---|---|
| Store one JSON value under a key | setKeyValue | Inserts the entry or replaces its value |
| Insert or update one item of an array | upsertArrayItem | Inserts the item or updates the item with the same itemId |
| Replace the complete array with a new list of items | replaceArrayItems | Stores the given items and removes every item that you omit |
The data you write is stored in the same way as data an end user wrote with the Key-value API or the Array API. End users see it through the End-User API, and you can inspect it with the read operations.
Name the owner with a target
Every write takes a target that identifies one user's key and scope.
| Field | Required | Meaning |
|---|---|---|
userId | Always | The user that owns the data |
scope | Always | USER, PROFILE, or APPLICATION |
profileId | PROFILE and APPLICATION scope only | The profile that owns the data |
applicationId | APPLICATION scope only | The application that owns the data |
key | Always | The key the data is stored under |
The scope decides which ids you provide.
| Scope | Provide | Omit |
|---|---|---|
USER | userId | profileId, applicationId |
PROFILE | userId, profileId | applicationId |
APPLICATION | userId, profileId, applicationId | None |
Ids that the scope needs must be given, and ids that the scope does not use must be left out.
A target that breaks this rule is rejected with the TARGET_CONSTRAINT reason, and nothing is written.
Nothing is guessed or dropped silently.
The same key can hold different data in different scopes and for different owners. Writing to one target never changes data stored under another target.
The key must match ^[\w-]+(:[\w-]+)*$.
Values are limited to 10,240 bytes.
See Errors and limits for all limits.
Set a key-value
setKeyValue stores one JSON value under the target key.
It inserts a new entry when none exists for the target and replaces the stored value otherwise.
The whole value is replaced, so include every property that should remain.
mutation SetUserPreferences($input: SetKeyValueInput!) {
setKeyValue(input: $input) {
acknowledged
insertedCount
updatedCount
}
}
{
"input": {
"target": {
"userId": "11111111-1111-4111-8111-111111111111",
"scope": "USER",
"key": "ui:preferences"
},
"value": {
"theme": "dark",
"language": "en"
}
}
}
For a user who had no value under ui:preferences yet, a successful response reports one inserted entry:
{
"data": {
"setKeyValue": {
"acknowledged": true,
"insertedCount": 1,
"updatedCount": 0
}
}
}
When an entry already exists, updatedCount reports the updated entry instead of insertedCount.
setKeyValue writes JSON values only.
It never writes or changes a progress entry, even when a progress entry is stored under the same key.
Progress is read-only in the Management API.
To change only some properties of existing values, or to change values for many users, use a bulk patch.
Upsert an array item
upsertArrayItem inserts or updates one item of an array.
The item is identified by the itemId that you choose.
When no item with that itemId exists in the target array, the service inserts it.
When one exists, the service updates it.
mutation UpsertFavorite($input: UpsertArrayItemInput!) {
upsertArrayItem(input: $input) {
acknowledged
insertedCount
matchedCount
modifiedCount
deletedCount
item {
id
itemId
value
dateModified
owner {
userId
profileId
applicationId
scope
key
}
}
}
}
{
"input": {
"target": {
"userId": "11111111-1111-4111-8111-111111111111",
"scope": "USER",
"key": "content:favorites"
},
"itemId": "movie:100",
"value": {
"title": "Example Movie",
"addedBy": "support"
},
"sortString": "Example Movie",
"sortDate": "2026-11-05T10:30:00Z"
}
}
The itemId must match ^[\w-]+(:[\w-]+)*$ and can be at most 256 characters.
It does not need to appear in the value.
Use a stable domain identifier such as movie:100.
The response fields describe what happened.
| Field | Meaning |
|---|---|
acknowledged | Whether the database accepted the write |
insertedCount | 1 when a new item was inserted, otherwise 0 |
matchedCount | 1 when an existing item was matched, otherwise 0 |
modifiedCount | The number of items the update modified |
deletedCount | Older duplicate items removed while converging on the itemId |
item | The inserted or updated item, including its owner |
Sort fields
The optional sort fields are sortString, sortNumber, and sortDate.
They follow three rules on an update.
- If you omit a sort field, the stored value stays as it is.
- If you send a value, the stored value is replaced.
- If you send
null, the stored value is removed.
The following variables update only the value and clear the date, while the other sort fields stay unchanged:
{
"input": {
"target": {
"userId": "11111111-1111-4111-8111-111111111111",
"scope": "USER",
"key": "content:favorites"
},
"itemId": "movie:100",
"value": {
"title": "Example Movie",
"addedBy": "support"
},
"sortDate": null
}
}
Date inputs accept the formats listed in Errors and limits.
Older items
Older array items can lack a stored itemId and be identified by a value.id.
When the itemId you upsert matches such an item, the service updates that item to use the itemId, in the same way the End-User API does.
Matching duplicates are removed, which deletedCount reports.
See Migrate from deprecated Array APIs for background.
Replace a whole array
replaceArrayItems replaces the complete array of one target with the list of items you send.
Replacement removes every existing item that is not in your list. Send the full set of items that the array should contain after the call. Items that you retain are written again from the values you send.
mutation ReplaceFavorites($input: ReplaceArrayItemsInput!) {
replaceArrayItems(input: $input) {
acknowledged
insertedCount
items {
id
itemId
value
dateModified
owner {
userId
scope
key
}
}
}
}
{
"input": {
"target": {
"userId": "11111111-1111-4111-8111-111111111111",
"scope": "USER",
"key": "content:favorites"
},
"items": [
{
"itemId": "movie:100",
"value": { "title": "Example Movie" },
"sortString": "Example Movie"
},
{
"itemId": "series:200",
"value": { "title": "Example Series" },
"sortNumber": 2
}
]
}
}
Replacement follows these rules:
itemsmust not be empty. An empty list is rejected with theEMPTY_ARRAYreason.- Every
itemIdmust be unique within the request. A repeateditemIdis rejected with theDUPLICATE_ITEM_IDreason. - The replacement is atomic. Either the whole array is replaced or the stored data stays unchanged.
insertedCountis the number of replacement items inserted, anditemsreturns them in the order you sent them.- Each item can carry the optional
sortString,sortNumber, andsortDatefields.
To remove all items of an array, use a bulk delete.
Choose between upsert and replace
| Situation | Use |
|---|---|
| Add or correct a single item | upsertArrayItem |
| Rebuild an array from an authoritative list in another system | replaceArrayItems |
| You do not know all the items that the array currently holds | upsertArrayItem |
Verify a write
Read the data back with the read operations. The following query and variables read back the favorites written above:
query VerifyFavorites($input: ListArrayItemsInput!) {
listArrayItems(input: $input) {
nodes {
itemId
value
dateModified
}
pageInfo {
hasNextPage
endCursor
}
}
}
{
"input": {
"filter": {
"userId": "11111111-1111-4111-8111-111111111111",
"key": "content:favorites",
"scope": "USER"
},
"first": 50
}
}
Handle write errors
| Reason or code | Meaning |
|---|---|
FORBIDDEN | The token lacks Data: Write or Admin |
TARGET_CONSTRAINT | The ids in the target contradict its scope |
IDENTIFIER_CONSTRAINT | A key or itemId does not match the required pattern or length |
EMPTY_ARRAY | replaceArrayItems received no items |
DUPLICATE_ITEM_ID | replaceArrayItems received the same itemId more than once |
CONCURRENT_WRITE_CONFLICT | An array write conflicted with another change and was not completed |
A CONCURRENT_WRITE_CONFLICT leaves the stored data unchanged.
You can send the same request again.
Inspect errors[].extensions rather than the message text.
See Errors and limits for the full reference.
Next steps
- Find and inspect data to check the result of a write.
- Bulk updates and deletions to change data for many users.
- Authentication and permissions for the permissions behind each operation.