Show examples in:
Javascript HTTP
Content Management API > Record

Record

DatoCMS stores the individual pieces of content you create from a model as records (for backwards compatibility the API calls these item). The shape of a record’s attributes depends on the fields defined by that record’s model — see the Object payload section for the full object payload documentation.

// A simple record
{
"id": "A4gkL_8pTZmcyJ-IlIEd2w",
"type": "item",
"attributes": {
"title": "My Blog Post",
"publication_date": "2024-01-15"
},
"relationships": {
"item_type": {
"data": { "id": "BxZ9Y2aKQVeTnM4hP8wLpD", "type": "item_type" }
}
}
}
📘 New to content modeling?

Check out the Content Modeling Guide to understand how to design models, fields, and relationships before diving into API usage.


Field types overview

Scalar fields

These store basic data types (ie. strings, numbers, booleans):

Single-line string

The field accepts String values or null.

Slug

The field accepts String values or null.

Multi-line text

The field accepts simple String values (can include newlines) or null

Boolean

The field accepts simple Boolean values or null.

Integer

The field accepts simple Integer values or null.

Float

The field accepts simple Float values or null.

Date

The field accepts String values in ISO 8601 date format (ie. "2015-12-29") or null.

Date time

The field accepts String values in ISO 8601 date-time format (ie. "2020-04-17T16:34:31.981+01:00") or null.

If you're on legacy timezone management, remember that when sending an ISO8601 datetime you should keep in mind that the system will ignore any provided timezone, and will use the project's timezone instead.

JSON

The field accepts String values that are valid JSON or null.

Note: Must be a JSON-serialized string, not a JavaScript object!

Object Fields

These require structured objects:

Color

The field accepts an object with the following properties, or null:

PropertyRequiredType
redInteger between 0 and 255
greenInteger between 0 and 255
blueInteger between 0 and 255
alphaInteger between 0 and 255
Location

The field accepts an object with the following properties, or null:

PropertyRequiredType
latitudeFloat between -90.0 to 90
longitudeFloat between -180.0 to 180
SEO

The field accepts an object with the following properties, or null:

PropertyRequiredTypeDescription
titleStringTitle meta tag (max. 320 characters)
descriptionStringDescription meta tag (max. 320 characters)
imageUpload IDAsset to be used for social shares
twitter_card"summary", "summary_large_image"Type of Twitter card to use
no_indexBooleanWhether the noindex meta tag should be returned
External video

The field accepts an object with the following properties, or null:

PropertyRequiredTypeDescriptionExample
provider"youtube", "vimeo", "facebook"External video provider"youtube"
provider_uidStringUnique identifier of the video within the provider"vUdGBEb1i9g"
urlURLURL of the video"https://www.youtube.com/watch?v=qJhobECFQYk"
widthIntegerVideo width459
heightIntegerVideo height344
thumbnail_urlURLURL for the video thumb"https://i.ytimg.com/vi/vUdGBEb1i9g/hqdefault.jpg"
titleStringTitle of the video"Next.js Conf Booth Welcoming!"
Reference Fields

These point to other resources (either assets or other records):

Single-asset

The field accepts an object with the following properties, or null:

PropertyRequiredTypeDescriptionExample
upload_idUpload IDID of an asset"dhVR2HqgRVCTGFi0bWqLqA"
titleStringTitle for the asset, if you want to override the asset's default value (see Upload default_field_metadata)"From my trip to Italy"
altStringAlternate text for the asset, if you want to override the asset's default value (see Upload default_field_metadata)"Florence skyline"
focal_point{ x: Float, y: Float }, nullFocal point for the asset, if you want to override the asset's default value (see Upload default_field_metadata). Values must be expressed as Float between 0 and 1. Focal point can only be specified for image assets.{ "x": 0.34, "y": 0.45 }
custom_dataRecord<String, String>An object containing custom keys that you can use on your frontend projects{ "watermark_image": "true" }

API responses: Always returns asset ID only (use separate asset API for details)

Asset gallery

