Guide 06 · Developer docs

The APIs: REST, GraphQL and custom endpoints

Every Core dna site answers three kinds of API request under /cdna-api: REST for everything, GraphQL for reads that span several entity types, and web events for endpoints you define yourself as jobs. Which fits which task, one worked request for each, and what is true of all three. Jobs were called hooks until the module was renamed Orchestration; the API's routes still use the old name.

L1–L3
Unit 02 · Writing content

Custom fields

Custom fields are the extra fields an administrator adds to menus, products, users and a dozen other record types. How to find a record's fields, read them by name, write one without touching the others, and why the answer to a write can look as if fields were lost.

Who this is for. The integrations engineer syncing product attributes, user profile data or menu settings with another system.

What has custom fields

Custom fields are defined in the admin, one set per record type, and the API reads and writes their values but not their definitions. These record types can have them: books, book units and book pages; categories, tags, layouts and SEO settings; forms; menus and menu elements; products and product categories; addresses; authors, people and users. A field's input type is text (with any of the text editors: plain, select, switch, date and the rest), image, file, record or records; never a component.

Two differences from layout areas matter to a client:

  • Custom fields are not versioned. They belong to the record, not to a revision, so a write changes them at once, with no revision to activate and none to roll back to.
  • Pages, blog posts and custom entity records have no custom fields. A custom entity record's fields are layout areas: see Writing areas.

Finding a record type's fields

The definitions hang off the input types. Ask for the input types used by custom fields on one entity class, with the fields' names:

