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
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
Source: Media module overview
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 ( |
a managed file in a |
Document (PDF, Office files), Audio file, Video file |
Image ( |
a managed image in an |
Image |
oEmbed ( |
a URL in a |
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_imageorfield_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 (theproviderslist above), and the contributed oEmbed Providers module offers a user interface for more providers. -
Providers are an allowlist for security reasons: the core
OEmbedclass only accepts hosts that appear in its plugin definition’sprovidersarray. Another provider can be added by altering the plugin definition instead of writing a new class. -
The
oembed_resourceconstraint 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 themedia_oembed_discovery_trusted_host_patternssetting, so by default it is off. -
Remote thumbnails are downloaded into
thumbnails_directory, by defaultpublic://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';
}
}
}
Source: OEmbed media source class
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
imagefield 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
Source: Media module overview
# 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: { }
Source: media_library.module
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:
-
Add the Media Library button to the toolbar by dragging it into Active toolbar.
-
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. -
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> -
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>
Source: Embedding media with CKEditor 5
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_dialogform 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>
Source: Working with Twig templates
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_filesizeandpost_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
|rawonly for markup you control; media output in Twig should usecontent.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 |
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 |
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.moduleare 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.