You can determine what custom fields are relevant to an entry through its field layout:
$layout = $entry->getFieldLayout();
A FieldLayout
may contain more than just custom fields (like headings and native fields), but you can filter those “field layout elements” down to just the ones we care about:
$fields = $layout->getCustomFields();
(In Craft 3, this would just be $layout->getFields()
!)
Then, you’ll need to loop over this array of Field
s and pull values off of the original $entry
. Here’s a function that puts this all together:
use craft\elements\Entry;
function getCustomFieldData(Entry $entry) {
$values = [];
// Fetch the entry’s field layout:
/** @var craft\models\FieldLayout $layout */
$layout = $entry->getFieldLayout();
// Get all custom fields in the layout:
$fields = $layout->getCustomFields();
// Loop over the fields:
foreach ($fields as $field) {
// Stash a reference to the field’s handle:
$handle = $field->handle;
// Grab the field’s value:
$value = $entry->getFieldValue($handle);
// Check what field type this is, so we can deal with its value appropriately:
switch (get_class($field)) {
// Date + Time fields...
case craft\fields\Date::class:
$values[$handle] = $value->format(DateTime::ATOM);
break;
// Asset fields...
case craft\fields\Assets::class:
// Get an array of Assets by executing the element query:
$values[$handle] = array_map(function ($asset) {
return $asset->url();
}, $value->all());
break;
// Any other type of field we haven’t specifically handled...
default:
// ...just gets the raw value:
$values[$handle] = $value;
}
}
return $values;
}
This function can live in your element-api.php
file, a Transformer, or a module or plugin.
Your endpoint might look something like this:
<?php
use craft\elements\Entry;
// ...function, from above!
return [
'endpoints' => [
'api/<entryId:\d+>.json' => function($entryId) {
return [
'resourceKey' => 'stories',
'elementType' => Entry::class,
'criteria' => [
'id' => $entryId
],
'transformer' => function(Entry $entry) {
return [
'id' => $entry->id,
'title' => $entry->title,
'url' => $entry->url,
// From the transformer, call our custom function with the current entry:
'customFields' => getCustomFieldData($entry),
];
},
];
}
]
];
This should return a structure that looks something like this:
{
"data":[
{
"id":"28",
"title":"The entry's title",
"url":"http://domain.com/entry-url",
"customFields":{
"assets":[
"http://domain.com/assets/image.png",
"http://domain.com/assets/image-alt.png"
],
"plainText":"Some text"
}
}
],
"meta":{
"pagination":{
"total":1,
"count":1,
"per_page":100,
"current_page":1,
"total_pages":1,
"links":{
}
}
}
}
transform
function?function (Entry $entry) { return $entry }
to see what the native serializer does with the class +ContentBehavior
trait—it may well be too verbose for a JSON API… If all else fails, you could write some kind of Transformer class that iterates over$entry->getAttributes()
and$entry->getFieldValues()
and attempts to coerce each type of field/data into the proper format. I've done this for packing data into a CSV, but not for JSON—it becomes additionally challenging when you start dealing with relational fields.