# 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](https://www.datocms.com/docs/content-management-api/resources/item.md#object-payload) section for the full object payload documentation.

```json
// 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" }
    }
  }
}
```

> [!PROTIP] 📘 New to content modeling?
> Check out the [Content Modeling Guide](https://www.datocms.com/docs/content-modelling.md) 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):

<details>
<summary>Single-line string</summary>

The field accepts `String` values or `null`.

</details>

<details>
<summary>Slug</summary>

The field accepts `String` values or `null`.

</details>

<details>
<summary>Multi-line text</summary>

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

</details>

<details>
<summary>Boolean</summary>

The field accepts simple `Boolean` values or `null`.

</details>

<details>
<summary>Integer</summary>

The field accepts simple `Integer` values or `null`.

</details>

<details>
<summary>Float</summary>

The field accepts simple `Float` values or `null`.

</details>

<details>
<summary>Date</summary>

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

</details>

<details>
<summary>Date time</summary>

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](https://www.datocms.com/product-updates/improved-timezone-management.md), 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.

</details>

<details>
<summary>JSON</summary>

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

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

</details>

###### Object Fields

These require structured objects:

<details>
<summary>Color</summary>

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

| Property | Required | Type |
| --- | --- | --- |
| `red` | ✅ | `Integer` between 0 and 255 |
| `green` | ✅ | `Integer` between 0 and 255 |
| `blue` | ✅ | `Integer` between 0 and 255 |
| `alpha` | ✅ | `Integer` between 0 and 255 |

</details>

<details>
<summary>Location</summary>

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

| Property | Required | Type |
| --- | --- | --- |
| `latitude` | ✅ | `Float` between -90.0 to 90 |
| `longitude` | ✅ | `Float` between -180.0 to 180 |

</details>

<details>
<summary>SEO</summary>

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

| Property | Required | Type | Description |
| --- | --- | --- | --- |
| `title` |  | `String` | Title meta tag (max. 320 characters) |
| `description` |  | `String` | Description meta tag (max. 320 characters) |
| `image` |  | `Upload ID` | Asset to be used for social shares |
| `twitter_card` |  | `"summary"`, `"summary_large_image"` | Type of Twitter card to use |
| `no_index` |  | `Boolean` | Whether the noindex meta tag should be returned |

</details>

<details>
<summary>External video</summary>

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

| Property | Required | Type | Description | Example |
| --- | --- | --- | --- | --- |
| `provider` | ✅ | `"youtube"`, `"vimeo"`, `"facebook"` | External video provider | `"youtube"` |
| `provider_uid` | ✅ | `String` | Unique identifier of the video within the provider | `"vUdGBEb1i9g"` |
| `url` | ✅ | `URL` | URL of the video | `"https://www.youtube.com/watch?v=qJhobECFQYk"` |
| `width` | ✅ | `Integer` | Video width | `459` |
| `height` | ✅ | `Integer` | Video height | `344` |
| `thumbnail_url` | ✅ | `URL` | URL for the video thumb | `"https://i.ytimg.com/vi/vUdGBEb1i9g/hqdefault.jpg"` |
| `title` | ✅ | `String` | Title of the video | `"Next.js Conf Booth Welcoming!"` |

</details>

###### Reference Fields

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

<details>
<summary>Single-asset</summary>

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

| Property | Required | Type | Description | Example |
| --- | --- | --- | --- | --- |
| `upload_id` | ✅ | `Upload ID` | ID of an asset | `"dhVR2HqgRVCTGFi0bWqLqA"` |
| `title` |  | `String` | Title for the asset, if you want to override the asset's default value (see Upload `default_field_metadata`) | `"From my trip to Italy"` |
| `alt` |  | `String` | Alternate 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 }`, `null` | Focal 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 }` |
| `poster_time` |  | `Float`, `null` | Time (in seconds) into the video used to generate the thumbnail, if you want to override the asset's default value (see Upload `default_field_metadata`). Poster time can only be specified for video assets. | `12.5` |
| `custom_data` |  | `Record<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)

</details>

<details>
<summary>Asset gallery</summary>

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

| Property | Required | Type | Description | Example |
| --- | --- | --- | --- | --- |
| `upload_id` | ✅ | `Upload ID` | ID of an asset | `"dhVR2HqgRVCTGFi0bWqLqA"` |
| `title` |  | `String` | Title for the asset, if you want to override the asset's default value (see Upload `default_field_metadata`) | `"Gallery Image Title"` |
| `alt` |  | `String` | Alternate 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 }`, `null` | Focal 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 }` |
| `poster_time` |  | `Float`, `null` | Time (in seconds) into the video used to generate the thumbnail, if you want to override the asset's default value (see Upload `default_field_metadata`). Poster time can only be specified for video assets. | `12.5` |
| `custom_data` |  | `Record<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