This field accepts an Array of objects with the following properties, or null:

PropertyRequiredTypeDescriptionExample
upload_idUpload IDID of an asset"dhVR2HqgRVCTGFi0bWqLqA"
titleStringTitle for the asset, if you want to override the asset's default value (see Upload default_field_metadata)"Gallery Image Title"
altStringAlternate text for the asset, if you want to override the asset's default value (see Upload default_field_metadata)"Gallery image description"
focal_point{ x: Float, y: Float }, nullFocal point for the asset, if you want to override the asset's default value (see Upload default_field_metadata). Values must be expressed as Float between 0 and 1. Focal point can only be specified for image assets.{ "x": 0.34, "y": 0.45 }
custom_dataRecord<String, String>An object containing custom keys that you can use on your frontend projects{ "watermark_image": "true" }

API responses: Always returns array of asset IDs only

Single link

This field accepts a String representing the ID of the linked record, or null. See Link Fields Guide for relationship modeling concepts.

API responses: Always returns record ID only

Multiple links

This field accepts an Array<String> representing the IDs of the linked records, or null. See Link Fields Guide for relationship modeling concepts.

API responses: Always returns array of record IDs only

Block Fields

These are special fields that contain blocks within records:

Field TypeWhat it contains
Modular contentAn array of blocks, perfect for building dynamic page sections
Single blockA single block instance or null
Structured textA rich text document that can have blocks embedded within the flow of content (DAST format)

Blocks are records within records - they're separate items that live inside fields of other records.

📚 Content Modeling Context

To understand when and how to design blocks vs models, see Blocks Guide. For field-specific concepts, see Modular Content and Structured Text.

Blocks inside those fields are unique because they can be represented in two different ways depending on the context: as a lightweight reference (an ID) or as a full content object. Understanding this duality is key to working with them effectively:

  • Block ID (Lightweight Reference): A simple String that uniquely identifies the block (ie. "dhVR2HqgRVCTGFi_0bWqLqA"). This is useful when you only need to know which block is there, not what's inside it.

  • Block Object (Full Content): The complete record object for the block, containing its own id, type, attributes, and relationships. This is used when you need to read or modify the block's actual content.

    {
    "id": "dhVR2HqgRVCTGFi_0bWqLqA",
    "type": "item",
    "attributes": {
    "title": "Block Title",
    "content": "Block content..."
    },
    "relationships": {
    "item_type": {
    "data": { "id": "BxZ9Y2aKQVeTnM4hP8wLpD", "type": "item_type" }
    }
    }
    }
Modular Content

A Modular Content field holds an array of blocks.

As an array of IDs:

{
"content_blocks": [
"dhVR2HqgRVCTGFi_0bWqLqA",
"kL9mN3pQrStUvWxYzAbCdE"
]
}

As an array of full objects:

{
"content_blocks": [
{
"id": "dhVR2HqgRVCTGFi_0bWqLqA",
"type": "item",
"attributes": { "title": "Hero Section", "content": "Welcome to our site" },
"relationships": { "item_type": { "data": { "id": "...", "type": "item_type" } } }
},
{
"id": "kL9mN3pQrStUvWxYzAbCdE",
"type": "item",
"attributes": { "title": "Image Gallery", "images": [...] },
"relationships": { "item_type": { "data": { "id": "...", "type": "item_type" } } }
}
]
}
Single Block

A Single Block field holds exactly one block, or null.

As an ID:

{
"featured_block": "dhVR2HqgRVCTGFi_0bWqLqA"
}

As an full object:

{
"featured_block": {
"id": "dhVR2HqgRVCTGFi_0bWqLqA",
"type": "item",
"attributes": { "title": "Featured Content", "summary": "A summary..." },
"relationships": { "item_type": { "data": { "id": "...", "type": "item_type" } } }
}
}
Structured Text

A Structured Text field can contain blocks within its document structure (DAST format). The item property of a block or inlineBlock node will hold either the ID or the full object.

With block IDs:

