WordPress’s oEmbed system automatically converts pasted URLs from whitelisted services — YouTube, Vimeo, Twitter, Spotify, SoundCloud — into rich embedded media. When an editor pastes a YouTube URL into the Gutenberg editor, WordPress detects it, queries YouTube’s oEmbed endpoint at https://www.youtube.com/oembed?url=..., receives embed HTML in return, and renders the video player. The whitelist is hardcoded in WordPress core, so URLs from custom or private video platforms, internal tools, or newer services that aren’t on the list appear as plain hyperlinks. Two functions extend this system: wp_oembed_add_provider() registers a URL pattern and the corresponding oEmbed API endpoint for services that implement the oEmbed specification; wp_embed_register_handler() registers a URL regex pattern and a PHP callback for services that don’t have an oEmbed API endpoint at all, letting you generate the embed HTML directly in PHP.
Problem: Editors paste URLs from a private corporate video platform into the block editor, but they appear as plain links because the domain isn't in WordPress's oEmbed whitelist.
Solution: If the platform has an oEmbed endpoint, use wp_oembed_add_provider(). If it doesn't, use wp_embed_register_handler() with a regex and a PHP callback that generates the <iframe> embed HTML directly.
<?php
// ── Approach 1: Service with an oEmbed endpoint ────────────────────────
// Register a provider for a video platform that implements oEmbed
add_action( 'init', 'register_custom_oembed_providers' );
function register_custom_oembed_providers() {
// wp_oembed_add_provider( URL pattern, oEmbed endpoint, regex flag )
wp_oembed_add_provider(
'https://videos.example.com/*', // URL pattern to match
'https://videos.example.com/api/oembed', // oEmbed endpoint URL
false // false = wildcard, true = regex
);
}
// ── Approach 2: Custom handler for a service without oEmbed ──────────
// wp_embed_register_handler( id, regex, callback, priority )
wp_embed_register_handler(
'example_video',
// Regex matches: https://embed.example.com/v/abc123
'#https?://embed\.example\.com/v/([a-zA-Z0-9_-]+)#i',
'render_example_video_embed'
);
function render_example_video_embed( $matches, $attr, $url, $rawattr ) {
// $matches[1] is the first capture group — the video ID
$video_id = sanitize_key( $matches[1] );
$width = isset( $attr['width'] ) ? (int) $attr['width'] : 640;
$height = isset( $attr['height'] ) ? (int) $attr['height'] : 360;
return sprintf(
'<div class="video-embed-wrapper"><iframe width="%d" height="%d"
src="https://embed.example.com/player/%s"
frameborder="0" allowfullscreen loading="lazy"></iframe></div>',
$width,
$height,
esc_attr( $video_id )
);
}
Remove an existing oEmbed provider (e.g. to disable Twitter embeds site-wide):
<?php
add_filter( 'oembed_providers', function ( $providers ) {
// Keys are URL patterns; find and remove the Twitter entry
foreach ( $providers as $pattern => $data ) {
if ( str_contains( $pattern, 'twitter.com' ) || str_contains( $pattern, 'x.com' ) ) {
unset( $providers[ $pattern ] );
}
}
return $providers;
} );
NOTE: WordPress caches oEmbed results in post meta (key _oembed_*) for 24 hours. If you register a new provider after posts already contain URLs from that provider, the old "no embed found" result may be cached. You can force a refresh by deleting the cached meta: delete_post_meta( $post_id, '_oembed_' . md5( $url ) ), or by running wp eval 'WP_oEmbed_Cache::delete_all();' via WP-CLI.