Media and the Media Library

This content was generated with the assistance of AI and should be verified against the official Drupal documentation before being relied on in production.

This sub-section’s Drupal bibliography lists the reference material consulted while preparing these pages.

Drupal has two core modules that turn uploaded files and remote resources into reusable, first-class content: Media (the media entity type, media types and source plugins) and Media Library (a modal browser and a reference-field widget on top of it). This page was checked against the official documentation on 2026-10-30 and written for Drupal 11.4.x with PHP 8.3 or later. The Media modules are part of core in Drupal 10 and Drupal 11, and nothing in them is among the modules that leave core in Drupal 12, so the content below applies unchanged when you upgrade; the Drupal 10 line reaches its end of life on 2026-12-09, so new sites should not start on it.

The running example is the Meridian Museum: exhibitions have a hero image, curators attach PDF catalogues and audio guides, and the events team embeds videos from a video host. All of that is media. The related pages are Content types and fields, Field types and entity references, Display modes and image styles, Breakpoints and responsive images, Text formats and CKEditor 5 and File system and private files.

Why media entities exist

A plain image or file field stores one file on one entity. The file is anonymous: nothing records its title, its alt text is welded to the field value, and the next node that wants the same photograph must upload it again. A media entity wraps the resource in an entity of its own. The consequences are:

  • it is reusable: any number of nodes, blocks or text fields point at one media item, and fixing a typo in its alt text or replacing its file fixes every place that uses it

  • it has its own fields, such as a credit line, a caption, a copyright holder or taxonomy terms, which a bare file cannot have

  • it has its own view modes, form modes, revisions, translations, owner, published status and permissions

  • it is managed in one place, the Media administration page at /admin/content/media

  • it is type-aware: the Media module knows what an image, a PDF or a YouTube link is and extracts metadata from it

Media items are normal content entities, so Views, JSON:API, Search API, Content Moderation and the Entity API treat them like nodes. Their canonical route /media/{ID} returns a 404 by default, because media is meant to be displayed inside another page; the Media settings can enable a standalone URL when you need one (the schema key is standalone_url).

The Media module ports most of what the contributed Media Entity project used to provide. Older tutorials that mention the media_entity, media_entity_image or media_entity_document modules describe the pre-core history; on a current site you enable core Media and nothing else.

Architecture at a glance

flowchart LR N[Exhibition node<br/>field_hero_media] -->|entity reference| M[Media item<br/>bundle: image] M -->|source field| F[(File entity<br/>field_media_image)] M --> T[Thumbnail<br/>from source plugin] MT[Media type config<br/>source plugin + field map] -.defines.-> M SP[Source plugin<br/>File / Image / oEmbed] -.reads.-> F SP -.fills.-> T ML[Media Library<br/>modal + widget] -->|select or upload| M CK[CKEditor 5<br/>drupal-media element] -->|data-entity-uuid| M

A media type (a config entity, media.type.*) is the bundle. It names one source plugin, one source field that holds the actual resource, and an optional field map. The source plugin knows how to read metadata from the source field’s value. When an editor saves a media item, Drupal asks the plugin for a thumbnail and for the values mapped to other fields.

Enabling the modules

# Media and Media Library are in core: only enable them.
ddev drush en media media_library -y
ddev drush cr

Media Library depends on Media, so enabling it also enables Media. The Standard installation profile enables both modules and creates the media types Document, Image, Audio, Video and Remote video; the Minimal profile and other distributions may create different types or none. Because the Standard profile stopped creating the Article and Page content types in 11.4, do not assume that any particular configuration exists on a fresh site: check /admin/structure/media first.

Media types and source plugins

Open Structure > Media types (/admin/structure/media). Core ships three source plugins, and many media types can share the same plugin:

Source plugin Stores Typical media types

File (file)

a managed file in a file field

Document (PDF, Office files), Audio file, Video file

Image (image)

a managed image in an image field

Image

oEmbed (oembed:video)

a URL in a string field

Remote video

Contributed modules add more plugins (Instagram, Twitter, slideshows and others). The plugins are discovered through the media_source_info plugin type, which is declared with a PHP attribute in 10.2 and later; see Plugin API and attributes.

Each media type form has these parts:

  • Media source: the plugin. It cannot be changed after the first item exists.

  • Source field: the field that stores the resource. When you create the first media type for a given source, Drupal creates the field for you (for example field_media_image or field_media_document). Later types that use the same source may reuse that field or create a new one. Fields are shared between bundles only when they have the same type, so a file-based and an image-based type never share one.

  • Field mapping: metadata the plugin can supply, copied into fields of your choosing. Mapped values are copied into empty fields when an item is first created; they are not refreshed on later saves, and an open core issue tracks a manual refresh of metadata and thumbnails.

  • Queue thumbnail downloads (queue_thumbnail_downloads): fetch remote thumbnails in the background by cron instead of during the save.

  • Create new revision (new_revision) and Publishing status.

