Templating custom post types in Drupal 8

The problem

In Drupal 8, it’s pretty easy to drop fields into a template for a custom post type template. But the fields come in as their entire mark-up which isn’t great, especially when I’m working with a CSS framework like Bulma.io or Bootstrap 4, and want to change the mark-up and add classes.

Even the labels are coming in as just divs when that’s not semantically helpful.

I don’t think this is possible in the Drupal 8 UI without Views.

EDIT: The Drupal module Display Suite can accomplish almost anything in this tutorial without templating. It’s really really cool. It’s a big module so I’ll publish a different tutorial on that later. This tutorial is still really useful for learning to write Drupal 8 templates, so read on anyway.

The question

How can I pull out the values and labels from a field separately? And how can I do it in the template?

Contents

  1. Creating a template for a custom post type
  2. Get just the raw field value
  3. Get just the raw field label
  4. Show multiple field values as a list

Creating a template for a custom post type

Twig suggests node--posttype.html.twig as a template to override node.html.twig for my custom post type.

How do I know? Twig debugging: https://www.drupal.org/docs/8/theming/twig/debugging-twig-templates

Templates go in mytheme/templates.

Get just the raw field value

I have a field for my People profile type called email whose machine name is field_email. (I can find this in Structure > Content Types > People > Manage Fields.)

In the documentation in node.html.twig it provides some hints on how to get fields.

- content: All node items. Use {{ content }} to print them all,
* or print a subset such as {{ content.field_example }}. Use
* {{ content|without(‘field_example’) }} to temporarily suppress the printing
* of a given child element.

Cool, so I can just do {{ content.field_email }} and I should get the email as a url. Sorry, no. I get:

<div data-quickedit-field-id="somejunk">email@gmail.com</div>

But I want:

<div class="people_email">
<span class="fa fa-envelope-o'></span>
<span>
<a href="mailto:email@gmail.com">email@gmail.com</a>
</span>
</div>

After some digging, I found this solution:

{{ node.field_email.0 }} will just produce the raw email. Woohoo!

So here is that solution all together:

<div class="people_email">
<span class="fa fa-envelope-o'></span>
<span>
<a href="mailto:{{ node.field_email.0 }}">{{ node.field_email.0 }}</a>
</span>
</div>

Get just the raw field label

I have another field called Research (field_research). It’s a textarea, and I want to display it as an <h2> label with the content in a <p>.

If I use Content Type > People > Manage Display I can show the label, but {{ content.field_research }} produces:

<div>Research</div>
<div><p>Blah blah blah.</p></div>

So that’s not fun. But here’s what I did: {{ node.field_research.fieldDefinition.label }} to get the label, and {{ node.field_research.0 }} to get the value.

Then I can use this code to get what I want:

<h2>{{ node.field_research.fieldDefinition.label }}</h2>
<div><p>{{ node.field_research.0 }}</p></div>

Show field values as a list

I have a field for Education. It allows multiple text field values, each line a type of academic degree. I want them to appear in a <ul>, but {{ content.field_education}} produces:

<div>Education</div>
<div>
<div>B.A., History, Iowa State University, 1978</div>
<div>M.A., Archaeology, University of Cambridge, 1982</div>
<div>Ph.D., Archaeology, University of Cambridge, 1985</div>
</div>

Ew. When I think about it, what I want to do is loop through each value in the field and wrap them in an <li>. Here’s what to do:

<h2>{{ node.field_education.fieldDefinition.label }}</h2>
<ul>
{% for key, item in content.field_education if key|first != '#' %}
<li>{{ item }}</li>
{% endfor %}
</ul>

This produces exactly what I want.

<h2 class="h4">Education</h2>
<ul class="list-unstyled">
<li>B.A., History, Iowa State University, 1978</li>
<li>M.A., Archaeology, University of Cambridge, 1982</li>
<li>Ph.D., Archaeology, University of Cambridge, 1985</li>
</ul>

Conclusion

Sorting this out opened theming wide open. It was suddenly super easy to make the dynamic, semantic, lovely layouts I’d designed, and made it way easier to use a framework or style guide.

I feel like it’s far easier to implement and maintain to do this in a template rather than with preprocess functions.

Hope this helps.