{
"rich_text_content": {
"schema": "dast",
"document": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [{ "type": "span", "value": "Text before block." }]
},
{
"type": "block",
"item": "dhVR2HqgRVCTGFi_0bWqLqA"
}
]
}
}
}

With full objects:

{
"rich_text_content": {
"schema": "dast",
"document": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [{ "type": "span", "value": "Text before block." }]
},
{
"type": "block",
"item": {
"id": "dhVR2HqgRVCTGFi_0bWqLqA",
"type": "item",
"attributes": { "title": "Embedded Block", "content": "..." },
"relationships": { "item_type": { "data": { "id": "...", "type": "item_type" } } }
}
}
]
}
}
}

API response modes: Regular vs. Nested

When fetching record data, the API gives you control over how block fields are represented in the response. These two modes, Regular and Nested, are available on the following endpoints:

Regular mode (default)

By default, the API returns block fields as IDs only. This is efficient and fast, making it ideal for listings or when you don't need the blocks' content immediately.

GET /items/A4gkL_8pTZmcyJ-IlIEd2w
{
"id": "A4gkL_8pTZmcyJ-IlIEd2w",
"type": "item",
"attributes": {
"title": "My Blog Post",
"content_blocks": ["dhVR2HqgRVCTGFi_0bWqLqA", "kL9mN3pQrStUvWxYzAbCdE"],
"featured_block": "nZ8xY2vWqTuJkL3mNcBeFg"
}
}
Nested mode (?nested=true)

The same endpoint, when passing the ?nested=true option, returns block fields as full objects. This is essential when you need to display or edit the content within the blocks.

GET /items/A4gkL_8pTZmcyJ-IlIEd2w?nested=true
{
"id": "A4gkL_8pTZmcyJ-IlIEd2w",
"type": "item",
"attributes": {
"title": "My Blog Post",
"content_blocks": [
{
"id": "dhVR2HqgRVCTGFi_0bWqLqA",
"type": "item",
"attributes": { "title": "Hero Section", "content": "Welcome to our site" },
"relationships": { ... }
},
{
"id": "kL9mN3pQrStUvWxYzAbCdE",
"type": "item",
"attributes": { "title": "Image Gallery", "images": [...] },
"relationships": { ... }
}
],
"featured_block": {
"id": "nZ8xY2vWqTuJkL3mNcBeFg",
"type": "item",
"attributes": { ... },
"relationships": { ... }
}
}
}
Block Fields vs. Other Reference Fields

Block fields are the only field type that change representation between modes! Asset and link fields always return IDs. To get full details for assets or linked records, you need to make separate API calls using their IDs.

When to use each mode?
Use "Regular Mode" when...Use "Nested Mode" when...
Listing many records or building navigation.Displaying or editing block content, as it provides the actual content needed.
You only need to know which blocks exist.You need to read the actual block content for display or updates.
Building navigationPreparing to update blocks
Performance is critical; it's faster because it returns smaller responses (block IDs instead of full content).You are building content editing interfaces where usability is more important than raw speed.

Creating and updating blocks

Working with blocks follows one fundamental constraint:

You cannot create, edit, or delete blocks directly. You must always update the parent record that contains them.

This ensures data integrity. To create/modify blocks, you send a payload to the parent record's endpoint, using a mix of Block IDs and Block Objects to describe the desired changes.

Key rules for block operations
  1. To create a new block: Provide the full object, including type, attributes, and the relationships.item_type which specifies the Block Model being used.
  2. To update an existing block: Provide the full object, including its id and the changed attributes. You only need to include the specific attributes that you want to change - unchanged attributes will be preserved. You don't need to specify relationships.item_type.
  3. To keep an existing block unchanged: Simply provide its Block ID string. This is the most efficient way to handle unchanged blocks.
  4. To delete a block: Omit it from the payload. For a Modular Content array, remove its ID. For a Single Block field, set the value to null.
  5. To reorder blocks (in Modular Content): Send an array of Block IDs in the new desired order.

The following examples show how to apply these rules.

Working with Modular Content Fields

Current state (from a regular API response):