Metadata the Image plugin can provide includes name, MIME type, file size, width and height. The oEmbed plugin provides the resource type, title, author name and URL, provider name and URL, cache age, default name, thumbnail URI, thumbnail width and height, the source URL, width, height and the HTML representation.

A media type exported to configuration

Media types are configuration, so they travel with the rest of the site’s configuration (Configuration management). This is the Meridian remote video type:

# config/sync/media.type.remote_video.yml
langcode: en
status: true
dependencies:
  module:
    - media
id: remote_video
label: 'Remote video'
description: 'Videos hosted by a video service, embedded with oEmbed.'
source: 'oembed:video'
queue_thumbnail_downloads: false
new_revision: true
source_configuration:
  source_field: field_media_oembed_video
  thumbnails_directory: 'public://oembed_thumbnails/[date:custom:Y-m]'
  providers:
    - YouTube
    - Vimeo
field_map:
  title: name

The schema defines id, label, description, source, queue_thumbnail_downloads, new_revision, source_configuration and field_map for each type. The global media.settings object holds icon_base_uri, iframe_domain, oembed_providers_url and standalone_url.

Creating a media type from code

Config can be created by importing the YAML, but when a custom module such as meridian_collection needs a media type for scanned archive documents it can ship the type in config/install. The File plugin needs a source field, so the field storage and field config must be delivered too:

# modules/custom/meridian_collection/config/install/media.type.archive_document.yml
langcode: en
status: true
dependencies:
  module:
    - file
    - media
id: archive_document
label: 'Archive document'
description: 'Scanned letters and catalogue pages.'
source: file
queue_thumbnail_downloads: false
new_revision: true
source_configuration:
  source_field: field_media_archive_file
field_map:
  mimetype: field_mime_type
  filesize: field_file_size

Config in config/install that depends on a field must declare that dependency, and the fields field_media_archive_file, field_mime_type and field_file_size must also exist, with matching field.storage.media.* and field.field.media.* files. Exporting a working configuration from a development site with drush config:export is the dependable way to obtain those files.

The built-in types

Image

The Image plugin stores an image field and reads width, height, file size and MIME type from the file. Its default view display uses the large image style when that style exists. The plugin supplies the thumbnail from the image itself, and it reads the alt text from the source field’s alt property (the metadata attribute is thumbnail_alt_value), so the thumbnail’s alt text is the alt text the editor typed for the image.

Document

The File plugin stores any allowed file extension in a file field. The Standard profile allows document formats such as txt, doc, docx, pdf and spreadsheets. Restrict the extensions to what editors really need: a permissive list is how executable or script uploads reach a public directory. The thumbnail is a generic icon taken from the directory in icon_base_uri unless a contributed module generates a preview.

Audio and video files

These are File-based types that display with the HTML5 file_audio and file_video formatters. Drupal does not transcode anything, so editors upload files in a format that browsers play. Like documents, they show the generic icon as their thumbnail. Accessible audio and video also need transcripts or captions, which belong in extra fields on the media type; see Accessibility and performance in themes.

Remote video and oEmbed

The oEmbed plugin stores only a URL. When the item is saved Drupal resolves the URL to a provider using the oEmbed provider list, requests the provider’s oEmbed endpoint, and keeps the returned title, author and thumbnail. At display time the oembed formatter renders the provider’s player in an <iframe>.

  • The core plugin can use any provider in the list at oembed.com/providers.json, but only YouTube and Vimeo are allowed by default. Allowed providers are chosen per media type, in the media type’s source configuration (the providers list above), and the contributed oEmbed Providers module offers a user interface for more providers.

  • Providers are an allowlist for security reasons: the core OEmbed class only accepts hosts that appear in its plugin definition’s providers array. Another provider can be added by altering the plugin definition instead of writing a new class.

  • The oembed_resource constraint validates the field value, so a URL that no allowed provider handles is rejected with a form error.

  • If no provider matches, the URL resolver falls back to discovery: it fetches the page and looks for a <link type="application/json+oembed"> tag. That fetch runs only for hosts that match the media_oembed_discovery_trusted_host_patterns setting, so by default it is off.

  • Remote thumbnails are downloaded into thumbnails_directory, by default public://oembed_thumbnails/[date:custom:Y-m], with a file name that is a hash of the thumbnail’s URL; an existing file is reused. Core has no media type for remote audio: audio from a streaming service needs a provider that supports oEmbed and a contributed or custom plugin.

