How to generate an image derivative for an image style by visiting a URL

Recently I was adding a photo gallery page to a Drupal 9 site. The markup for each photo looked something like this1:

<a href="/sites/default/files/styles/max_1280x1280/public/2021-01/butterfly.jpg?itok=EUoBeFr6">
  <img src="/sites/default/files/styles/photo_thumbnail/public/2021-01/butterfly.jpg?itok=PhOG4ZTh">

When a photo was clicked/tapped, the link was supposed to take you to a larger version of the image. Instead, to my surprise, it gave a “page not found” error.

How do image style URLs work?

First I spent some time trying to understand image style URLs better.

When I upload an image to this Drupal site, the original image is placed in a folder in the document root — e.g. sites/default/files/2021-01/. (Depending on how your site’s file system is configured, uploaded images could be placed in a different location or on a different server.)

The first part of the URL — e.g. /sites/default/files/styles/photo_thumbnail/public/2021-01/butterfly.jpg — refers to an image that Drupal generates automatically from the original image (by scaling, cropping, etc.). This image is called an image derivative.

The last part of the URL — e.g. ?itok=PhOG4ZThavoids a denial of service vulnerability.

When are the image derivatives generated?

The answer in the Drupal documentation is: “the first time a particular image is requested in that style”.

Indeed, the first time I visited my photo gallery page, all of the image derivatives referenced from <img> tags were created in my sites/default/files/styles/photo_thumbnail/public/2021-01 folder. Thus all of the images appeared on the page.

But when I clicked on an image to follow the link, the image derivative referenced from the <a> tag did not get created.

I had incorrectly assumed that clicking the link would count as the image being “requested in that style”. On further testing, I learned that if the request is coming from an <a> tag, the URL has to be constructed in a particular format in order to prompt the image derivative to be generated.

What is the format of a URL to generate an image derivative?

At first, I hadn’t realized that I could request the image derivative via a URL. Thinking it would require custom code, I started working on that and came across Drupal core’s ImageStyleDownloadController class. From there, I discovered that the custom functionality I’d started implementing already existed in Drupal core.

core/modules/image/image.routing.yml defines the following route2:

  path: '/system/files/styles/{image_style}/{scheme}'
    _controller:  '\Drupal\image\Controller\ImageStyleDownloadController::deliver'
    _access: 'TRUE'

When a request is made to this route, ImageStyleDownloadController::deliver is called. In the first lines, this function constructs a URI for the image — e.g. public://2021-01/butterfly.jpg — by grabbing the first part from the scheme route parameter and the second part from the request’s file query parameter.

public function deliver(Request $request, $scheme, ImageStyleInterface $image_style) {
  $target = $request->query->get('file');
  $image_uri = $scheme . '://' . $target;

A little later, the function looks up the expected image token using the image_style route parameter, then compares it to the itok query parameter. (If they don’t match, it throws an exception.)

  $valid &= hash_equals($image_style->getPathToken($image_uri),
    $request->query->get(IMAGE_DERIVATIVE_TOKEN, ''));

The function uses the image_style route parameter again to build a URI for the image derivative.

  $derivative_uri = $image_style->buildUri($image_uri);

Then, basically, the function generates the image derivative if it doesn’t already exist.

Based on that code, here are the pieces of the URL we need to build:

  • Start with /system/files/styles (from the route path).
  • Add the machine name for the image style as a path component (first routing parameter).
  • Add the scheme, either public or private, as a path component (second routing parameter).
  • Add the URL-encoded relative path to the original image file as a query parameter called file.
  • Add the image token as a query parameter called itok.

So instead of this:

<a href="/sites/default/files/styles/max_1280x1280/public/2021-01/butterfly.jpg?itok=EUoBeFr6">

We have this:

<a href="/system/files/styles/max_1280x1280/public?file=2021-01%2Fbutterfly.jpg&itok=EUoBeFr6">

Clicking on the link now takes you to the image derivative in the max_1280x1280 image style, first creating the image file if it doesn’t already exist.

Jaymie Strecker is a software developer at Kosada.

  1. This markup is, of course, simplified. The actual markup was generated by a View. The reason the View needed to link to the photo was to integrate with Swipebox↩︎

  2. Despite the name, the route also handles public image files. ↩︎