I’m writing a module that has a custom Field (cruises) and Field Formatter (Cruise Calendar). I have the backend squared away and have assigned “Unlimited” related cruises to my node. My goal is to display all the details of the related cruises as well as a single Cruise Calendar on each page with a single formatter. I’ve got a twig template working for each individual item displaying the details of each cruise, but I’m stuck as to how to best create the wrapper/calendar element in a twig template. The wrapper needs to be passed a simple list of which dates should be highlighted on the calendar.

At first I thought I could do this by creating a nested Render Array in my viewElements funtion, but the more I dig I don’t think that’s true. Overwriting viewElements in FormatterBase really only effects the individual elements right? How best can I create a custom template wrapper for my field formatter?

Here’s my sample code, when I thought I could return a nested Render Array. But of course if I return $wrapper my child elements don’t appear.

class FareHarborCalendar extends FormatterBase {

   * {@inheritdoc}
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];
    $all_cruise_dates = [];
    foreach ($items as $delta => $fh_cruise) {
        $local_cruise = FareharborCruises::load($fh_cruise->target_id);
        $availabilities = FareharborAvailabilities::getByTypeDate($fh_cruise->target_id);
        foreach($availabilities as $departure){
          $date_parts = explode("T", $departure->start);
          $all_cruise_dates[] = $date_parts[0];
        $elements[$delta] = [
            '#theme' => 'fareharbor_cruise_details',
            '#cruise_id' => $fh_cruise->target_id,
            '#name' => $local_cruise->name->value,
            '#booking_notes' => $local_cruise->booking_notes->value,
            '#headline' => $local_cruise->headline->value,
            '#details' => $local_cruise,
            '#availabilities' => $availabilities,
    $elements['#attached']['library'][] = 'fareharbor/fareharbor';

    $wrapper = [
      '#theme' => 'fareharbor_calendar',
      '#name' => "some fixed name",
      '#all_dates' => $all_cruise_dates,
      'child_details' => $elements,
    return $elements;


My hook_theme in .module file

 * Implements hook_theme().
function fareharbor_theme() {
  $vars['cruise_id'] = NULL;
  $vars['name'] = NULL;
  $vars['booking_notes'] = NULL;
  $vars['headline'] = NULL;
  $vars['details'] = NULL;
  $vars['availabilities'] = NULL;

  $theme['fareharbor_cruise_details'] = [
    'template' => 'fareharbor_cruise_details',
    'variables' => $vars,

  $cal_vars['name'] = NULL;
  $cal_vars['child_details'] = $theme['fareharbor_cruise_details'];
  $theme['fareharbor_calendar'] = [
    'template' => 'fareharbor_calendar',
    'variables' => $cal_vars,

  return $theme;


and a Dump of available values in fareharbor_calendar.html.twig

^ array:14 [▼
  "name" => "some fixed name"
  "child_details" => array:2 [▼
    "template" => "fareharbor_cruise_details"
    "variables" => array:6 [▼
      "cruise_id" => null
      "name" => null
      "booking_notes" => null
      "headline" => null
      "details" => null
      "availabilities" => null
  "theme_hook_original" => "fareharbor_calendar"
  "attributes" => Drupal\Core\Template\Attribute {#4854 ▶}
  "title_attributes" => Drupal\Core\Template\Attribute {#4847 ▶}
  "content_attributes" => Drupal\Core\Template\Attribute {#4851 ▶}
  "title_prefix" => []
  "title_suffix" => []
  "db_is_active" => true
  "is_admin" => true
  "logged_in" => true
  "user" => Drupal\Core\Session\AccountProxy {#2996 ▶}
  "directory" => "themes/custom/flagship"
  "theme_hook_suggestions" => []

Any hints as to how to best go about this and have a better understanding of how it's supposed to work would be greatly appreciated.



1 Answer 1


viewElements() has to return a renderable array for $items, as an array of child elements keyed by consecutive numeric indexes starting from 0, according to the formatter interface.

If you want to provide a different field wrapper you would need to override view(), but I wouldn't recommend to replace standard field theming with your own. Instead call the parent view() method and you can add additional variables if the render array contains the field template.

Extend the field template for your field type:

 * Implements hook_theme().
function mymodule_theme() {
  return [
    'field__myfieldtype' => [
      'base hook' => 'field',

Then your field module can provide a default field--myfieldtype.html.twig in its /templates folder and preprocess it in

function mymodule_preprocess_field__myfieldtype(&$variables) {
  // preprocess $variables
  • Thank you! It hadn't occurred to me to do it this way and I've been pulling out my hair. Fix is in place and working.
    – summerg
    Commented Jul 15, 2022 at 14:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.