An event team can allow one more provider for the Meridian video type with an OOP hook (since 11.1; see Hooks and OOP hooks):

<?php

declare(strict_types=1);

namespace Drupal\meridian_collection\Hook;

use Drupal\Core\Hook\Attribute\Hook;

/**
 * Media-related hook implementations for the Meridian Museum.
 */
final class MediaHooks {

  /**
   * Allows an additional oEmbed provider for remote video media types.
   *
   * @param array<string, array<string, mixed>> $sources
   *   The media source plugin definitions.
   */
  #[Hook('media_source_info_alter')]
  public function alterMediaSources(array &$sources): void {
    if (isset($sources['oembed:video'])) {
      $sources['oembed:video']['providers'][] = 'Dailymotion';
    }
  }

}

Allowing a provider in the plugin definition makes it available to media types; each media type still has to select it in its own configuration. Before Drupal 11.1 the same code lives in meridian_collection.module as the function hook_media_source_info_alter().

The iframe domain

An oEmbed player is third-party markup. Core renders it through its own route (/media/oembed) inside an iframe, and the Media settings page at /admin/config/media/media-settings has an iFrame domain field (iframe_domain) so that the player can be served from another domain that also points at the Drupal site, which keeps session cookies away from the embedded content. Core does not validate that the alternate domain really points back to Drupal, and the setting needs a DNS name and TLS certificate of its own; check the current status of the feature before depending on it in production.

Thumbnails

Every media item has a thumbnail base field. It is produced by the source plugin on save:

  • Image: the image file itself, shown through an image style

  • oEmbed: the remote thumbnail, downloaded and stored locally

  • File, Audio and Video: a generic icon from icon_base_uri

The thumbnail’s alt text comes from the plugin’s thumbnail_alt_metadata_attribute. Thumbnails are used in the Media administration list, the Media Library grid and the thumbnail formatter. In the Media Library the grid uses the medium image style, falling back to a style named media_library. Set Queue thumbnail downloads on slow remote providers, so that a save does not wait for the network.

Alt text and accessibility

Alt text is the most common media defect, so decide where it lives:

  • For image media, alt text is a property of the source field value. On the media type’s image field the setting Alt field is enabled and Alt field required is on by default. The Media Library upload form exposes the alt field as soon as an image is uploaded. The media item’s own alt text is the default wherever it is displayed.

  • Because the alt text belongs to the media item, one image reused on ten pages has one alt text. If the same photograph needs a different description in a particular article, the CKEditor 5 embed offers an alt text override for that placement (see below). Reference fields have no per-reference alt text, so describe the image’s content, not the page’s topic.

  • Core has no first-class "decorative image" option for media: leaving the required alt field empty is blocked, and there is no checkbox. Contributed modules (Decorative Image Widget, Decorative Images) add one, and the lack of a decorative toggle in the media embed dialog is a known open issue. A decorative image is often better placed as a CSS background by the theme than as a media item.

  • Video and audio need captions and transcripts, which are content, not metadata.

  • The AI module can propose alt text with a vision model; see AI module and providers. Treat suggestions as drafts to be reviewed.

For multilingual sites the alt text is translatable only when the media type and its source field are made translatable; see Multilingual.

The Media Library

The Media Library module provides:

  • the library page at /admin/content/media (a view whose Grid tab shows thumbnails and whose Table tab shows the classic list)

  • a modal with an upload tab per allowed media type, and a library tab

  • the Media library field widget (media_library_widget) for media reference fields

  • a CKEditor 5 button (see below)

  • two entity form/view displays per media type, named media_library, created automatically when the type is created. The form display exposes the name field (if it is not mapped) and the source field, including the image’s alt and title properties; the view display shows the thumbnail with an image style.

Enabling the widget on a reference field

Create a field Reference > Media on the content type, restrict it to the media types you accept, and choose the Media library form widget. Meridian’s exhibition has a hero image that must be an image and a document list that must be PDFs:

# config/sync/field.field.node.exhibition.field_hero_media.yml (excerpt)
field_name: field_hero_media
entity_type: node
bundle: exhibition
label: 'Hero image'
required: true
field_type: entity_reference
settings:
  handler: 'default:media'
  handler_settings:
    target_bundles:
      image: image
    sort:
      field: _none
    auto_create: false
