Is there a way to render HTML5 captions in Drupal 8? I've tried with embedding images and embedding media in the WYSIWYG, and I've also tried with media migrated from D7 to D8, but nothing works.

An image with a source like this in the WYSIWYG:

<img alt="mermaid" data-caption="actual photo of a mermaid" data-entity-type="file" data-entity-uuid="5b148b62-6890-4b90-98e6-63a2cad3fe70" src="/sites/default/files/inline-images/mermaid.gif" />

renders on the page like this:

  <img alt="mermaid" data-entity-type="file" data-entity-uuid="5b148b62-6890-4b90-98e6-63a2cad3fe70" src="/sites/default/files/inline-images/mermaid.gif" />
  actual photo of a mermaid

and a media entity with a source like this on the WYSIWYG:

<drupal-entity data-caption="this is the caption" data-embed-button="media" data-entity-embed-display="view_mode:media.large_480x480" data-entity-type="media" data-entity-uuid="7012b662-1430-4610-8fa3-65eede89f7ba"></drupal-entity>

renders on the page like this:

<div class="contextual-region row bs-1col media media--type-image media--view-mode-large-480x480">
  <div class="col-sm-12 bs-region bs-region--main">
      <a href="example.com/sites/default/files/field/image/groundhog_480x480.jpg">
        <img src="/sites/default/files/styles/large/public/field/image/groundhog_480x480.jpg?itok=1DapCxP0" alt="Photo of a groundhog" /> 
this is the caption

with random <div> tags that hold nothing and the caption pulled entirely out of the relevant tag so its parent is actually the body field. Neither of these use HTML5 with a wrapping <figure> tag or even a <figcaption> or <caption> tag.

I found a suggested hack that uses the image's title tag as a caption, and the sandboxed Image Caption module uses jQuery to produce similar results, but my site is migrating hundreds of articles where the content team has used captions for image credits and we're legally required to display those, so we can't pretend the image title is the caption. Also, the way captions are currently being rendered doesn't allow us to style the them in a way that ties them to the image or separates them from the surrounding paragraphs, so they just blend in with the rest of the text.

I tried to dump the image's variables to the page, but there is no caption:

array:8 [
  "#theme" => "image_style"
  "#style_name" => "large"
  "#attributes" => []
  "#title" =>null
  "#uri" => "public://field/image/groundhog_480x480.jpg"
  "#width" => "480"
  "#height" => "480"
  "#alt" => "Photo of a groundhog"

so somewhere in the rendering process the caption is being removed from the image and rendered separately.

3 Answers 3


Did you double-check that your Text Format (under /admin/config/content/formats) was set to allow the HTML5 tags for this? (See below) If you haven't then the filtering will remove those tags and replace them with <div> tags.


I am not sure if you are using the core Media module and Entity Embed / Media Embed for CKEditor, if you are, the way I do it is add the caption field to the media type (Image). I don't use any of the other core stuff (Image2 plugin?), as I prefer the Media/entity way.

In the theme (or module), I add a hook_preprocess_field:

  if ($variables['field_name'] == 'field_mymedia_image_field') {
    foreach ($variables['items'] as $key => $item) {
      $variables['items'][$key]['content']['#options']['attributes']['class'][] = 'custom_class_foo';
      $variables['items'][$key]['content']['#options']['attributes']['class'][] = 'custom_class_bar';
      $variables['items'][$key]['content']['#options']['attributes']['data-caption'] = 'Field value from Caption field';

^ This will add attributes to the image tag when the image field is rendered. I added two css classes as an example, and a data-caption attribute. You can't do this if you just have raw image tags in the WYSIWYG source. In that case you'd have to add the attributes manually in the source.

Note that your $variables structure may be slightly different than mine depending on your setup.

For the figure/figcaption part, using Media grants you twig templates (and you can add more suggestions as needed), but I also solve it there too (example, media--image.html.twig):

{% if content %}
    <figure{{ attributes.addClass(classes) }}>
      {{ content.image }}

      {% if media.field_credit is not empty or media.field_caption is not empty %}
        <figcaption class="hl__figure__figcaption">
          {% if media.field_caption is not empty %}
            <div class="hl__figure__text-caption">
              {{ content.field_caption }}
              {% if media.field_caption_link is not empty %}
                {{ content.field_caption_link }}
              {% endif %}
          {% endif %}

          {% if media.field_credit is not empty %}
            <div class="hl__figure__text-credit">
              {% include "@mytheme/svg/media/caption_icon.html.twig" %}
              {{ content.field_credit }}
          {% endif %}
      {% endif %}
  {% endif %}

I think there is some stripping going on during the processing of your data - which is why I think Media is the better route (entity render pipeline).

If you could adjust your Media migration to read attributes into field data, I think this is a good shot. It's been a while since I have done that, and can't find an example offhand.


This looks a lot like something you can control in the theme layer.

Turn on twig debug in your service.yml (sites/default/…) and it will tell you the template being used to generate that output, and the paterns needed to override it.

  # Twig debugging:
  # When debugging is enabled:
  # - The markup of each Twig template is surrounded by HTML comments that
  #   contain theming information, such as template file name suggestions.
  # - Note that this debugging markup will cause automated tests that directly
  #   check rendered HTML to fail. When running automated tests, 'debug'
  #   should be set to FALSE.
  # - The dump() function can be used in Twig templates to output information
  #   about template variables.
  # - Twig templates are automatically recompiled whenever the source code
  #   changes (see auto_reload below).
  # For more information about debugging Twig templates, see
  # https://www.drupal.org/node/1906392.
  # Not recommended in production environments
  # @default false
  debug: true

Example output:

 <!-- THEME DEBUG -->
 <!-- THEME HOOK: 'image' -->
 <!-- BEGIN OUTPUT from 'core/themes/classy/templates/field/image.html.twig' -->
 <img src="…">
 <!-- END OUTPUT from 'core/themes/classy/templates/field/image.html.twig' -->
  • I dumped the variables in the image template and there was no caption Jun 5, 2018 at 15:07

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.