Blog CMS Workflows 7 min read

Drupal Media Entity Placeholder Images for Empty Fields

Handle empty Drupal media entity fields in Twig templates and decoupled frontends with generated placeholder URLs sized to match your image styles.

Drupal placeholder imageDrupal media entityTwig template fallbackHeadless DrupalCMS Workflows
Drupal Media Entity Placeholder Images for Empty Fields

Drupal media entities decouple files from field instances, which means an image field can reference a media entity that does not exist yet. Empty media fields return null at the Twig layer and as null in JSON:API and GraphQL responses. Twig templates that call `file_url(content.field_image.entity.field_media_image.entity.uri.value)` without checking for entity existence throw a fatal error.

The defensive pattern is to check for the media entity at each step of the chain before resolving the file URL. When any step is null, fall back to a generated placeholder URL. fallback.pics returns cacheable SVG images from URL parameters, so you do not need to upload placeholder assets to Drupal's file system.

Media entity chain

Why Drupal image fields throw on null

A typical Drupal image field is a media reference field. The chain is: content type field → media entity → file entity → URI → public URL. Any step in this chain can be null if the media entity was not attached, if the media entity was deleted, or if the file was removed from the managed file table.

Drupal 10 uses the Media module by default. When a media reference field is optional and left empty, `node.field_image.entity` is null. Twig's null-propagation (the `?.` operator is not native HubL, but Twig has its own approach) requires explicit checks before property access.

For decoupled Drupal frontends using JSON:API, the field value comes back as null inside the `relationships` key. Frontends receive `{ field_image: { data: null } }` and must handle it without crashing the render.

Twig template

Null-safe image rendering in Twig

Use Twig's `is not empty` check or the `?:` null-coalescing approach before rendering the image URL. For a media reference field, check the entity existence before navigating to the file entity.

Set explicit `width` and `height` attributes on the rendered `<img>` tag. These attributes must match the Drupal image style dimensions to prevent layout shift when the real image loads.

Implementation text
{# node--article.html.twig #}
{% set img_src = null %}
{% if content.field_image[0] is not empty %}
  {% set img_src = content.field_image[0]['#media'].field_media_image.entity.uri.value | file_url %}
{% endif %}

{% if img_src %}
  <img src="{{ img_src }}" alt="{{ content.field_image[0]['#media'].field_media_image.alt }}" width="800" height="450" />
{% else %}
  <img
    src="https://fallback.pics/api/v1/800x450?text=Image+Missing"
    alt="{{ node.title.value }}"
    width="800"
    height="450"
  />
{% endif %}

Image styles

Match fallback dimensions to Drupal image styles

Drupal image styles define specific output dimensions. A `16_9_medium` style might output 800×450. Your fallback URL should use the same dimensions as the image style you target for the context. If the image style scales proportionally, use the largest expected output dimensions.

When multiple image styles are used for different breakpoints, use different fallback URLs per breakpoint in your `<picture>` or `srcset` setup. Dimension-matched fallbacks ensure no reflow when an image loads or when the fallback is shown permanently.

Implementation text
{# Using multiple image styles with srcset and matching fallbacks #}
{% if img_src %}
  <picture>
    <source srcset="{{ img_src | image_style('16_9_large') }}" media="(min-width: 1024px)" />
    <source srcset="{{ img_src | image_style('16_9_medium') }}" media="(min-width: 640px)" />
    <img src="{{ img_src | image_style('16_9_small') }}" width="400" height="225" alt="{{ alt }}" />
  </picture>
{% else %}
  <picture>
    <source srcset="https://fallback.pics/api/v1/1200x675?text=No+Image" media="(min-width: 1024px)" />
    <source srcset="https://fallback.pics/api/v1/800x450?text=No+Image" media="(min-width: 640px)" />
    <img src="https://fallback.pics/api/v1/400x225?text=No+Image" width="400" height="225" alt="{{ node.title.value }}" />
  </picture>
{% endif %}

JSON:API

Decoupled Drupal: handle null in JSON:API responses

A decoupled frontend using Drupal's JSON:API receives media reference fields inside the `relationships` key. When the field is empty, the value is `{ data: null }`. To get the file URL, you need to resolve the included resources — the media entity and then the file entity.

Write a resolver function that accepts the full JSON:API response and the node data, walks the included resources to find the file entity, and returns the real URL or a fallback URL. Keep the resolver deterministic so the same null input always produces the same fallback URL.

Implementation tsx
function resolveMediaUrl(
  node: JsonApiNode,
  included: JsonApiResource[],
  width: number,
  height: number,
): string {
  const mediaRef = node.relationships?.field_image?.data;
  if (!mediaRef) {
    return `https://fallback.pics/api/v1/${width}x${height}?text=No+Image`;
  }

  const mediaEntity = included.find(
    (r) => r.type === mediaRef.type && r.id === mediaRef.id,
  );
  const fileRef = mediaEntity?.relationships?.field_media_image?.data;
  const fileEntity = included.find(
    (r) => r.type === fileRef?.type && r.id === fileRef?.id,
  );
  const url = fileEntity?.attributes?.uri?.url;

  return url
    ? `${process.env.DRUPAL_BASE_URL}${url}`
    : `https://fallback.pics/api/v1/${width}x${height}?text=No+Image`;
}

Default image

Use the Drupal field default image vs URL fallback

Drupal's image field has a built-in default image setting. You can upload an image to the media library and configure the field to use it when no media is attached. This is the simplest approach for Twig-only setups. The tradeoff: you store an asset in Drupal and need to manage it per-environment. A URL-based fallback requires no storage.

For decoupled setups, the Drupal default image appears in the JSON:API response as a real file entity, so your resolver handles it automatically. URL-based fallbacks are more useful in headless contexts where the frontend team controls the placeholder design independently of the CMS.

Resources

Further reading

For more on placeholder image patterns across CMS platforms, see the general CMS placeholder guide and the Contentful and Strapi guides in this series.

Implementation text
https://fallback.pics/docs/
https://fallback.pics/placeholder-image-api/
https://fallback.pics/blog/strapi-media-fallback/
https://fallback.pics/blog/placeholder-images-cms-previews-missing-media/

Key takeaways

What to standardize before shipping

  • Drupal media entity chains can fail at multiple points — check each step before accessing the final file URL.
  • Match fallback.pics dimensions to your Drupal image style output dimensions to prevent layout shift.
  • In decoupled setups, walk the JSON:API included resources to resolve media-to-file references before deciding on a fallback.
  • Drupal's built-in default image works for Twig setups; URL-based fallbacks are better for headless frontends where the frontend controls placeholder design.
  • Use a `<picture>` element with separate fallback URLs per breakpoint when multiple image styles are active.

Production fallback layer

Use fallback.pics anywhere an image URL is accepted.

Start with one deterministic URL and standardize fallback behavior across your design system.

Read the docs