</details>

<details>
<summary>Single link</summary>

This field accepts a `String` representing the ID of the linked record, or `null`. See [Link Fields Guide](https://www.datocms.com/docs/content-modelling/links.md) for relationship modeling concepts.

**API responses**: Always returns record ID only

</details>

<details>
<summary>Multiple links</summary>

This field accepts an `Array<String>` representing the IDs of the linked records, or `null`. See [Link Fields Guide](https://www.datocms.com/docs/content-modelling/links.md) for relationship modeling concepts.

**API responses**: Always returns array of record IDs only

</details>

###### Block Fields

These are special fields that contain **blocks within records**:

| Field Type | What it contains |
| --- | --- |
| **Modular content** | An array of blocks, perfect for building dynamic page sections |
| **Single block** | A single block instance or `null` |
| **Structured text** | A rich text document that can have blocks embedded within the flow of content ([DAST format](https://www.datocms.com/docs/structured-text/dast.md)) |

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

> [!PROTIP] 📚 Content Modeling Context
> To understand when and how to design blocks vs models, see [Blocks Guide](https://www.datocms.com/docs/content-modelling/blocks.md). For field-specific concepts, see [Modular Content](https://www.datocms.com/docs/content-modelling/modular-content.md) and [Structured Text](https://www.datocms.com/docs/content-modelling/structured-text.md).

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.
    
    ```json
    {
      "id": "dhVR2HqgRVCTGFi_0bWqLqA",
      "type": "item",
      "attributes": {
        "title": "Block Title",
        "content": "Block content..."
      },
      "relationships": {
        "item_type": {
          "data": { "id": "BxZ9Y2aKQVeTnM4hP8wLpD", "type": "item_type" }
        }
      }
    }
    ```
    

<details>
<summary>Modular Content</summary>

A Modular Content field holds an array of blocks.

**As an array of IDs:**

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

**As an array of full objects:**

```json
{
  "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" } } }
    }
  ]
}
```

</details>

<details>
<summary>Single Block</summary>

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

**As an ID:**

```json
{
  "featured_block": "dhVR2HqgRVCTGFi_0bWqLqA"
}
```

**As an full object:**

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

</details>

<details>
<summary>Structured Text</summary>

A Structured Text field can contain blocks within its document structure ([DAST format](https://www.datocms.com/docs/structured-text/dast.md)). The item property of a `block` or `inlineBlock` node will hold either the ID or the full object.

**With block IDs:**

```json
{
  "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:**

```json
{
  "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" } } }
          }
        }
      ]
    }
  }
}
```

</details>

---

## 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:

-   [Retrieve a single record (`GET /items/:id`)](https://www.datocms.com/docs/content-management-api/resources/item/self.md)
-   [Retrieve multiple records (`GET /items`)](https://www.datocms.com/docs/content-management-api/resources/item/instances.md)
-   [Retrieve records referenced by a record (`GET /items/:id/references`)](https://www.datocms.com/docs/content-management-api/resources/item/references.md)
-   [Retrieve records linked to an asset (`GET /upload/:id/references`)](https://www.datocms.com/docs/content-management-api/resources/upload/references.md)

###### 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.

```json
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.

```json
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": { ... }
    }
  }
}
```

> [!WARNING] 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 navigation | Preparing 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.

