Sorry, no results found for "".

Show examples in:
Javascript HTTP

Content Management API > Record

Update a record
📚 New to DatoCMS records?

We strongly recommend reading the Introduction to Records guide first. The payload for updating a record follows the same structure as creating one, so that guide is also an essential prerequisite.

The fundamental rules for structuring field values (i.e., strings, numbers, objects, references) are the same for both creating and updating records. For a complete reference on how to format the value for every field type, please see the Field Types Overview in the main records guide.

When updating an existing record, you only need to provide the fields you want to change. Any fields you omit from your payload will remain untouched.

import { buildClient } from "@datocms/cma-client-node";
async function run() {
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "4235";
const item = await client.items.update(itemId, {
// we just pass the field that we want to change
title: "[EDIT] My first blog post!",
});
console.log(item);
}
run();
{
id: "hWl-mnkWRYmMCSTq4z_piQ",
title: "My first blog post!",
content: "Lorem ipsum dolor sit amet...",
category: "24",
image: {
alt: "Alt text",
title: "Image title",
custom_data: {},
focal_point: null,
upload_id: "20042921",
},
meta: {
created_at: "2020-04-21T07:57:11.124Z",
updated_at: "2020-04-21T07:57:11.124Z",
published_at: "2020-04-21T07:57:11.124Z",
first_published_at: "2020-04-21T07:57:11.124Z",
publication_scheduled_at: "2020-04-21T07:57:11.124Z",
unpublishing_scheduled_at: "2020-04-21T07:57:11.124Z",
status: "published",
is_current_version_valid: true,
is_published_version_valid: true,
current_version: "4234",
stage: null,
has_children: true,
},
item_type: { type: "item_type", id: "DxMaW10UQiCmZcuuA-IkkA" },
}

The following sections highlight the rules and strategies that are specific to the update process.

Updating Block Fields

As with creation, you cannot edit blocks directly; you must update the parent record that contains them. The payload you send uses a mix of block IDs and block objects to define the desired final state.

The rules for adding, updating, keeping, deleting, and reordering blocks are covered in detail in the main records guide.

➡️ See the complete guide to Creating and Updating Blocks

When updating a block, you use destructuring to combine existing and updated content. Use Array.map() to go through the existing blocks one by one, keeping their current ordering.

import { buildClient } from "@datocms/cma-client-node";
async function run() {
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
// These define what to update
const recordId = "A4gkL_8pTZmcyJ-IlIEd2w"; // The record's unique ID
const fieldWithBlocks = "modular_content_field"; // Which record field are the blocks in?
const blockId = "ToBApjdYQaCgeFJxp_ty0A"; // ID of the block we want to update ("Example block #1")
const blockFieldsToUpdate = {
// Inside the block, what are we updating?
title: "Example block #1 (Updated title)", // Update the title
};
// Get the current record
const currentRecord = await client.items.find(recordId, {
nested: true, // Also fetch the content of nested blocks, not just their IDs
});
console.log(
"Current record before the update, with nested blocks",
JSON.stringify(currentRecord, null, 2),
);
// Build the update before sending it
const currentBlock = currentRecord[fieldWithBlocks].find(
(block) => block.id === blockId,
); // The block's existing content
const updatedBlock = {
...currentBlock, // Keep its existing metadata
attributes: {
...currentBlock.attributes, // Keep existing attributes that we didn't update
...blockFieldsToUpdate, // Inject updated fields
},
};
const updatedFieldWithBlocks = currentRecord[fieldWithBlocks].map(
(
block, // Map through existing blocks so we keep their ordering
) =>
block.id === updatedBlock.id
? updatedBlock // If it's the block we want to update, inject the updated data
: block.id, // Otherwise, just provide the old block's ID and the API will know to keep it unchanged
);
// Perform the update by actually sending what we built to the API
const updatedRecord = await client.items.update(recordId, {
[fieldWithBlocks]: updatedFieldWithBlocks, // The record field to update
// We don't have to specify the other fields. The API will keep them the same.
});
console.log(
"Updated record. Only block IDs are returned, not their content.",
updatedRecord,
);
}
run();
// Current record before the update, with nested blocks
{
id: "A4gkL_8pTZmcyJ-IlIEd2w",
type: "item",
title: "This is an example record with blocks in a modular content field",
modular_content_field: [
{
type: "item",
attributes: {
title: "Example block #1",
},
relationships: {
item_type: {
data: {
id: "cR-9e-65SNat84KEs5M22w",
type: "item_type",
},
},
},
id: "ToBApjdYQaCgeFJxp_ty0A",
},
{
type: "item",
attributes: {
title: "Example block #2",
},
relationships: {
item_type: {
data: {
id: "cR-9e-65SNat84KEs5M22w",
type: "item_type",
},
},
},
id: "YGrrPYrkSNaq-dBaP7aSaQ",
},
],
item_type: {
id: "LvTZuGYRTQO9aBVk0g35Jw",
type: "item_type",
},
creator: {
id: "627975",
type: "organization",
},
meta: {
created_at: "2024-09-06T17:19:59.151+01:00",
updated_at: "2024-10-03T00:41:15.497+01:00",
published_at: "2024-10-03T00:41:15.538+01:00",
publication_scheduled_at: null,
unpublishing_scheduled_at: null,
first_published_at: "2024-09-06T17:19:59.215+01:00",
is_valid: true,
is_current_version_valid: true,
is_published_version_valid: true,
status: "published",
current_version: "EFYp72wJQf24L_jL1C1_tw",
stage: null,
},
}
/* Updated record. Only block IDs are returned, not their content.
{
id: 'A4gkL_8pTZmcyJ-IlIEd2w',
type: 'item',
title: 'This is an example record with blocks in a modular content field',
modular_content_field: [ 'ToBApjdYQaCgeFJxp_ty0A', 'YGrrPYrkSNaq-dBaP7aSaQ' ],
item_type: { id: 'LvTZuGYRTQO9aBVk0g35Jw', type: 'item_type' },
creator: { id: '627975', type: 'organization' },
meta: {
created_at: '2024-09-06T17:19:59.151+01:00',
updated_at: '2024-10-03T00:41:21.730+01:00',
published_at: '2024-10-03T00:41:21.784+01:00',
publication_scheduled_at: null,
unpublishing_scheduled_at: null,
first_published_at: '2024-09-06T17:19:59.215+01:00',
is_valid: true,
is_current_version_valid: true,
is_published_version_valid: true,
status: 'published',
current_version: 'Af6ykuN7S6WGMcwD3XZUdQ',
stage: null
}
}
*/