{
"content_blocks": ["dhVR2HqgRVCTGFi_0bWqLqA", "kL9mN3pQrStUvWxYzAbCdE", "fG8hI1jKlMnOpQrStUvWxY"]
}

To update the second block and reorder the others:

{
"content_blocks": [
"fG8hI1jKlMnOpQrStUvWxY", // Reordered: kept as ID
{
"id": "kL9mN3pQrStUvWxYzAbCdE", // Updated: sent as object
"type": "item",
"attributes": { "title": "Updated Title" }
},
"dhVR2HqgRVCTGFi_0bWqLqA" // Reordered: kept as ID
]
}

To add a new block at the end and remove the first block:

{
"content_blocks": [
"kL9mN3pQrStUvWxYzAbCdE", // Kept as ID
"fG8hI1jKlMnOpQrStUvWxY", // Kept as ID
{
"type": "item", // New block: sent as object with relationships
"attributes": { "title": "A Brand New Block" },
"relationships": {
"item_type": {
"data": { "id": "BxZ9Y2aKQVeTnM4hP8wLpD", "type": "item_type" }
}
}
}
]
}
Working with Single Block Fields

Current state (from a regular API response):

{
"hero_block": "dhVR2HqgRVCTGFi_0bWqLqA"
}

To update the block's content:

{
"hero_block": {
"id": "dhVR2HqgRVCTGFi_0bWqLqA",
"type": "item",
"attributes": { "title": "Updated Hero Title" }
}
}

To replace it with a new block:

{
"hero_block": {
"type": "item",
"attributes": { "title": "New Hero Block" },
"relationships": {
"item_type": {
"data": { "id": "BxZ9Y2aKQVeTnM4hP8wLpD", "type": "item_type" }
}
}
}
}

To remove (delete) the block:

{
"hero_block": null
}
Working with Structured Text Fields

Updating blocks within Structured Text follows the same pattern: you replace the item's ID with a full object for the block you want to change.

Current state (from a regular API response):

{
"rich_content": {
"schema": "dast",
"document": {
"type": "root",
"children": [
{ "type": "block", "item": "dhVR2HqgRVCTGFi_0bWqLqA" },
{ "type": "paragraph", "children": [{ "type": "span", "value": "Some text." }] }
]
}
}
}

To update the block's content:

{
"rich_content": {
"schema": "dast",
"document": {
"type": "root",
"children": [
{
"type": "block",
"item": {
"id": "dhVR2HqgRVCTGFi_0bWqLqA", // The block to update
"type": "item",
"attributes": { "title": "Updated DAST Block Title" }
}
},
{ "type": "paragraph", "children": [{ "type": "span", "value": "Some text." }] }
]
}
}
}
Deeply-nested blocks

Blocks can contain other blocks, creating hierarchies multiple levels deep. The same principles apply recursively. When you fetch a record with ?nested=true, the API will expand nested blocks at all levels.

When updating, you are always sending a payload to the top-level parent record, but you can specify changes to deeply nested blocks using the same ID vs. object rules.

Example: Updating a nested block

Imagine a "Wrapper" block that contains a Modular Content field with "Child" blocks inside it. To update "Child Block 1" while leaving "Child Block 2" untouched:

// This payload is sent to the top-level record containing the "Parent Block"
{
"wrapper_block": {
"id": "dhVR2HqgRVCTGFi_0bWqLqA", // ID of the parent block being updated
"type": "item",
"attributes": {
"nested_content": [
{
"id": "kL9mN3pQrStUvWxYzAbCdE", // ID of the nested block being updated
"type": "item",
"attributes": { "title": "Updated Child Block 1" }
},
"fG8hI1jKlMnOpQrStUvWxY" // Unchanged nested block, sent as ID
],
// You can skip any attribute that does not need to change
}
}
}

Localization

Localization allows you to store different versions of your content for different languages or regions. When you mark a field as "localizable" in your model, its structure in the API changes to accommodate multiple values.

The fundamental change is that the field's value is no longer a single piece of data but an object keyed by locale codes.