> [!PROTIP] 🆔 Custom block IDs
> Any new block payload (on create or update) may optionally include an `id` (an RFC 4122 v4 UUID expressed in URL-safe base64). If provided, that value becomes the new block's ID; otherwise the server generates one. On **update**, the `id` is interpreted as follows: if it matches a block already in the parent record, that block is updated; otherwise — provided the `id` is a valid v4-base64 UUID and not yet used by any record in the environment — a new block is created with that ID. An `id` that resolves to a record outside the parent, or is not a valid v4-base64 UUID, is rejected.

The following examples show how to apply these rules.

<details>
<summary>Working with Modular Content Fields</summary>

**Current state** (from a regular API response):

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

**To update the second block and reorder the others:**

```json
{
  "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:**

```json
{
  "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" }
        }
      }
    }
  ]
}
```

</details>

<details>
<summary>Working with Single Block Fields</summary>

**Current state** (from a regular API response):

```json
{
  "hero_block": "dhVR2HqgRVCTGFi_0bWqLqA"
}
```

**To update the block's content:**

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

**To replace it with a new block:**

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

**To remove (delete) the block:**

```json
{
  "hero_block": null
}
```

</details>

<details>
<summary>Working with Structured Text Fields</summary>

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):

```json
{
  "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:**

```json
{
  "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." }] }
      ]
    }
  }
}
```

</details>

###### 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.

<details>
<summary>Example: Updating a nested block</summary>

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:

```json
// 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
    }
  }
}
```

</details>

---

## 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:

```json
{
  "title": "Hello World"
}
```

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

```json
{
  "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.

<details>
<summary>Example: Localized Modular Content field</summary>

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

```json
{
  "content_blocks": {
    "en": ["dhVR2HqgRVCTGFi0bWqLqA", "kL9mN3pQrStUvWxYzAbCdE"],
    "it": ["fG8hI1jKlMnOpQrStUvWxY", "dhVR2HqgRVCTGFi0bWqLqA"]
  }
}
```

</details>

<details>
<summary>Example: Localized Single Block field</summary>

A different block can be assigned to each locale.

```json
{
  "hero_block": {
    "en": "dhVR2HqgRVCTGFi0bWqLqA",
    "it": "kL9mN3pQrStUvWxYzAbCdE"
  }
}
```

</details>

<details>
<summary>Example: Localized Structured Text field</summary>

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

```json
{
  "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" }
        ]
      }
    }
  }
}
```

</details>

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.

```json
// ❌ 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`](https://www.datocms.com/docs/content-management-api/resources/item-type.md#object-payload) 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.

```json
// ❌ 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:

```typescript
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](https://www.datocms.com/docs/scripting-migrations/installing-the-cli.md), you can use the `schema:generate` command to generate a comprehensive TypeScript definition file describing your DatoCMS project structure (models and blocks):

Terminal window

```bash
$ npx datocms schema:generate schema.ts
```

The output describes your DatoCMS project structure (models and blocks) and emits, for each one, both an `ItemTypeDefinition` **type** and a runtime **constant** with `ID` / `REF` properties:

```typescript
// schema.ts — generated by `npx datocms schema:generate`.
//
// ⚠️ Do not hand-write or hand-edit these types in your own code. The
// generator is authoritative: re-run it whenever the schema changes, and
// import from the generated file. Redeclaring ItemTypeDefinition<...>
// inline duplicates the schema and drifts silently the moment a field
// is added, renamed, or removed.

import type { ItemTypeDefinition } from '@datocms/cma-client';

type EnvironmentSettings = { locales: 'en' | 'it' };

export type Article = ItemTypeDefinition<
  EnvironmentSettings,
  '76hhD-LaS5CM3NPJw0991w', // ID of the Article model
  {
    name: { type: 'string' };
    slug: { type: 'slug' };
    accent_color: { type: 'color' };
    sections: { type: 'rich_text'; blocks: ArticleSection };
  }
>;
export const Article = {
  ID: '76hhD-LaS5CM3NPJw0991w',
  REF: { type: 'item_type', id: '76hhD-LaS5CM3NPJw0991w' },
} as const;
```

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.

> [!WARNING] 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).

