Hello,

This is the first time I am trying to get a simple custom module working with theme suggestions. The theme suggestion is showing in the twig debug output, but the actual twig file is not being picked up (used). I have looked at a few articles and the various API docs, but I am not able to figure out what I am missing.

My module name is: "programs".

In the base twig file (programs.html.twig) it simply outputs five letters: A, B, C, D, E.

I want to add another theme suggestion (programs--listing.html.twig) in which it will only output the first letter, A.

This is my controller file (ProgramsController.php):

class ProgramsController extends ControllerBase {
    public function content() {
        $data['#theme'][] = 'programs';
        $data['#data'] = ['A', 'B', 'C', 'D', 'E'];

        return $data;
    }
}

This is my module file (programs.module):

/**
 * Implements hook_theme().
 */
function programs_theme($existing, $type, $theme, $path) {
    $variables = array(
        'programs' => array(
            'variables' => array(
                'data' => 'No data.',
            )
        ),
    );
    
    return $variables;
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function programs_theme_suggestions_programs_alter(&$suggestions, $variables, $hook) {
    $suggestions[] = $hook . '__' . 'listing';
}

In the twig debug output I can see the programs--listing.html.twig suggestion, but the actual programs--listing.html.twig file is not being picked up (used). Only the base (programs.html.twig) file is used.

I can't figure out what I am missing to get this working. Thank you for any suggestions!

Comments

Jaypan’s picture

If you're seeing it in twig debug, it means you've set the suggestion correctly, but something is wrong with your template file. It either cannot be found (incorrect location, or incorrect spelling in the file name), is not being picked up because you haven't cleared your registry, or cannot be read due to file permissions.

wombatbuddy’s picture

Updated

I guess, as you create the suggestions for your own module then it's more reasonable to use hook_theme_suggestions_HOOK().
Your code will be work, if you place 'programs--listing.html.twig' into 'YOUR_THEME/templates' folder.

elpea’s picture

Thanks guys, but actually none of them is working. Of course I clear my caches. Every time I make a change the cache is cleared.

I put a copy of "programs--listing.html.twig" file to two different places. One is in the module's template folder, and the other is in the theme's template folder - just in case, since I don't know which one is going to be called, so this way at least I have it in both places.

@wombatbuddy When I change the theme suggestion to hook_theme_suggestions_HOOK(), then even the name disappears from the twig debug listing. So that doesn't work.

I think the problem lies somewhere that any documentation or article I've seen so far, they all show suggestions for nodes, pages, or entities. But here I don't have any of those. I just have a simple module.

wombatbuddy’s picture

Updated

1. 

One is in the module's template folder, and the other is in the theme's template folder

Ensure that your folder name is 'templates' but not 'template'.

2.  If you decide to implement the hook_theme_suggestions_HOOK(), then of cause you need to change your code, for instance like this: 

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function my_module_theme_suggestions_programs($variables) {
  $suggestions[] = 'programs__listing';
  return $suggestions;
}
elpea’s picture

Thanks @wombatbuddy! I was able to get it working with the hook_theme_suggestion_HOOK() function as well. However, it makes no difference whether I use hook_theme_suggestion_HOOK or hook_theme_suggestion_HOOK_alter. The suggestion will show up in twig in both cases, however the twig template will NOT be used unless I add the two array keys for BOTH suggestions. So it seems like that is the critical piece.

PS: yes, my templates folder name was/is "templates". Still it doesn't work unless I add the two array keys for both suggestions in the modulename_theme() function.

wombatbuddy’s picture

Still it doesn't work unless I add the two array keys for both suggestions in the modulename_theme() function.

I don't do this and everything works well. 

Jaypan’s picture

Yes, that step should not be necessary. It's good it worked, but there must be something on your system causing non-standard behavior.

wombatbuddy’s picture

I was wrong, when I say that it will be work in 'YOUR_MODULE/templates' folder.
It works only if it placed in 'YOUR_THEME/templates' folder.
I updated my previous answers.

elpea’s picture

I think this is where I have to bang my head against the wall. I realized the reason it didn't work because I was using the original Bartik theme. Even though my theme inherits the Bartik theme, it was not enabled. The core Bartik theme was enabled. Since they look the same, I looked everywhere but the Appearance settings, and so was not aware that my theme was not the active one. Sheesh.... so much time wasted!!!

Anyhow, thanks both for the help!

Jaypan’s picture

Heh, we've all done something like that.

Hundreds of times.

Well found :)

elpea’s picture