# config/sync/core.entity_form_display.node.exhibition.default.yml (excerpt)
content:
  field_hero_media:
    type: media_library_widget
    weight: 3
    region: content
    settings:
      media_types: {  }
    third_party_settings: {  }

The field’s cardinality sets how many items the editor can pick: the modal tells the user how many slots remain, and the widget stores one reference per selected item in order, which the editor can drag into a new order. The widget lets an editor select existing items or upload new ones in the same dialog, and Add media needs the permission to create media of the type (create image media, create document media and so on).

How the modal stays secure

The library is opened through a URL that carries a MediaLibraryState value object. It records an opener id (the widget or editor button that requested the library), the allowed media types, the selected type and the number of remaining slots. The state is signed with an HMAC that uses the site’s private key and hash salt, and a request with a modified query string is rejected with a 400 error. This is why a request to the library with a hand-edited media_library_allowed_types fails: an attacker cannot widen the list of types. It is also why changing the hash_salt invalidates the open dialogs and why environment-specific salts must not be shared between sites.

Embedding media in CKEditor 5

Embedding places a <drupal-media> element in the text field’s HTML. CKEditor 5 has been the only editor since Drupal 10.0; the documentation for CKEditor 4’s media button describes software that no longer exists. Setup on the text format at /admin/config/content/formats:

  1. Add the Media Library button to the toolbar by dragging it into Active toolbar.

  2. Enable the Embed media filter (media_embed) and drag it so that it runs last. The filters Limit allowed HTML tags and correct faulty HTML, Align images and Caption images must run before it.

  3. If the HTML-limiting filter is enabled, Drupal adds the element automatically to the allowed tags:

    <drupal-media data-entity-type data-entity-uuid data-view-mode data-align data-caption alt>
  4. Optionally set the filter’s default view mode, the media types that may be chosen in the library, and the view modes that may be selected in the Edit media dialog. To show a view-mode dropdown, also tick Allow the user to override the default view mode in the CKEditor 5 plugin settings for the media plugin.

The stored markup is a reference by UUID, not a copy of the file:

<drupal-media data-entity-type="media"
  data-entity-uuid="7f0d6f7c-0000-4000-8000-000000000001"
  data-view-mode="embedded_wide"
  data-align="center"
  data-caption="The 1912 orrery, restored in 2024."
  alt="Brass orrery with nine planets on curved arms"></drupal-media>

The UUID is a placeholder. When the page is rendered the media_embed filter replaces the element with the output of the chosen view mode, wrapped in whatever the theme provides for embedded media, and attaches the cache tags of the media item, so editing the media updates every page that embeds it.

Points that trip up editors:

  • only one item can be selected or added at a time in the library when it is opened from CKEditor

  • the live preview inside the editor may differ from the final page, because the theme CSS is not identical

  • the balloon toolbar of an embedded item offers alignment, a caption and an alt text override, depending on the media type and on the format’s configuration

  • a custom attribute, such as an extra CSS class, must be added to the format’s allowed tags or the HTML filter strips it. Customising the Edit media dialog by altering the editor_media_dialog form is not covered by core’s backward compatibility promise, so it can break in a minor release

  • the Embed media filter is a render-time filter: the stored text still contains <drupal-media>, which JSON:API consumers must resolve (see JSON:API)

  • do not place the Embed media filter on a format that untrusted users can use unless you also restrict the media types and view modes, because it renders any media item the user can name by UUID that they can view

Plain images pasted or uploaded straight into CKEditor 5 (the drupalImage plugin) are a different mechanism: they upload a file into the file system without creating a media entity. Prefer the Media Library button on sites that use media, and remove the image upload button from the toolbar, so that images do not bypass the library.

Rendering media in a theme

A media item renders through media.html.twig and the more specific suggestions media--[type].html.twig, media--[view-mode].html.twig and media--[type]--[view-mode].html.twig. The Meridian theme overrides the hero view mode of the image type:

{# themes/custom/meridian/templates/content/media--image--hero.html.twig #}
{%
  set classes = [
    'media',
    'media--type-' ~ media.bundle()|clean_class,
    'media--view-mode-hero',
  ]
%}
<figure{{ attributes.addClass(classes) }}>
  {{ content.field_media_image }}
  {% if content.field_credit|render|trim is not empty %}
    <figcaption class="media__credit">{{ content.field_credit }}</figcaption>
  {% endif %}
</figure>

Create the hero view mode at Structure > Display modes > View modes, enable it for the image media type, and select a responsive image style on its field_media_image formatter (Breakpoints and responsive images). Use view modes, not hard-coded sizes, so that an editor who embeds an image chooses a role ("wide", "thumbnail"), and designers keep control of the size.

Media versus a direct image field

Question Media reference Plain image or file field

Same asset on several pages?

Yes, one item, many references

No, each entity has its own file

Metadata beyond alt text (credit, licence, taxonomy)?

Add fields to the media type

Not possible without custom code

Editors browse and search earlier uploads?

Yes, in the Media Library

No

Remote video, audio, documents under one model?

Yes, one reference field for several types

Separate fields per kind

Per-use alt text?

Only by overriding in CKEditor 5

Yes, alt is stored with each field value

Complexity and storage

Extra entity, view modes and displays to maintain

Simple, low overhead

Access control

Media permissions plus file access; the file does not inherit the parent’s access

Follows the field’s formatter and file access

Use media for anything editors reuse or describe: hero images, galleries, documents and videos. A plain image field is still reasonable for a one-off decoration, such as a logo in a block, or an avatar. Media is also the better choice for decoupled sites, because JSON:API exposes media as a resource with a thumbnail and the source file as relationships.

Older books show media in a different light. Drupal 10 Masterclass (2023) treats media as a recent addition and was written for Drupal 10.0 and 10.1; the Media modules in 11.4 keep the same architecture. Tutorials for CKEditor 4, the contributed Entity Embed and Media Entity modules, and the 8.x-era "Media browser" (Media Entity Browser, Entity Browser and IMCE) describe configurations that are not needed with core Media Library.

Permissions and access

Media permissions are per type: Create [type] media, Edit own [type] media, Edit any [type] media, Delete own …​, View own unpublished media, View media, plus Administer media, Administer media types and Access the Media overview page. Media Library needs View media for the library tab and the create permission for the upload tab.

Files behave differently from entities:

  • A media item and its file are separate. A file in public:// is reachable by anyone who has the URL, regardless of the media item’s published status.

  • To store files privately, set the Upload destination of the source field to the private file system on the media type’s Manage fields page (field storage settings). With a private file system, the file of a published media item is publicly reachable and the file of an unpublished one is denied to anonymous users.

  • Media assets do not inherit the access rules of the entities that reference them. Showing a file only to users who can view the node that uses it requires custom access control, for example an access hook on private files.

Security checklist

  • Limit allowed file extensions for each source field and keep the default maximum upload size consistent with PHP’s upload_max_filesize and post_max_size.

  • Keep oEmbed providers to the ones you trust, leave discovery off unless a trusted-host pattern is defined, and consider an iframe domain.

  • Do not allow SVG uploads from untrusted users: an SVG is a document that can carry scripts.

  • Use |raw only for markup you control; media output in Twig should use content.field_*, which is escaped and cached.

  • Review which roles can use the Embed media filter, and put it only on formats that need it (Security hardening).

  • Image styles generate derivatives on demand, so serve them from a cache or CDN. Image style derivative URLs carry a token, so visitors cannot request arbitrary sizes.

Common errors and pitfalls

Symptom Cause and fix

Thumbnail shows a generic icon for an image

The type uses the File plugin instead of Image; create an Image-based type.

Remote video URL is rejected

No allowed provider matches. Allow the provider on the media type or add it through the contributed oEmbed Providers module.

Thumbnail of a remote video is missing after an outage

The download failed; re-save the item, or use queued downloads and run cron.

Field map does not update after changing the file

Mapped values are written only into empty fields when the item is first created.

Embedded media disappears from rendered text

The Embed media filter is disabled, runs before another filter that removes the element, or the allowed-HTML list lacks drupal-media and its attributes.

Editor cannot choose a view mode in CKEditor 5

The override is not enabled in the CKEditor 5 media plugin settings, or the filter allows fewer than two view modes.

Library shows no upload tab

The user lacks the create permission for any allowed type, or the field allows only types without an upload-capable source.

A private file is visible to everyone

The source field still uploads to public://.

Alt text is empty on embedded images

The image was inserted with the drupalImage plugin, or the alt field is not required on the media type.

Version notes

  • Media and Media Library are stable core modules in Drupal 10 and 11, and the oEmbed source is part of core Media.

  • CKEditor 5 replaced CKEditor 4 in 10.0. Contributed Entity Embed is not needed for media.

  • PHP-attribute plugin discovery applies to media source plugins since 10.2; annotations are on a deprecation path.

  • In 11.3 and 11.4 some procedural helpers in media_library.module are deprecated (the template preprocess functions are scheduled for removal in 12.0.0). Custom code that calls them should rely on the registered theme hooks instead.

  • No part of the Media modules is moved to contributed projects in Drupal 12; the upgrade path is the normal 11.3 or later.