> [!PROTIP] Other ways to generate types
> Use `--item-types=product,article` to scope generation to specific models/blocks, or include the same definitions inline in a [migration script](https://www.datocms.com/docs/scripting-migrations/scripting-migrations-with-the-datocms-cli.md#option-1-write-a-migration-script-manually) via `npx datocms migrations:new 'tweak articles' --schema=article,author` (or `--schema=all`).

###### Using markers in API calls

Each generated `ItemTypeDefinition` (e.g. `Article`) acts as a **marker**: a branded type the client uses to infer the right request and response shapes when you pass it as a generic. By convention, import the generated file as a `Schema` namespace so each marker reads as `Schema.Article`, `Schema.ArticleSection`, etc.:

```typescript
import * as Schema from './schema';
```

Use a marker as a **type** when calling generic methods like `client.items.create<Schema.Article>(...)`, and as a **value** to reference the model's ID (`Schema.Article.ID`) or build an `item_type` relationship (`Schema.Article.REF`).

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

```typescript
// Fully typed record retrieval
const record = await client.items.find<Schema.Article>('AZUeMuPySxuJCJ8ibEVE7w');
record.accent_color; // -> { red; green; blue; alpha } (properly typed!)

// Type-safe record creation
const record = await client.items.create<Schema.Article>({
  item_type: Schema.Article.REF,
  accent_color: '#FF0000', // ✅ TypeScript catches the wrong format!
});
```

###### Extracting a concrete field type

When you need the actual TypeScript type of a field — to annotate a helper, an intermediate variable, or a function parameter — reach for one of the `FieldValue*` helpers. A marker can't be indexed directly: its top-level keys are blueprint metadata, not field API keys.

```typescript
// ❌ TypeScript error — Schema.Article describes the model, not its payload
type Sections = Schema.Article['sections'];
```

Which helper you pick depends on which side of the wire you're on:

| Helper | Resolves to the field as it appears in… |
| --- | --- |
| `FieldValueInRequest<T, 'field_key'>` | a `client.items.create` / `client.items.update` payload |
| `FieldValue<T, 'field_key'>` | a default response (e.g. `client.items.find(id)`) |
| `FieldValueInNestedResponse<T, 'field_key'>` | a nested response (e.g. `client.items.find(id, { nested: true })`) |

Each one accepts the same first argument in two equivalent forms:

-   **A value already in scope** — pass `typeof record` or `typeof block`. This is the common case when you've just fetched a record, or narrowed a nested block with `isBlockOfType`. No need to restate the model name.
-   **A model marker** — pass `Schema.X` directly. Use this when no value is in scope yet — typing a helper that builds a payload from scratch, or a function parameter that hasn't read anything.

**From a fetched value**

The same expression works on a top-level record and on a narrowed nested block:

```typescript
import {
  buildBlockRecord,
  type FieldValueInRequest,
  isBlockOfType,
} from '@datocms/cma-client';
import * as Schema from './schema';

const page = await client.items.find<Schema.LandingPage>(id, { nested: true });

const sections: NonNullable<FieldValueInRequest<typeof page, 'sections'>> = [];

for (const block of page.sections) {
  if (isBlockOfType(Schema.HeroBlock.ID, block)) {
    // Same expression, applied to a narrowed nested block.
    const ctas: NonNullable<FieldValueInRequest<typeof block, 'ctas'>> = [];
    // …rebuild ctas, then push the new HeroBlock into sections…
  } else {
    sections.push(block.id);
  }
}

await client.items.update<Schema.LandingPage>(page.id, { sections });
```

**From a model marker**

```typescript
import { buildBlockRecord, type FieldValueInRequest } from '@datocms/cma-client';
import * as Schema from './schema';

type Sections = NonNullable<FieldValueInRequest<Schema.Article, 'sections'>>;

function buildLaunchSections(headline: string): Sections {
  return [
    buildBlockRecord<Schema.ArticleSection>({
      item_type: Schema.ArticleSection.REF,
      title: headline,
    }),
  ];
}
```

> [!PROTIP] Typing whole payloads
> The `FieldValue*` helpers each materialize a single field's type. To annotate an **entire** create/update payload — or an entire response — reach for the corresponding `ApiTypes.*` shape (`ApiTypes.ItemCreateSchema<Schema.X>`, `ApiTypes.ItemUpdateSchema<Schema.X>`, `ApiTypes.Item<Schema.X>`, `ApiTypes.ItemInNestedResponse<Schema.X>`).

###### Narrowing a record or block to a specific model

When you have a value whose static type is a union of models (e.g. a record pulled from a mixed `list`, or a block from a Modular Content field), TypeScript needs to know *which* one you're holding before it'll let you reach into its attributes. The model ID is the natural way to tell them apart, but its canonical location (`relationships.item_type.data.id`) is nested four levels deep and TypeScript won't auto-narrow through that path.

Use the `isBlockOfType` helper from `@datocms/cma-client` instead — it's a proper type-guard predicate that works in both inline checks and array methods. It supports two equivalent calling styles:

```typescript
import { isBlockOfType } from "@datocms/cma-client";
import * as Schema from './schema';

const article = await client.items.find<Schema.Article>(articleId, {
  nested: true,
});

// 1. Inline check — pass both the model ID and the value.
for (const block of article.content) {
  if (isBlockOfType(Schema.HeroBlock.ID, block)) {
    block.attributes.headline; // OK — narrowed to HeroBlock
  }
}

// 2. As a predicate — pass just the model ID, get back a curried type guard
//    suitable for `.filter` / `.find`. A bare equality check here would NOT
//    narrow the result type.
const images = article.content.filter(isBlockOfType(Schema.ImageBlock.ID));
images[0].attributes.upload_id; // OK — narrowed to ImageBlock
```

> [!PROTIP] Skipping the helper for inline checks
> Every record and block in responses also carries a top-level `__itemTypeId` property, so `if (item.__itemTypeId === Schema.HeroBlock.ID)` narrows just as well as `isBlockOfType` for inline checks.

## Object payload

**`id`**

- Type: string
- Example: `"hWl-mnkWRYmMCSTq4z_piQ"`

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

**`type`**

- Type: string

Must be exactly `"item"`.

**`meta.created_at`**

- Type: date-time

Date of creation

**`meta.updated_at`**

- Type: date-time

Last update time

**`meta.published_at`**

- Type: null, date-time

Date of last publication

**`meta.first_published_at`**

- Type: null, date-time

Date of first publication

**`meta.publication_scheduled_at`**

- Type: null, date-time

Date of future publication

**`meta.unpublishing_scheduled_at`**

- Type: null, date-time

Date of future unpublishing

**`meta.status`**

- Type: null, enum
- Example: `"published"`

Status

<details>
<summary>Show enum values</summary>

**`draft`**

The record is not published

**`updated`**

The record has some unpublished changes

**`published`**

The record is published

</details>

**`meta.is_current_version_valid`**

- Type: null, boolean

Whether the current version of the record is valid or not

**`meta.is_published_version_valid`**

- Type: null, boolean

Whether the published version of record is valid or not

**`meta.current_version`**

- Type: string
- Example: `"4234"`

The ID of the current record version

**`meta.stage`**

- Type: null, string

Workflow stage in which the item is

**`meta.has_children`**

- Type: null, boolean

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

**`item_type`**

- Type: [ResourceLinkage\<"item_type"\>](https://www-draft.datocms.com/docs/content-management-api/resources/item_type.md)

The record's model

**`creator`**

- Type: [ResourceLinkage\<"account"\>](https://www-draft.datocms.com/docs/content-management-api/resources/account.md), [ResourceLinkage\<"access_token"\>](https://www-draft.datocms.com/docs/content-management-api/resources/access_token.md), [ResourceLinkage\<"user"\>](https://www-draft.datocms.com/docs/content-management-api/resources/user.md), [ResourceLinkage\<"sso_user"\>](https://www-draft.datocms.com/docs/content-management-api/resources/sso_user.md), [ResourceLinkage\<"organization"\>](https://www-draft.datocms.com/docs/content-management-api/resources/organization.md)

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

<details>
<summary>Show deprecated</summary>

**`meta.is_valid`**

- Deprecated
- Type: boolean

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

</details>

## Available endpoints

-   [List all records](https://www.datocms.com/docs/content-management-api/resources/item/instances.md)
-   [Create a new record](https://www.datocms.com/docs/content-management-api/resources/item/create.md)
-   [Duplicate a record](https://www.datocms.com/docs/content-management-api/resources/item/duplicate.md)
-   [Update a record](https://www.datocms.com/docs/content-management-api/resources/item/update.md)
-   [Referenced records](https://www.datocms.com/docs/content-management-api/resources/item/references.md)
-   [Retrieve a record](https://www.datocms.com/docs/content-management-api/resources/item/self.md)
-   [Delete a record](https://www.datocms.com/docs/content-management-api/resources/item/destroy.md)
-   [Publish a record](https://www.datocms.com/docs/content-management-api/resources/item/publish.md)
-   [Unpublish a record](https://www.datocms.com/docs/content-management-api/resources/item/unpublish.md)
-   [Publish items in bulk](https://www.datocms.com/docs/content-management-api/resources/item/bulk_publish.md)
-   [Unpublish items in bulk](https://www.datocms.com/docs/content-management-api/resources/item/bulk_unpublish.md)
-   [Destroy items in bulk](https://www.datocms.com/docs/content-management-api/resources/item/bulk_destroy.md)
-   [Move items to stage in bulk](https://www.datocms.com/docs/content-management-api/resources/item/bulk_move_to_stage.md)

## Related content in "Content Management API"

- [Content Management API Overview](https://www.datocms.com/docs/content-management-api.md)
- [Using the JavaScript CMA client](https://www.datocms.com/docs/content-management-api/using-the-nodejs-clients.md)
- [API versioning](https://www.datocms.com/docs/content-management-api/api-versioning.md)
- [Authentication](https://www.datocms.com/docs/content-management-api/authentication.md)
- [Environments](https://www.datocms.com/docs/content-management-api/setting-the-environment.md)
- [Error codes & handling failures (CMA)](https://www.datocms.com/docs/content-management-api/errors.md)
- [Pagination](https://www.datocms.com/docs/content-management-api/pagination.md)
- [Asynchronous jobs](https://www.datocms.com/docs/content-management-api/async-jobs.md)
- [CMA Technical Limits & Rate Limits](https://www.datocms.com/docs/content-management-api/technical-limits.md)
- [Record](https://www.datocms.com/docs/content-management-api/resources/item.md)
- [List all records](https://www.datocms.com/docs/content-management-api/resources/item/instances.md)
- [Create a new record](https://www.datocms.com/docs/content-management-api/resources/item/create.md)
- [Duplicate a record](https://www.datocms.com/docs/content-management-api/resources/item/duplicate.md)
- [Update a record](https://www.datocms.com/docs/content-management-api/resources/item/update.md)
- [Referenced records](https://www.datocms.com/docs/content-management-api/resources/item/references.md)
- [Retrieve a record](https://www.datocms.com/docs/content-management-api/resources/item/self.md)
- [Delete a record](https://www.datocms.com/docs/content-management-api/resources/item/destroy.md)
- [Publish a record](https://www.datocms.com/docs/content-management-api/resources/item/publish.md)
- [Unpublish a record](https://www.datocms.com/docs/content-management-api/resources/item/unpublish.md)
- [Publish items in bulk](https://www.datocms.com/docs/content-management-api/resources/item/bulk_publish.md)
- [Unpublish items in bulk](https://www.datocms.com/docs/content-management-api/resources/item/bulk_unpublish.md)
- [Destroy items in bulk](https://www.datocms.com/docs/content-management-api/resources/item/bulk_destroy.md)
- [Move items to stage in bulk](https://www.datocms.com/docs/content-management-api/resources/item/bulk_move_to_stage.md)
- [Scheduled publication](https://www.datocms.com/docs/content-management-api/resources/scheduled-publication.md)
- [Scheduled unpublishing](https://www.datocms.com/docs/content-management-api/resources/scheduled-unpublishing.md)
- [Upload](https://www.datocms.com/docs/content-management-api/resources/upload.md)
- [Site](https://www.datocms.com/docs/content-management-api/resources/site.md)
- [Model/Block model](https://www.datocms.com/docs/content-management-api/resources/item-type.md)
- [Field](https://www.datocms.com/docs/content-management-api/resources/field.md)
- [Fieldset](https://www.datocms.com/docs/content-management-api/resources/fieldset.md)
- [Record version](https://www.datocms.com/docs/content-management-api/resources/item-version.md)
- [Upload permission](https://www.datocms.com/docs/content-management-api/resources/upload-request.md)
- [Upload track](https://www.datocms.com/docs/content-management-api/resources/upload-track.md)
- [Manual tags](https://www.datocms.com/docs/content-management-api/resources/upload-tag.md)
- [Smart tags](https://www.datocms.com/docs/content-management-api/resources/upload-smart-tag.md)
- [Upload Collection](https://www.datocms.com/docs/content-management-api/resources/upload-collection.md)
- [Search Index](https://www.datocms.com/docs/content-management-api/resources/search-index.md)
- [Search result](https://www.datocms.com/docs/content-management-api/resources/search-result.md)
- [Search indexing activity](https://www.datocms.com/docs/content-management-api/resources/search-index-event.md)
- [Environment](https://www.datocms.com/docs/content-management-api/resources/environment.md)
- [Maintenance mode](https://www.datocms.com/docs/content-management-api/resources/maintenance-mode.md)
- [Menu Item](https://www.datocms.com/docs/content-management-api/resources/menu-item.md)
- [Schema Menu Item](https://www.datocms.com/docs/content-management-api/resources/schema-menu-item.md)
- [Uploads filter](https://www.datocms.com/docs/content-management-api/resources/upload-filter.md)
- [Model filter](https://www.datocms.com/docs/content-management-api/resources/item-type-filter.md)
- [Plugin](https://www.datocms.com/docs/content-management-api/resources/plugin.md)
- [Workflow](https://www.datocms.com/docs/content-management-api/resources/workflow.md)
- [Asynchronous job](https://www.datocms.com/docs/content-management-api/resources/job.md)
- [Job result](https://www.datocms.com/docs/content-management-api/resources/job-result.md)
- [Account](https://www.datocms.com/docs/content-management-api/resources/account.md)
- [Organization](https://www.datocms.com/docs/content-management-api/resources/organization.md)
- [Invitation](https://www.datocms.com/docs/content-management-api/resources/site-invitation.md)
- [Collaborator](https://www.datocms.com/docs/content-management-api/resources/user.md)
- [Role](https://www.datocms.com/docs/content-management-api/resources/role.md)
- [API token](https://www.datocms.com/docs/content-management-api/resources/access-token.md)
- [Webhook](https://www.datocms.com/docs/content-management-api/resources/webhook.md)
- [Webhook call](https://www.datocms.com/docs/content-management-api/resources/webhook-call.md)
- [Build trigger](https://www.datocms.com/docs/content-management-api/resources/build-trigger.md)
- [Deploy activity](https://www.datocms.com/docs/content-management-api/resources/build-event.md)
- [Subscription limit](https://www.datocms.com/docs/content-management-api/resources/subscription-limit.md)
- [Subscription feature](https://www.datocms.com/docs/content-management-api/resources/subscription-feature.md)
- [SSO Settings](https://www.datocms.com/docs/content-management-api/resources/sso-settings.md)
- [SSO User](https://www.datocms.com/docs/content-management-api/resources/sso-user.md)
- [SSO Group](https://www.datocms.com/docs/content-management-api/resources/sso-group.md)
- [White-label settings](https://www.datocms.com/docs/content-management-api/resources/white-label-settings.md)
- [Audit log event](https://www.datocms.com/docs/content-management-api/resources/audit-log-event.md)