Drupalwoo ☉

Building a custom Drupal media source for Vimeo

Drupal’s media system is most useful when editors need more control than pasting a provider iframe into a rich-text field. A Media Source plugin connects a media type to the origin of its content, while the media entity retains ownership of the title, caption, thumbnail, alt text and workflow status. A Vimeo source gives editors a familiar media dialog and lets templates reuse consistent fields without copying Vimeo video files into Drupal.

This guide builds a provider-specific source plugin for Drupal 10 and 11. It accepts a Vimeo video reference, validates it, retrieves public metadata through a fixed endpoint and prepares a privacy-aware player URL. Vimeo appears frequently in briefs from Sydney and Melbourne agency teams, while the same governed workflow can suit Brisbane-based publishers and Perth-based project teams that need consistent handover across campaign sites.

Planning the plugin and stored data

Begin by separating the remote asset from the information Drupal stores. The source field should hold a numeric Vimeo video ID and, where required, the privacy hash for an unlisted video. It should never hold an entire oEmbed response, iframe HTML or a Vimeo access token. Titles, uploaders, durations, thumbnails, captions and transcripts belong in separate media fields so an editor can correct a snapshot without rewriting the reference.

Create a module such as vimeo_media_source, with the plugin under src/Plugin/MediaSource/Vimeo.php and network code under src/Service/VimeoClient.php. Give the plugin the stable ID vimeo; the media type can then be called “Vimeo video”. A media type provides a reusable editorial contract, whereas the source plugin connects that contract to one remote provider.

Decide which fields are required before coding. At minimum, keep the video reference; useful additions include title, thumbnail URL, duration, caption and transcript. Fetch provider metadata through a service rather than from the browser, cache successful responses by video ID, and refresh them in a queue. This keeps page rendering fast and prevents an unavailable Vimeo API from blocking every media request.

Implementing the Vimeo source plugin

The plugin class lives in the standard Media Source namespace and carries an annotation that identifies it to Drupal’s plugin manager:

namespace Drupal\vimeo_media_source\Plugin\MediaSource;

use Drupal\media\Plugin\MediaSource\MediaSourceBase;

/**
 * @MediaSource(
 *   id = "vimeo",
 *   label = @Translation("Vimeo"),
 *   description = @Translation("Provides media items sourced from Vimeo."),
 *   default_destination = "media_vimeo"
 * )
 */
final class Vimeo extends MediaSourceBase {
}

The class extends core’s MediaSourceBase and implements the source-field contract used by the installed Drupal branch:

public function createSourceField(
  MediaTypeInterface $media_type,
  $name
) {
  return BaseFieldDefinition::create('string')
    ->setLabel($this->t('Vimeo video reference'))
    ->setDescription($this->t('Numeric Vimeo video ID and optional privacy hash.'))
    ->setSetting('max_length', 64)
    ->setProvider('vimeo_media_source')
    ->setDisplayConfigurable('view', 'default')
    ->setRevisionable(TRUE);
}

Returning a string field is deliberate: a Vimeo ID is opaque text rather than a Drupal entity reference. Validate it in the plugin’s edit form, but do not rely on the widget alone. Accept only Vimeo hosts, extract the numeric ID from normal share and player URLs, and carry an unlisted privacy hash separately. Reject lookalike domains before any HTTP request; otherwise a pasted URL could turn the site into an unrestricted server-side fetcher.

Put Vimeo calls in VimeoClient, inject Drupal’s HTTP client, and request https://vimeo.com/api/oembed.json with a validated video URL. Use a short timeout, check the response status and decode the body as JSON. Private videos may need Vimeo’s authenticated API instead of public oEmbed. Keep an API token in a secret store or settings.php, never in JavaScript, source control or exported site configuration. Implement the remote-ID, rendering-preparation and thumbnail methods required by the current MediaSourceInterface; method signatures differ between Drupal 8 and the supported 10/11 branches, so use core’s interface as the source of truth.

Rendering and securing remote video

At render time, read the stored ID and build https://player.vimeo.com/video/{id}; add the privacy hash and dnt=1 only when appropriate. Pass the resulting URL into a controlled iframe template, along with the stored title for the accessible frame title. Keep the player markup in a theme override or a dedicated render service, not in the source field. This lets a design refresh change dimensions and loading behaviour without editing media records.

Treat oEmbed metadata as untrusted remote data even when Vimeo is the expected provider. Escape titles, validate thumbnail URLs against expected Vimeo CDN hosts and never inject the provider’s HTML directly into a page. Add https://player.vimeo.com to the site’s frame-src policy, allow only the permissions the site needs, and avoid autoplay by default. Private hash values should be treated as access material: keep them out of logs, query-string analytics and public cache variations.

The practical trade-offs are:

Approach Drupal data External dependency Best use Main drawback
Core oEmbed source Share URL Vimeo oEmbed Generic embeds Limited provider validation
Custom Vimeo source ID and optional hash Vimeo oEmbed or API Repeated provider workflows Requires ongoing maintenance
Iframe in body Complete embed HTML Vimeo player Temporary one-off content Weak governance and accessibility
Custom player ID and custom interface Vimeo API Complete design control Highest development complexity

For a Drupal site that expects Vimeo to be a first-class source, the custom plugin offers the strongest balance. Keep the core oEmbed route when generic embedding is enough, and avoid direct iframe markup in article bodies because it bypasses field validation, accessibility checks and media permissions.

Testing the editorial workflow

Create a “Vimeo video” media type, attach the source plugin and verify that its source field appears in the media form. Test public, unlisted and revoked videos as separate cases, then confirm the thumbnail and title survive a metadata refresh. Teams that need richer editorial profiles can use custom user fields to record specialisms or coverage assignments, while role permissions and the source plugin remain separate concerns.

Before a release, work through this implementation checklist:

Run the launch checks in the environments where the content will be consumed:

Australian campaigns often have parallel social, agency and government deliverables, so the same Vimeo item may feed several destinations. A saved caption can be adapted for a website, transcript and social cutdown without reopening the remote video. Testing in Sydney and Melbourne offices is useful, but it does not replace a check from a regional connection; cached thumbnails and deferred metadata refreshes are especially helpful on slower links.

Deploying and maintaining the source

Deploy the module and media-type configuration through the normal Drupal release process, then set the Vimeo credential separately in each environment. Exported configuration should describe the source plugin and fields without containing secrets. If the source field or field labels change, write an update hook or configuration migration and preserve existing media IDs. A deployment that swaps the module but loses references can break hundreds of pages even when the front page still appears healthy.

Monitor VimeoClient failures by video ID and response category rather than logging full URLs, because those URLs can contain privacy hashes. Schedule refreshes outside peak Australian business hours and display timestamps in the site’s configured timezone; Perth remains on Australian Western Standard Time while Sydney alternates between Australian Eastern Daylight and Standard Time. This makes incident logs easier to compare without changing Drupal’s underlying date handling.

Document how editors recover a missing thumbnail, replace a restricted video and report an incorrect title. If the module starts storing uploader names or account identifiers, assess that personal information under the Australian Privacy Act instead of assuming every Vimeo attribute is harmless. Regular review against Vimeo API changes, Drupal core releases and current media-field usage keeps the plugin stable without turning Drupal into a second video hosting platform.