You can reorder block records by changing their IDs position in the modular content field array.

import { buildClient } from "@datocms/cma-client-node";
async function run() {
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "4235";
const item = await client.items.update(itemId, {
// previous order was ['565346546', '565346547', '565346548']
content: ["565346548", "565346547", "565346546"],
});
console.log(item);
}
run();
const result = {
type: "item",
id: "4235",
title: "Title",
content: ["565346548", "565346547", "565346546"],
meta: {
/* ... */
},
item_type: { type: "item_type", id: "44" },
creator: { type: "access_token", id: "312" },
};

In this case, you must not pass an ID to buildBlockRecord.

It's not required the presence of all the fields in the payload. If it's easier for you to include all of them, just pass null, as it's a valid value for every field type.

When a field is not present in the payload, or its value is null, then the field's default value will be used (if available).

import { buildClient, buildBlockRecord } from "@datocms/cma-client-node";
async function run() {
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "4235";
const item = await client.items.update(itemId, {
// previous order was ['565346546', '565346547', '565346548']
content: ["565346548", "565346547", "565346546"],
});
console.log(item);
}
run();
async function run() {
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "4235";
const blockModelId = "23455234";
const item = await client.items.update(itemId, {
content: [
// new block record:
buildBlockRecord({
item_type: { type: "item_type", id: blockModelId },
text: "new block record text",
subtext: "another text",
}),
// existing block record IDs:
"565346546",
"565346547",
"565346548",
],
});
console.log(item);
}
run();
const result = {
type: "item",
id: "4235",
title: "Title",
// block '565346549' has been added:
content: ["565346549", "565346546", "565346547", "565346548"],
meta: {
/* ... */
},
item_type: { type: "item_type", id: "44" },
creator: { type: "access_token", id: "312" },
};

To delete a block record, remove its ID from the modular content field array.

import { buildClient } from "@datocms/cma-client-node";
async function run() {
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "4235";
const item = await client.items.update(itemId, {
// previous value was ['565346546', '565346547', '565346548'], let's remove '565346547'
content: ["565346546", "565346548"],
});
console.log(item);
}
run();
const result = {
type: "item",
id: "4235",
title: "Title",
// block '565346547' has been removed!
content: ["565346546", "565346548"],
meta: {
/* ... */
},
item_type: { type: "item_type", id: "44" },
creator: { type: "access_token", id: "312" },
};