The custom fields of a menu element
GET /cdna-api/common/input-types?customFields.entity=Menus\Entities\Element&properties={id,name,category,editor,possibleValues{value},customFields{id,name,label,entity,required}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Response 200 OK
{
  "result": "success",
  "totalCount": 2,
  "data": [
    {
      "id": 10,
      "name": "Image",
      "category": "image",
      "editor": null,
      "possibleValues": [],
      "customFields": [
        {"id": 3, "name": "apiTestImage", "label": null, "entity": "Menus\\Entities\\Element", "required": false}
      ]
    },
    {
      "id": 1,
      "name": "Text",
      "category": "text",
      "editor": "text",
      "possibleValues": [],
      "customFields": [
        {"id": 1, "name": "apiTestText", "label": null, "entity": "Menus\\Entities\\Element", "required": false},
        {"id": 2, "name": "apiTestNote", "label": null, "entity": "Menus\\Entities\\Element", "required": false}
      ]
    }
  ]
}
Real response from this documentation site, whose sandbox menu element has three test fields

The entity class is the platform's, with backslashes: Products\Entities\Product, Users\Entities\User, Common\Entities\Category. A select field's option keys are in possibleValues, as for a layout area: the part before ::.

Reading values

customFieldContent returns the values keyed by field name, each described the way areasContent describes a layout area. customFieldValues returns the rows behind them, with their ids, which you need only to write rows by id.

Read a menu element's custom fields
GET /cdna-api/menus/element/53?properties={id,name,customFieldContent,customFieldValues{id,customField{id,name},content{id}}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Response 200 OK
{
  "result": "success",
  "data": {
    "id": 53,
    "name": "Sandbox element",
    "customFieldContent": {
      "apiTestText": {"id": 75999, "type": "Text", "category": "text", "editor": "text", "text": null},
      "apiTestNote": {"id": 75997, "type": "Text", "category": "text", "editor": "text", "text": "note changed"},
      "apiTestImage": {"id": 75998, "type": "Image", "category": "image", "imageId": 1, "imagePath": "/files/testx/CW-blog-h1.jpg"}
    },
    "customFieldValues": [
      {"id": 1, "customField": {"id": 1, "name": "apiTestText"}, "content": {"id": 75999}},
      {"id": 2, "customField": {"id": 2, "name": "apiTestNote"}, "content": {"id": 75997}},
      {"id": 3, "customField": {"id": 3, "name": "apiTestImage"}, "content": {"id": 75998}}
    ]
  }
}
Real response from this documentation site, on its sandbox menu element

Read again after a write. The answer to a custom field write lists the rows as that request left them, and it can leave fields out: a field emptied in the request, and every row a customFieldValues list did not name, are missing from the answer though they are still the record's. A field that has never been set has no row, and is missing from every read. Treat a missing key and an empty value the same, and read the record again when you need its fields.

Writing some fields: by name

Send customFields, an object keyed by field name, on the record's ordinary edit route. Only the fields named change; the others keep their values.

Write one custom field by name
PUT /cdna-api/menus/element/53?properties={id,customFieldContent,customFieldValues{id,customField{name},content{id}}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{
  "customFields": {
    "apiTestText": "Written by name"
  }
}
Response
{
  "result": "success",
  "data": {
    "id": 53,
    "customFieldContent": {
      "apiTestText": {"id": 90095, "type": "Text", "category": "text", "editor": "text", "text": "Written by name"},
      "apiTestNote": {"id": 75997, "type": "Text", "category": "text", "editor": "text", "text": "note changed"},
      "apiTestImage": {"id": 75998, "type": "Image", "category": "image", "imageId": 1, "imagePath": "/files/testx/CW-blog-h1.jpg"}
    },
    "customFieldValues": [
      {"id": 1, "customField": {"name": "apiTestText"}, "content": {"id": 90095}},
      {"id": 2, "customField": {"name": "apiTestNote"}, "content": {"id": 75997}},
      {"id": 3, "customField": {"name": "apiTestImage"}, "content": {"id": 75998}}
    ]
  },
  "message": "The Menu Element has been saved successfully."
}
{
  "result": "error",
  "validationErrors": {
    "customFields.apiTestTxt": [
      {
        "message": "No custom field found for Menus\\Entities\\Element named apiTestTxt",
        "code": "f170ed60-50f3-34ad-3bf8-e478cd926935",
        "propertyPath": "customFields.apiTestTxt"
      }
    ]
  },
  "message": "No custom field found for Menus\\Entities\\Element named apiTestTxt"
}
Real response from this documentation site, on its sandbox menu element

The value for each input type:

Input typeSend
Text, Textarea and the other text editorsA string or number. A select takes its option key
Switchtrue or 1 for on; anything else is off
Image, FileThe image's or file's id. One that does not exist is a 422 "Could not find the referenced record with id …", keyed image or file rather than by the field's name
RecordA record id
RecordsA list of record ids
Anynull empties the field

A name that is not a field of this record type is a 422 that names it, as above. Several fields, and the record's own properties, can go in one request.

Writing rows by id

customFieldValues writes the rows themselves. What happens to a row you leave out depends on the record type:

  • Products, product categories and people delete it: the list replaces the record's custom fields. (This site has no custom fields on those types to show it; the rule is in the platform's code.)
  • Every other type keeps it, but the write's answer leaves it out, so the write looks as if it removed the field. On the sandbox menu element, a write naming one row of three answered with one field; reading the element again showed all three.
Write one row: the answer leaves the other two out
PUT /cdna-api/menus/element/53?properties={id,customFieldContent,customFieldValues{id,customField{name},content{id}}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{
  "customFieldValues": [
    {"id": 1, "content": {"id": 75999, "text": "Written by row"}}
  ]
}
Response 200 OKThe answer shows one field
{
  "result": "success",
  "data": {
    "id": 53,
    "customFieldContent": {
      "apiTestText": {"id": 75999, "type": "Text", "category": "text", "editor": "text", "text": "Written by row"}
    },
    "customFieldValues": [
      {"id": 1, "customField": {"name": "apiTestText"}, "content": {"id": 75999}}
    ]
  },
  "message": "The Menu Element has been saved successfully."
}
Real response from this documentation site, on its sandbox menu element. Read again, the element still had all three fields

Each row in the list takes one of three shapes: an unchanged row as {"id": 2}; a changed row as {"id": 1, "content": {"id": <content id>, "text": …}}; and a row for a field that has none yet as {"customField": <field id>, "content": {"type": <input type id>, "text": …}}. A new row for a field that already has one, even an emptied one, does not work: write to the existing row's id instead. An emptied field keeps its row, though a write's answer hides it. Read customFieldValues{id,customField{id}} first and use the row ids. For most writes, by name is shorter, finds the rows itself, and cannot remove a field.

Filtering a list by a custom field

A list can be filtered on the value: customFieldValues.content.text=note changed returns the records where any custom field holds that text. Naming the field as well does not narrow it to that field: each dotted filter is matched on its own, so on the sandbox, customFieldValues.customField.name=apiTestImage together with customFieldValues.content.text=note changed matched the element, though the text is in a different field. Filter on values that only one field can hold, or check the field in the client.

Go deeper

Was this page helpful?

Your answer goes to the docs team.