Sorry, no results found for "".

Show examples in:
Javascript HTTP

Content Management API > Record

Update a record

Updating record is very similar to creating a new one from scratch, so make sure to read that guide and all the examples for more details.

When updating you can pass just the fields you want to change.

Updating and version locking

DatoCMS optionally supports optimistic locking. When updating an existing record, you can specify its current version within the appropriate meta property. DatoCMS compares this version with the current version stored to ensure that a client doesn't overwrite a record that has since been updated. If the version changed in-between, DatoCMS would reject the update with a 422 STALE_ITEM_VERSION error.

For more information, take a look at the Javascript examples.

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,
},
item_type: { type: "item_type", id: "DxMaW10UQiCmZcuuA-IkkA" },
}
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!

Working with block records

Block records live inside of Modular Content, Structured Text and Single Block fields, which require different data structures as their value: while Modular Content is simply an array of blocks, Single Block accept only one block. Structured Text accepts a Structured text document, which in turn might contain a number of block nodes interspersed with textual content.

We’ll get to some detailed examples for both fields below, but the general idea that applies to both of them is that to preserve the atomicity property of ACID transactions, DatoCMS prohibits you from creating/editing/deleting blocks directly; you always need to add/edit/remove blocks by performing a create/update operation on the record that embeds them, so:

  • If you want to add a block to a particular field, you need to add to the existing field value the new “fragment” related to the new block;
  • If you want to edit a block that’s already present in the field, you need to pass the whole field value and replace only the “fragment” related to such block, while keeping the rest unchanged;
  • If you want to remove a block that’s already present in the field, you need to pass the whole field value and only remove the "fragment” related to such block, while keeping the rest unchanged.

Depending on the endpoints you're going to use, the “fragment” representing the block can be simply its ID, or a full JSON API object:

EndpointHTTP request blocks representationHTTP response blocks representation
GET /itemsN/AID
GET /items?nested=trueN/AJSON
resource object
GET /item/
N/AID
GET /item/
?nested=true
N/AJSON
resource object
POST /itemsJSON
resource object
ID
PUT /items/
ID or JSON
resource object
ID

During an update operation, to reference a block:

  • when you want to make some changes to the content of a specific block, you pass the complete JSON
    resource object (complete with its existing id);
  • when you want to add a totally new block, you pass the complete JSON
    resource object (with no ID of course, as it’s a new object);
  • when you don’t want to make any change to an existing block, you can pass just the block ID (the update operation will be faster this way, as it won’t have to process it).

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' },
};

Working with localized content

Before diving in, the key concepts to remember are the following:

  • Localization is handled on a per-field basis. That is, in the same model, some fields can have a different value specified for each locale, and some others just a single (non-localized) value.
  • On localized fields, records express a value for multiple locales using an object, whose keys represent the environment’s locales.
  • The locales specified in a record must be coherent across each localized field. That is, if you have two localized fields, they must share the same locale keys. You cannot ie. specify locales en and it for the title field, and only en for the content field.
  • When a model contains one or more localized fields, it can enforce the presence of a value for all locales, or not.
  • Every user/API token is linked to a role, which in turn specifies which locales can be managed by the user/API token itself on a per-model level.

Above we said that when you perform an update operation to a record, you can pass just the fields you want to change, and not all of them. When dealing with localized content, the only exception to this rule is when you have a model that does not enforce a value for every locale, and you want to add or remove a locale on a specific record. Only in this case, you are required to pass all localized fields in the payload to enforce a locales coherence between all the localized fields.

Also, when you want to update the value for a specific localized field, you always need to pass an object containing the value for each locale you want to preserve, not just the one you might want to update, otherwise you’ll get an error.

If your API token can only manage a subset of locales, during an update operation you are required not to include in the payload any other locales. In this case, the content for the locales you cannot control and that you won’t pass will be kept intact by the system.

In the following table, you’ll find a recap showing the result of an update operation, depending on the API token permissions, the locales already present in a record, and the locales specified in the payload of the update operation itself:

Role can manage content inRecord contains localized fields with content inPayload contains all localized fields with content inResult for the update operation
EnglishEnglishEnglishEnglish is updated.
English, ItalianEnglishEnglish, ItalianEnglish is updated.
Italian is added.
English, ItalianEnglish, ItalianEnglishEnglish is updated.
Italian is removed.
English, ItalianEnglish, ItalianEnglish, ItalianEnglish is updated.
Italian is updated.
English, Italian, FrenchEnglish, ItalianEnglish, FrenchEnglish is updated.
Italian is removed.
French is added.
EnglishEnglish, ItalianEnglishEnglish is updated.
Italian is preserved.
English, ItalianEnglish, FrenchEnglish, ItalianEnglish is updated.
French is preserved.
Italian is added.
English, ItalianEnglish, FrenchItalianEnglish is removed.
French is preserved.
Italian is added.

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' },
};

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