Updating Localized Fields

➡️ Before proceeding, ensure you have read the general guide on Localization

When you send an update request, the API follows these strict rules.

Rule 1: To change a locale value, send the whole set

When you update a translated field, you must provide the entire object for that field, including all the languages you want to keep unchanged. You can't just send the one language you're changing.

  • Correct: To update the Italian title, you send both English and Italian:
    {
    "title": {
    "en": "Hello World",
    "it": "Ciao a tutti! (Updated)"
    }
    }
  • Incorrect: If you only send the Italian value, the API will assume you want to delete the English one!
Rule 2: To add/remove a language, send all translated fields

This is the only time you can't just send the one field you're changing. To add or remove a language from an entire record, you must include all translated fields in your request. This is to enforce the Locale Sync Rule and ensure all fields remain consistent.

  • Example: To add French to a blog post that already has a translated title and content, your request must include both fields with the new fr locale.
Rule 3: Limited permissions? Only send what you can manage

If your API key only has permission for certain languages (e.g., only English), you must only include those languages in your update. The system is smart and will automatically protect and preserve the content for the languages you can't access (like Italian or French).

Update scenarios at a glance

This table shows what happens in different situations. The key takeaway is that your update payload defines the new final state for the languages you are allowed to manage.

Your Role managesRecord currently HasYour payload sendsResult
EnglishEnglishEnglish✅ English is updated.
English, ItalianEnglishEnglish, Italian✅ English is updated.
➕ Italian is added.
English, ItalianEnglish, ItalianEnglish✅ English is updated.
➖ Italian is removed.
English, ItalianEnglish, ItalianEnglish, Italian✅ English is updated.
✅ Italian is updated.
Eng, Ita, FreEnglish, ItalianEnglish, French✅ English is updated.
➖ Italian is removed.
➕ French is added.
EnglishEnglish, ItalianEnglish✅ English is updated.
🛡️ Italian is preserved.
English, ItalianEnglish, FrenchEnglish, Italian✅ English is updated.
🛡️ French is preserved.
➕ Italian is added.
English, ItalianEnglish, FrenchItalian➖ English is removed.
🛡️ French is preserved.
➕ Italian is added.
Block fields

The rules about localization work in combination with the rules for updating blocks: you use full block objects to create/update and block IDs to leave unchanged, but you do so within the object for a specific locale.

Example: Updating a block in one locale

This payload updates the title of an existing block in the en locale, while leaving the second English block and all Italian blocks untouched. The it locale needs to be included in the payload, or the Italian locale will be deleted!

{
"content_blocks": {
"en": [
{
"id": "dhVR2HqgRVCTGFi0bWqLqA",
"type": "item",
"attributes": { "title": "Updated English Title" }
},
"kL9mN3pQrStUvWxYzAbCdE"
],
"it": [
"dhVR2HqgRVCTGFi_0bWqLqA",
"kL9mN3pQrStUvWxYzAbCdE"
]
}
}
Example: Adding a new block to one locale

This payload adds a new block to the it locale only. The en locale needs to be included in the payload, or the Italian locale will be deleted!

{
"content_blocks": {
"en": [
"dhVR2HqgRVCTGFi_0bWqLqA",
"kL9mN3pQrStUvWxYzAbCdE"
],
"it": [
"fG8hI1jKlMnOpQrStUvWxY",
{
"type": "item",
"attributes": { "title": "Nuovo Blocco" },
"relationships": {
"item_type": { "data": { "id": "BxZ9Y2aKQVeTnM4hP8wLpD", "type": "item_type" } }
}
},
"dhVR2HqgRVCTGFi0bWqLqA"
]
}
}
Example: Adding a new locale

To add a new locale to an existing record, you must provide values for all localized fields for that new locale, and include existing locales that you want to preserve.

{
"title": {
"en": "English Title",
"fr": "Titre Français",
},
"content_blocks": {
"en": [
"dhVR2HqgRVCTGFi_0bWqLqA",
"kL9mN3pQrStUvWxYzAbCdE"
],
"fr": [
{
"type": "item",
"attributes": { "title": "Nouveau Bloc Français" },
"relationships": {
"item_type": { "data": { "id": "BxZ9Y2aKQVeTnM4hP8wLpD", "type": "item_type" } }
}
}
]
}
}