I think I got it working, although I am not sure if this is the best way to do it, but I got the "idea" from this article:

URL: https://www.drupal.org/docs/8/theming/twig/create-custom-twig-templates-for-custom-module

I my module_name.module file, it seems like I have to add an array with a key for each name suggestions that I want to have. In this case, originally I only had the array with the key "programs" added to $variables, but now I also had to add another array with the key "programs__listing".

/**
 * Implements hook_theme().
 */
function programs_theme($existing, $type, $theme, $path) {
  $variables = array(
    'programs' => [
      'variables' => [
        'data' => null
      ]
    ],
    // I had to add this array with the name I want to suggest
    'programs__listing' => [
      'variables' => [
        'data' => null
      ]
    ],
  );
	
  return $variables;
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function programs_theme_suggestions_programs_alter(&$suggestions, $variables, $hook) {
  $suggestions[] = $hook . '__' . 'listing';
}
jayemel’s picture

I'm not entirely sure what the use case is for hook_theme_suggestions_HOOK() in a custom module. In my case it doesn't seem to do anything. I still have to tell Drupal about the templates in my_module/templates via hook_theme, or it simply won't pick them up. 

In my custom module I have:

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function my_module_theme_suggestions_my_entity(array $variables) {
  $suggestions = [];
  $entity = $variables['elements']['#my_entity'];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');

  $suggestions[] = 'my_entity__' . $sanitized_view_mode;
  $suggestions[] = 'my_entity__' . $entity->bundle();
  $suggestions[] = 'my_entity__' . $entity->bundle() . '__' . $sanitized_view_mode;
  $suggestions[] = 'my_entity__' . $entity->id();
  $suggestions[] = 'my_entity__' . $entity->id() . '__' . $sanitized_view_mode;
  return $suggestions;
}

But the above does nothing. I have to explicitly tell Drupal about my template files individually in my hook_theme.

function my_module_theme() {
  return [
    'my_entity__my_bundle' => [
      'template' => 'my-entity--my-bundle',
      'base hook' => 'node'
    ],
    'my_entity__my_other_bundle' => [
      'template' => 'my-entity--my-other-bundle',
      'base hook' => 'node'
    ],
  ];
}
mrarkantos’s picture

I have the same issue. I was working on this for hours, and really I don't know why the hook_theme_suggestions_HOOK() doensn't work. I'm trying the template override in a custom module, and yes, I followed the solutions and comments propposed here and in the official documentation, but still with not happy end.

Jaypan’s picture

That's frustrating I'm sure. But we can't give assistance without seeing the code that isn't working.

mrarkantos’s picture

Hi, thanks for reply my post. Here is the full issue description:

  1. The module was generated with drupal generate:module (The module was named "books")
  2. A custom entity was generated with drupal generate:entity:content (The entity was named "book")
  3. So, after generate the module and entity, a warning was displayed in the console about a conflict on the hook_theme that you must fix manually, the module generator and the entity generator insert the hook_theme two times in the module, but it was pretty simple to fix just merging the content or avoiding the hook generated by module, at the end, the hook theme looks like bellow:
/**
 * Implements hook_theme().
 */
function books_theme() {
  $theme = [];

  $theme['book_entity'] = [
    'render element' => 'elements',
    'file' => 'book_entity.page.inc',
  ];

  $theme['book_entity_content_add_list'] = [
    'render element' => 'content',
    'file' => 'books.page.inc',
    'variables' => ['content' => NULL],
  ];

  return $theme;
}

The entity generator also creates the hook_theme_suggestions_HOOK that looks like bellow:

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function books_theme_suggestions_book_entity(array $variables) {
  $suggestions = [];
  $entity = $variables['elements']['#book_entity'];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');

  // Here is a custom code to generate suggestions based on a field
  if ($entity->hasField('field_template')) {
    $template = $entity->field_template->getValue();

    switch (intval($template[0]['value'])) {
      case 0:
        $tpl = 'normal';
        break;
    }

    $suggestions[] = 'book-entity__' . $entity->bundle() . '__' . $tpl;
  }

  $suggestions[] = 'book-entity__' . $sanitized_view_mode;
  $suggestions[] = 'book-entity__' . $entity->bundle();
  $suggestions[] = 'book-entity__' . $entity->bundle() . '__' . $sanitized_view_mode;
  $suggestions[] = 'book-entity__' . $entity->id();
  $suggestions[] = 'book-entity__' . $entity->id() . '__' . $sanitized_view_mode;
  return $suggestions;
}

So, in the "modules/custom/books/templates/" path there are 3 twig files:

  • book-entity--test--normal.html.twig

  • book-entity-content-add-list.html.twig

  • book-entity.html.twig

After inspect the source code with the developer tools and the twig debug active in a content entity previously created, we have:

<!-- THEME DEBUG -->
<!-- THEME HOOK: 'book_entity' -->
<!-- FILE NAME SUGGESTIONS:
   * book-entity--1--full.html.twig
   * book-entity--1.html.twig
   * book-entity--test--full.html.twig
   * book-entity--test.html.twig
   * book-entity--full.html.twig
   * book-entity--test--normal.html.twig
   x book-entity.html.twig
-->
<!-- BEGIN OUTPUT from 'modules/custom/books/templates/book-entity.html.twig' -->

So, at this point, we have the right code, the right files, but the book-entity--test--normal.html.twig wasn't picked up as expected, by default, always the book-entity.html.twig is rendered.

wombatbuddy’s picture

1. Change priority of you suggestions.
2. Also, if the 'field_template' will not have a value you will give a error message. You should add the additional check.
The example: 

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function books_theme_suggestions_book_entity(array $variables) {
  $suggestions = [];
  $entity = $variables['elements']['#book_entity'];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');

  $suggestions[] = 'book_entity__' . $sanitized_view_mode;
  $suggestions[] = 'book_entity__' . $entity->bundle();
  $suggestions[] = 'book_entity__' . $entity->bundle() . '__' . $sanitized_view_mode;
  $suggestions[] = 'book_entity__' . $entity->id();
  $suggestions[] = 'book_entity__' . $entity->id() . '__' . $sanitized_view_mode;
  
  // Here is a custom code to generate suggestions based on a field
  if ($entity->hasField('field_template') && !$entity->get('field_template')->isEmpty()) {
    $template = $entity->field_template->getValue();

    switch (intval($template[0]['value'])) {
      case 0:
        $tpl = 'normal';
        break;
    }

    $suggestions[] = 'book_entity__' . $entity->bundle() . '__' . $tpl;
  }
  
  return $suggestions;
}
mrarkantos’s picture

Hi there, I tried the proposed solution but still not working, It only changes the template suggestions order.

<!-- THEME DEBUG -->
<!-- THEME HOOK: 'book_entity' -->
<!-- FILE NAME SUGGESTIONS:
   * book-entity--test--normal.html.twig
   * book-entity--1--full.html.twig
   * book-entity--1.html.twig
   * book-entity--test--full.html.twig
   * book-entity--test.html.twig
   * book-entity--full.html.twig
   x book-entity.html.twig
-->
<!-- BEGIN OUTPUT from 'modules/custom/books/templates/book-entity.html.twig' -->

I also tried to override with another twig file, but still rendering the last one.

wombatbuddy’s picture

Try to move the templates into the 'YOUR_THEME/templates' folder.

mrarkantos’s picture

Hi there, yes, I also tried that scope before, but it doesn't work.

wombatbuddy’s picture

1. Try to replace hyphens with an underscores.
Replace this: 

$suggestions[] = 'book-entity__' . $sanitized_view_mode;

with this: 

$suggestions[] = 'book_entity__' . $sanitized_view_mode;

2. Move templates to the ''YOUR_THEME/templates' folder.

3. Rebuild the cashes.

mrarkantos’s picture

My friend, thank you so much for your time and patience about this issue, finally it works as expected. The hyphens were the problem all the time. The thing is, when you generate the entity from the console, and name the entity like "hello world" the space is replaced with a hyphen. I read about Twig Template naming conventions, but there isn't any reference to this important issue. Learned for the future. Thanks again.

hermann77’s picture

By the way I wonder if it's possible to hold the template files suggested by Drupal NOT in 'YOUR_THEME/templates' but in 'YOUR_MODULE/templates'?

If so, is it maybe bad practice? Or it's OK?

How declare the place of my template files suggested by Drupal?

I tried to do so in hook_theme() but it doesn't work. If the theme debugger shows a file name suggestion

node--MY_NODE_TYPE--teaser

I try

function MY_MODULE_theme() {
  return 
      'node_mynodetype_teaser' => [
        'template' => 'node--MY_NODE_TYPE--teaser',
        'base hook' => 'node',
        'variables' => [
          'content' => null,
        ],
      ]
    ],
  ];
}

but it has no effect. 

I suppose, the only solution is to move node--MY_NODE_TYPE--teaser.html.twig

to 'YOUR_THEME/templates'. Is not that so?

Thanks.