The Drupal 8 dev team decided to include the REST module in core because of the growing movement towards decoupled architecture, which we often refer to as “Headless Drupal.” Along these lines, entities have built in REST support, and furthermore it is extremely simple to expose custom lists of entities as REST resources by using the Views module REST display type. Drupal 8 is thoroughly headless-ready.

Often though, you will have to tweak these resources a bit to get them ready for your client app(s). Today I’d like to discuss a very specific example of this: how to provide the raw file urls for specific image styles as part of a REST resource. Note: this tutorial will require some coding, although you should be able to just copy and paste the snippet into your module.

Let’s start with the use case.

I’ve got a Drupal 8 CMS backend feeding into an Express frontend app on another server. I’ve also got a /portfolio path that is supposed to show a list of portfolio items, which are composed of a thumbnail, a title and a body. Finally, there is a view providing a REST export at /api/portfolio.

Here’s the catch: I do not want any markup in my resource (except for the body field where the markup is considered content). The title field has no markup, but the image field does. Why do I care about whether there is markup? Mainly, because structural markup is something that the frontend is supposed to take care of. There’s a performance factor too: if we just used the regular image field formatter, we would have to invoke the theme layer in Drupal. We can avoid that.

My Approach 

Here is the approach I chose to take:

/**
* @file
* Contains \Drupal\dp_rest\Plugin\Field\FieldFormatter\ImageFormatterUri.
*/

namespace Drupal\dp_rest\Plugin\Field\FieldFormatter;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Cache\Cache;
use Drupal\image\Plugin\Field\FieldFormatter\ImageFormatterBase;
use Drupal\image\Entity\ImageStyle;

/**
* Plugin implementation of the 'image uri' formatter.
*
* @FieldFormatter(
*   id = "image_uri",
*   label = @Translation("Image URI"),
*   field_types = {
*     "image"
*   }
* )
*/
class ImageFormatterUri extends ImageFormatterBase {

 /**
  * {@inheritdoc}
  */
 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
   return new static(
     $plugin_id,
     $plugin_definition,
     $configuration['field_definition'],
     $configuration['settings'],
     $configuration['label'],
     $configuration['view_mode'],
     $configuration['third_party_settings'],
     $container->get('entity.manager')->getStorage('image_style')
   );
 }

 /**
  * {@inheritdoc}
  */
 public static function defaultSettings() {
   return array(
     'image_style' => '',
   ) + parent::defaultSettings();
 }

 /**
  * {@inheritdoc}
  */
 public function settingsForm(array $form, FormStateInterface $form_state) {
   $image_styles = image_style_options(FALSE);

   $element['image_style'] = [
     '#title' => t('Image style'),
     '#type' => 'select',
     '#default_value' => $this->getSetting('image_style'),
     '#empty_option' => t('None (original image)'),
     '#options' => $image_styles,
   ];

   return $element;
 }

 /**
  * {@inheritdoc}
  */
 public function settingsSummary() {
   $summary = array();

   $image_styles = image_style_options(FALSE);
   // Unset possible 'No defined styles' option.
   unset($image_styles['']);
   // Styles could be lost because of enabled/disabled modules that defines
   // their styles in code.
   $image_style_setting = $this->getSetting('image_style');
   if (isset($image_styles[$image_style_setting])) {
     $summary[] = t('Image style: @style', array('@style' => $image_styles[$image_style_setting]));
   }
   else {
     $summary[] = t('Original image');
   }

   return $summary;
 }

 /**
  * {@inheritdoc}
  */
 public function viewElements(FieldItemListInterface $items, $langcode) {
   $elements = array();
   $files = $this->getEntitiesToView($items, $langcode);

   // Early opt-out if the field is empty.
   if (empty($files)) {
     return $elements;
   }

   $style = ImageStyle::load($this->getSetting('image_style'));

   foreach ($files as $delta => $file) {
     $image_uri = $file->getFileUri();
     $elements[$delta] = array(
       '#markup' => $style->buildUrl($image_uri),
     );
   }

   return $elements;
 }

}

My module was called “dp_rest”. Substitute your own name of course, and then put this file inside yourmodule/src/Plugin/Field/FieldFormatter/ImageFormatterUri.php.

I chose to do this as a field formatter rather than a Views field plugin because it would allow me to use this in contexts outside of Views, and it also made it easy to make sure the field got the right data in Views (since we’re just piggybacking on the Views field provided for our image field by virtue of it being a field).

Here is an explanation of what the methods are in ImageFormatterUri:

create()

Mainly I just borrowed this from the ImageFormatter class, which ImageFormatterUri considers a sibling. This just mimics the fancy way that ImageFormatter establishes itself within the Factory pattern, which mainly allows you to invoke it statically. You can actually remove this bit and your resource will work fine.

defaultSettings()

This establishes the canonical set of settings for the formatter. Drupal 7 Field API had something analogous.

settingsForm()

This defines the settings form for the formatter. Here is where we let people choose which image style they want to use. #empty_option is a cool new feature in Drupal 8 Form API, and eliminates the need to arbitrarily pick a silly key for your “none” option.

settingsSummary()

This is just a summary of the options chosen in the settingsForm.

viewElements()

Here is the place where we actually render the output. I borrowed the basic structure from ImageFormatter::viewElements(). Here is the basic procedure:

  1. Get the entities being shown in this field. (Did you know that image fields are children of Entity Reference fields now?)
  2. Load the image style object.
  3. For every file entity found, do the following:
    1. Get the URI for the original image file.
    2. Use the image style object to give you the url for the selected image style version of the file.

Now, all you have to do is use this formatter in your REST view, and hit the correct endpoint (make sure you clear the cache if necessary). For extra neatness, you can customize the alias of the image field, and change it to something that indicates it is a thumbnail. This is under Format > Show/Fields in the Views UI.

Keeping markup out of your endpoints is important in terms of both performance and code usability. And if nothing else, you at least know how to create new field formatters now!

PS:

Here is a similar handler that will give you just the alt value for an image:

/**
* @file
* Contains \Drupal\dp_rest\Plugin\Field\FieldFormatter\ImageFormatterAlt.
*/

namespace Drupal\dp_rest\Plugin\Field\FieldFormatter;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Cache\Cache;
use Drupal\image\Plugin\Field\FieldFormatter\ImageFormatterBase;
use Drupal\image\Entity\ImageStyle;

/**
* Plugin implementation of the 'image alt' formatter.
*
* @FieldFormatter(
*   id = "image_alt",
*   label = @Translation("Image Alt only"),
*   field_types = {
*     "image"
*   }
* )
*/
class ImageFormatterAlt extends ImageFormatterBase {

 /**
  * {@inheritdoc}
  */
 public function viewElements(FieldItemListInterface $items, $langcode) {
   $elements = array();

   foreach ($items as $delta => $item) {
     $image = $item->getValue();
     $elements[$delta] = array(
       '#markup' => $image['alt'],
     );
   }

   return $elements;
 }

}

Additional Resources
Understanding Decoupled Drupal 8 | Video