For example, a simple non-localized title field looks like this:

{
"title": "Hello World"
}

When localized, it becomes an object containing a value for each configured locale:

{
"title": {
"en": "Hello World",
"it": "Ciao Mondo",
"fr": "Bonjour le Monde"
}
}

This principle applies to every type of field, from simple strings to Modular Content, Single Block, and Structured Text fields. For instance, a localized Modular Content field will contain a separate array of blocks for each language. This powerful feature allows you to have completely different block structures for each locale.

Example: Localized Modular Content field

In a regular API response, you would see different arrays of block IDs for each locale.

{
"content_blocks": {
"en": ["dhVR2HqgRVCTGFi0bWqLqA", "kL9mN3pQrStUvWxYzAbCdE"],
"it": ["fG8hI1jKlMnOpQrStUvWxY", "dhVR2HqgRVCTGFi0bWqLqA"]
}
}
Example: Localized Single Block field A different block can be assigned to each locale.
{
"hero_block": {
"en": "dhVR2HqgRVCTGFi0bWqLqA",
"it": "kL9mN3pQrStUvWxYzAbCdE"
}
}
Example: Localized Structured Text field

The entire DAST document is localized, allowing for different text and different embedded blocks per locale.

{
"rich_content": {
"en": {
"schema": "dast",
"document": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "span",
"value": "Welcome to our product showcase. Here's what we're featuring today:"
}
]
},
{ "type": "block", "item": "dhVR2HqgRVCTGFi0bWqLqA" }
]
}
},
"it": {
"schema": "dast",
"document": {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "span",
"value": "Benvenuti nella nostra vetrina prodotti. Ecco cosa presentiamo oggi:"
}
]
},
{ "type": "block", "item": "kL9mN3pQrStUvWxYzAbCdE" }
]
}
}
}
}

When reading or writing localized content, there are a few key rules to follow to ensure data integrity.

Locale consistency

Within a single record, all localized fields must have a consistent set of locales. You cannot have a title with English and Italian, and a description with English and French in the same record.

// ❌ This will FAIL due to inconsistent locales ("it" vs "fr")
{
"title": { "en": "Title", "it": "Titolo" },
"description": { "en": "Description", "fr": "Description" }
}
// ✅ This is VALID because locales are consistent across all fields
{
"title": { "en": "Title", "it": "Titolo" },
"description": { "en": "Description", "it": "Descrizione" }
}
Models enforcing all locales

You can configure a model to require every project locale to be present for its localized fields using the all_locales_required attribute.

When this setting is enabled, records must include a key for every defined locale within each localized field. The value for a locale can be null, but the key itself is mandatory.

// ❌ FAILS: The "it" locale is missing.
{
"title": { "en": "Title" }
}
// ✅ VALID: All required locale keys ("en", "it") are present.
{
"title": { "en": "Title", "it": "Titolo" }
}
// ✅ ALSO VALID: The "it" key is present, even with a `null` value.
{
"title": { "en": "Title", "it": null }
}

Type-safe development with TypeScript

Since DatoCMS records don't have a predetermined structure, the JavaScript client cannot provide strict TypeScript types out of the box:

import { buildClient } from "@datocms/cma-client-node";
const client = buildClient({ apiToken: process.env.DATOCMS_API_TOKEN });
const record = await client.items.find('dhVR2HqgRVCTGFi0bWqLqA');
record.accent_color; // -> TypeScript type: unknown

To get full type-safety plus auto-completions and type hints in your code editor, you can leverage the DatoCMS CLI to automatically generate TypeScript types based on your specific project schema.

Generating types from your schema

After installing and configuring the CLI, you can use the schema:generate command to generate a comprehensive TypeScript definition file describing your DatoCMS project structure (models and blocks):

