Guide 03 · Developer docs

The theme repo and the asset pipeline

Everything a developer changes on a Core dna site lives in its theme repository. This guide covers the repository and its build, printing records in templates, stylesheets and client-side code, and the parts of a site around the main content.

L1–L2
Unit 02 · Records in templates

Custom fields in templates

Menus, products, users, categories and a dozen other record types carry custom fields an administrator defines. Which records have them, the getters by type, what comes back when nothing is stored, and why a misspelt field name is not an error in either engine.

Who this is for. The theme developer printing an author-defined value: a menu item's icon, a product's badge, a category's colour. Afterwards you can read any custom field, handle the empty case on purpose, and tell an unset field from a misspelt one. Whether a value should be a custom field at all is Custom fields in Guide 5.

Which records have them

A custom field is defined once, in the admin, for a whole record type. In templates these record types have them: books, book units and book pages; menus and menu elements; categories, tags, layouts and SEO settings; products and product categories; forms; invoices; addresses; authors, people and users.

Pages, blog posts and custom entity records do not. Their extra values are layout areas, read with the area getters: Area getters, by return type.

Custom field values belong to the record, not to a revision, so a change to one shows on the next uncached render with nothing to publish.

The getters

GetterReturns
getCustomFieldText(name, default)The stored value as text: any text field, a select's key, a date as written
getCustomFieldBoolean(name, default)true only when the stored value is '1', as a switch stores it
getCustomFieldImage(name), getCustomFieldFile(name)The image or file record, or nothing
getCustomFieldContent(name)The stored content object, or nothing
hasCustomField(name)Whether the record type defines a field of that name, whatever is stored

A real one: this documentation site's header menu gives each menu element a text field navKey, which the navigation template reads to highlight the current section.

Template code
{$menu = $_menu->get('Header Menu')}
{if $menu}
  {foreach $menu->getRootElements() as $element}
    {$key = $element->getCustomFieldText('navKey', '')}
    {$isNew = $element->getCustomFieldBoolean('isNew', false)}
    <a href="{$element->getUrl()}" data-nav="{$key|escape}">
      {$element->getName()|escape}{if $isNew} <span class="badge">New</span>{/if}
    </a>
  {/foreach}
{/if}
{% set menu = _menu.get('Header Menu') %}
{% if menu %}
  {% for element in menu.getRootElements() %}
    {% set key = element.getCustomFieldText('navKey', '') %}
    {% set isNew = element.getCustomFieldBoolean('isNew', false) %}
    <a href="{{ element.getUrl() }}" data-nav="{{ key|e('html_attr') }}">
      {{ element.getName()|e }}{% if isNew %} <span class="badge">New</span>{% endif %}
    </a>
  {% endfor %}
{% endif %}

What comes back when nothing is stored

getCustomFieldText() and getCustomFieldBoolean() resolve in this order:

  1. the stored value, when the record has one, even if it is an empty string;
  2. the field's default value, when the field has one;
  3. the default you pass as the second argument;
  4. nothing (null), when you passed none.

Pass a default whenever the template needs a value to work with, and test for the empty string as well as for nothing when a stored value can be blank.

A misspelt name is not an error

A name the record type does not define behaves exactly like a field with nothing stored: you get your default, or nothing, in both engines. A typo produces an empty badge and no message. When a value is unexpectedly empty, ask the record whether the field exists:

Template code
{if !$element->hasCustomField('navKey')}
  <!-- navKey is not defined for menu elements on this site -->
{/if}
{% if not element.hasCustomField('navKey') %}
  <!-- navKey is not defined for menu elements on this site -->
{% endif %}

Remove the check once you have your answer. Field names are case-sensitive, and they are site data: a field created on the development site does not exist on production until someone creates it there too.

Go deeper

Was this page helpful?

Your answer goes to the docs team.