If the 'All locales required?' option in a model is turned off, then its records do not need all environment's locales to be defined for localized fields, so you're free to add/remove locales during an update operation.

Suppose your environment's locales are English, Italian and German (['en', 'it', 'de']) and the following record currently defines en and it locales on its localized fields (title and content):

const item = {
id: '4235',
// localized field
title: {
en: 'My title',
it: 'Il mio titolo'
},
// localized field
content: {
en: 'Article content',
it: 'Contenuto articolo',
},
// non-localized field
votes_count: 10,
meta: { /* ... */ },
item_type: { type: 'item_type', id: '44' },
creator: { type: 'access_token', id: '312' },
}

To add the de locale to this record, you have to send an update request containing all localized fields. For each one of these, you must define the exiting locales plus the ones you want to add:

import { buildClient } from "@datocms/cma-client-node";
async function run() {
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "4235";
const item = await client.items.update(itemId, {
title: {
en: "My title",
it: "Il mio titolo",
de: "Mein Titel",
},
content: {
en: "Article content",
it: "Contenuto articolo",
de: "Artikelinhalt",
},
});
console.log(item);
}
run();
const result = {
type: "item",
id: "4235",
title: {
en: "My title",
it: "Il mio titolo",
de: "Mein Titel",
},
content: {
en: "Article content",
it: "Contenuto dell'articolo",
de: "Artikelinhalt",
},
votes_count: 10,
meta: {
/* ... */
},
item_type: { type: "item_type", id: "44" },
creator: { type: "access_token", id: "312" },
};

If the 'All locales required?' option in a model is turned off, then its records do not need all environment's locales to be defined for localized fields, so you're free to add/remove locales during an update operation.

Let's suppose your environment's locales are English (en), Italian (it) and German (de) and the following record currently defines en and it locales on its localized fields (title and content):

const item = {
id: '4235',
// localized field
title: {
en: 'My title',
it: 'Il mio titolo'
},
// localized field
content: {
en: 'Article content',
it: 'Contenuto articolo',
},
// non-localized field
votes_count: 10,
meta: { /* ... */ },
item_type: { type: 'item_type', id: '44' },
creator: { type: 'access_token', id: '312' },
}

To remove the it locale from this record, you have to send an update request containing all localized fields. For each one fo these, you must define the exiting locales, except the ones you want to remove:

import { buildClient } from "@datocms/cma-client-node";
async function run() {
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const itemId = "4235";
const item = await client.items.update(itemId, {
title: {
en: "My title",
},
content: {
en: "Article content",
},
});
console.log(item);
}
run();
const result = {
type: "item",
id: "4235",
title: {
en: "My title",
},
content: {
en: "Article content",
},
votes_count: 10,
meta: {
/* ... */
},
item_type: { type: "item_type", id: "44" },
creator: { type: "access_token", id: "312" },
};

Optimistic Locking

To prevent clients from accidentally overwriting each other's changes, the update endpoint supports optimistic locking. You can include the record's current version number in the meta object of your payload.

If the version on the server is newer than the one you provide, the API will reject the update with a 422 STALE_ITEM_VERSION error, indicating that the record has been modified since you last fetched it.

import { ApiError, buildClient } from "datocms-client";
// Make sure the API token has access to the CMA, and is stored securely
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
async function updateRecordByIncrementingCounter(itemId) {
// first we get the record we want to update
const record = await client.items.find(itemId);
try {
// now we increment the counter value, passing the current version
// to enable optimistic-locking
client.items.update(itemId, {
counter: record.counter + 1,
meta: { current_version: record.meta.current_version },
});
console.log("Done!");
} catch (e) {
// if we get a STALE_ITEM_VERSION error, this means that the
// the record changed in-between the find and update operations, so we have
// to fetch the latest version of the record and try again
if (e instanceof ApiError && e.findError("STALE_ITEM_VERSION")) {
console.log("Stale version, retrying...");
return updateRecordByIncrementingCounter();
}
throw e;
}
}
await updateRecordByIncrementingCounter("1230982");
> node example.js
Stale version, retrying...
Stale version, retrying...
Done!

Body parameters

meta.created_at string Optional

Date of creation

meta.first_published_at null, string Optional

Date of first publication

meta.current_version string Optional

The ID of the current record version (for optimistic locking, see the example)

Example: "4234"
meta.stage string, null Optional

The new stage to move the record to

item_type Optional

The record's model

creator Optional

The entity (account/collaborator/access token/sso user) who created the record

Returns

Returns a resource object of type item