Terminal window
$ npx datocms schema:generate schema.ts
schema.ts
import type { ItemTypeDefinition } from '@datocms/cma-client';
export type Article = ItemTypeDefinition<
'76hhD-LaS5CM3NPJw0991w', // ID of the Product model
{
name: { type: 'string'; };
slug: { type: 'slug'; };
accent_color: { type: 'color'; };
sections: { type: 'rich_text'; blocks: ArticleSection; };
}
>;
export type ArticleSection = ItemTypeDefinition<
'FJM79jjKRMSVg-fR6k6X2A', // ID of the ProductVariation block
{
title: { type: 'string'; };
}
>;

You can use the --item-types flag to only generate definitions for specific models/blocks:

Terminal window
$ npx datocms schema:generate --item-types=product,article schema.ts

The same type definitions can be included in your migration scripts by using the migrations:new CLI command with --schema flag:

Terminal window
# generate definitions for all the models/blocks
$ npx datocms migrations:new 'tweak articles' --schema=all
# only generate definitions for article and author
$ npx datocms migrations:new 'tweak articles' --schema=article,author

An ItemTypeDefinition is a minimal type blueprint for your API payloads. It only includes what's needed for typed API calls: field names, their data types, and any allowed block types. It intentionally omits details like validation rules or default values, as they don't affect the shape of the data sent to or from the API.

😉 Practical, not perfect

These types are designed for a practical developer experience, not perfect precision. In other words, you might still encounter API errors even if TypeScript gives you the green light. The types ensure the structure of a request is valid, but not necessarily the values within it (e.g., a string that's too long).

Use generated types

These definition types can be used as generics in all API calls related to records to get a fully typed interface:

import * as Schema from './schema';
// Fully typed record retrieval
const record = await client.items.find<Schema.Article>('AZUeMuPySxuJCJ8ibEVE7w');
record.accent_color; // -> ColorFieldValue (properly typed!)
// Type-safe record creation
const record = await client.items.create<Schema.Article>({
item_type: { id: 'dhVR2HqgRVCTGFi0bWqLqA', type: 'item_type' },
accent_color: '#FF0000', // ❌ TypeScript error! Wrong format!
});
TypeScript type narrowing with __itemTypeId

Records include a top-level __itemTypeId property to help with TypeScript's type narrowing.

Since TypeScript cannot infer types from nested properties like record.item_type.id, you can use the top-level __itemTypeId in your checks. This allows TypeScript to correctly identify the specific record type, enabling type-safe access to its unique properties.

// ❌ This doesn't work because `item_type.id` is a nested property.
if (item.item_type.id === "BxZ9Y2aKQVeTnM4hP8wLpD") {
// TypeScript still sees a generic item type.
item.attributes.headline; // Error: Property 'headline' is `unknown`
}
// ✅ This works because `__itemTypeId` is a top-level property.
if (item.__itemTypeId === "BxZ9Y2aKQVeTnM4hP8wLpD") {
// TypeScript now correctly infers the type.
item.attributes.headline; // OK!
}

Object payload

id string

RFC 4122 UUID of record expressed in URL-safe base64 format

Example: "hWl-mnkWRYmMCSTq4z_piQ"
type string

Must be exactly "item".

meta.created_at date-time

Date of creation

meta.updated_at date-time

Last update time

meta.published_at

Date of last publication

Type: null, date-time
meta.first_published_at

Date of first publication

Type: null, date-time
meta.publication_scheduled_at

Date of future publication

Type: null, date-time
meta.unpublishing_scheduled_at

Date of future unpublishing

Type: null, date-time
meta.status null, enum

Status

Example: "published"
draft

The record is not published

updated

The record has some unpublished changes

published

The record is published

meta.is_current_version_valid null, boolean

Whether the current version of the record is valid or not

meta.is_published_version_valid null, boolean

Whether the published version of record is valid or not

meta.current_version string

The ID of the current record version

Example: "4234"
meta.stage null, string

Workflow stage in which the item is

meta.has_children null, boolean

When the records can be organized in a tree, indicates whether the record has children

item_type

The record's model

creator

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

meta.is_valid boolean Deprecated

Whether the current record is valid or not

This field will be removed in the future: use is_current_version_valid or is_published_version_valid instead, according to the specific use case

Available endpoints