Which built-in type fits which purpose, how categories, tags, slugs, custom URLs and SEO settings behave across them, how revisions and publishing gate what the public sees, how to model a domain the platform has no type for, and where uploaded files live.
L1–L3
Unit 05 · Custom fields
Reading custom fields
Custom fieldsPage 30 of 35Updated
Custom fields are read with typed getters, the same in Smarty and Twig on every entity that has them, and the input type decides what comes back. Reading each kind, the empty case, and the shape in an API response.
Who this is for. Developers writing templates or integrations that use custom fields. After this page you can read a custom field of any type in either engine and through the API, and handle a field that has no value.
Typed getters, never the shorthand
A layout area can be printed with the shorthand, {$page->intro} or {{ page.intro }}. A custom field cannot: the shorthand resolves layout areas only, so {$person->jobTitle} raises "Property jobTitle does not exist" in Smarty and prints nothing in Twig, even when the person has a jobTitle custom field. Read a custom field with the getter for its category. The getters are the same on all sixteen entities and in both engines.
Getter
For
Returns
With no value
getCustomFieldText(name, default)
text, any editor
The stored text. A select returns its option key, a switch '1' or '0', a date or datetime its stored string
The field's default, then the default you pass, then null
getCustomFieldBoolean(name, default)
text, switch editor
true or false
The field's default, then the default you pass, then null
getCustomFieldImage(name)
image
The image
null
getCustomFieldFile(name)
file
The file
null
getCustomFieldValueByName(name)
any; the way to read record and records
The stored value, whose getContent() gives getRecordModel() (record) or getRecordModels() (records)
null
getCustomFields()
The custom field definitions of the record's entity, for a loop over every field
getCustomFieldValues()
The values stored on this record
A date custom field comes back as its stored string. A date layout area's shorthand returns a printable date object, but custom fields have no date-aware getter. The two layers of default are explained on Defining a custom field.
Text, select and switch fields
Menu elements are the usual place for them. Here element is the menu element the menu template is looping over:
Template code
{$isMegaMenu = $element->getCustomFieldBoolean('isMegaMenu', false)}
{$icon = $element->getCustomFieldText('icon')}
{$anchor = $element->getCustomFieldText('anchorLink')}
<a href="{if $anchor}#{$anchor}{else}{$element->url}{/if}"
target="{$element->getCustomFieldText('target', '_self')}"{if $isMegaMenu} data-mega-menu="1"{/if}>
{if $icon}<span class="icon icon-{$icon}"></span>{/if}
{* the link text *}
</a>
{% set isMegaMenu = element.getCustomFieldBoolean('isMegaMenu', false) %}
{% set icon = element.getCustomFieldText('icon') %}
{% set anchor = element.getCustomFieldText('anchorLink') %}
<a href="{{ anchor is empty ? element.url : '#' ~ anchor }}"
target="{{ element.getCustomFieldText('target', '_self') }}"{% if isMegaMenu %} data-mega-menu="1"{% endif %}>
{% if icon %}<span class="icon icon-{{ icon }}"></span>{% endif %}
{# the link text #}
</a>
Image and file fields
getCustomFieldImage() returns the image and getCustomFieldFile() the file, or null, so test the result before using it. What a template can do with an image (resizing, converting, picture tags) is in Images end to end; the custom field only hands you the image. Its stored bytes can also be printed directly, which suits an SVG icon shown inline:
Template code
{$cv = $person->getCustomFieldFile('cv')}
{if $cv}<a href="{$cv->getPath()}">Download CV</a>{/if}
{* an SVG icon, printed inline from its stored content *}
{$svg = $element->getCustomFieldImage('iconImage')}
{if $svg}{$svg->getActiveRevision()->getVirtualFile()->getContent()}{/if}
{% set cv = person.getCustomFieldFile('cv') %}
{% if cv %}<a href="{{ cv.getPath() }}">Download CV</a>{% endif %}
{# an SVG icon, printed inline from its stored content #}
{% set svg = element.getCustomFieldImage('iconImage') %}
{% if svg %}{{ svg.activeRevision.virtualFile.content }}{% endif %}
There is no typed getter for a record or records field. Read the stored value with getCustomFieldValueByName(), test it, then take getContent()->getRecordModel() for a record field or getContent()->getRecordModels() for a records field. Here a category has a records field featuredPosts that links blog posts:
{% set featured = category.getCustomFieldValueByName('featuredPosts') %}
{% if featured %}
<ul>
{% for post in featured.getContent().getRecordModels() %}
<li><a href="{{ post.url }}">{{ post.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
The empty case
A field with no value never raises an error. getCustomFieldText() and getCustomFieldBoolean() return the field's default, then the default you pass, and null when neither exists. getCustomFieldImage(), getCustomFieldFile() and getCustomFieldValueByName() return null, which is why every example above tests the result before calling a method on it.
Make a misspelt field name visible
The getters look a field up by name and do not check that it exists, so getCustomFieldText('jobTitel') behaves exactly like a field with no value, in both engines. While developing, pass a default that stands out, such as 'NOT SET', so a typo shows on the page.
In an API response
An API read returns a record's custom fields under customFieldContent, an object keyed by field name. Each entry is described the way a layout area's content is: id, type (the input type's name), category and editor, then the value under the keys its category uses. A field with no stored value has no entry at all: customFieldContent only lists the fields a value has been saved for.
Category
Value keys
text
text, or active (true or false) for a switch editor
image
imageId, imagePath
file
fileId, filePath
record
record: {id, fullPath}
records
records: a list of {id, fullPath}
A person with a text field and a switch reads back like this (the ids are illustrative); a field with no stored value, such as an unset department field, has no entry at all:
Writing custom fields goes through the same API: a customFields object keyed by field name on the record's ordinary edit route, live at once because custom fields are not versioned. A site's own form that saves a custom field, such as a profile page, uses that route too. The requests, the value for each input type and the customFieldValues rows behind them are on Custom fields in the APIs guide.