oEmbed Provider Generator

Registers an oEmbed provider WordPress does not know about, with the URL pattern, the regex flag people get wrong, and a note on where the result is cached.

Enable JavaScript to customise; default output below.

Prefixes the function names so they cannot collide with another plugin's.

Providers
  1. Only used in a comment, so the next person knows what the row is for.

    With the wildcard form, * matches anything. Tick the regex box below to pass a real expression instead, delimiters and all.

    From the service's own documentation. WordPress appends ?url= and &format=json itself.

One row per service. The pattern matches the URL a user pastes; the endpoint is the service's oEmbed API.

Live preview oembed-provider.php
<?php
/**
 * oEmbed providers.
 *
 * Adds services WordPress does not ship a provider for, so a bare URL on its own
 * line becomes an embed.
 *
 * WordPress can also find an endpoint by itself: it fetches the page and looks for
 * a <link rel="alternate" type="application/json+oembed"> tag. That is discovery,
 * it is on by default, and it means a provider entry is only needed when the
 * service does not advertise an endpoint, when discovery has been turned off, or
 * when you want the embed to work without the extra request.
 */

defined( 'ABSPATH' ) || exit;

add_action( 'init', 'my_plugin_register_oembed_providers' );

/**
 * Registers the providers.
 *
 * On init rather than at file load: wp_oembed_add_provider() writes into the
 * oEmbed singleton, and calling it too early is how a provider silently goes
 * missing.
 *
 * @return void
 */
function my_plugin_register_oembed_providers() {
	// Loom.
	wp_oembed_add_provider(
		'https://www.loom.com/share/*',
		'https://www.loom.com/v1/oembed',
		false
	);

}

add_action( 'init', 'my_plugin_maybe_clear_oembed_cache' );

/**
 * Clears cached oEmbed results after a provider change.
 *
 * Results are cached twice: in post meta, as _oembed_<hash> on the post that
 * contains the URL, and in a transient for the HTTP response. Changing a provider
 * does not touch either, which is why an edit often appears to do nothing at all.
 *
 * Bump the version constant below to clear them once.
 *
 * @return void
 */
function my_plugin_maybe_clear_oembed_cache() {
	$version = '1';

	if ( get_option( 'my_plugin_oembed_version' ) === $version ) {
		return;
	}

	global $wpdb;

	// Post meta first: these are what the editor and the front end read.
	$wpdb->query(
		"DELETE FROM {$wpdb->postmeta} WHERE meta_key LIKE '\_oembed\_%'"
	);

	update_option( 'my_plugin_oembed_version', $version );
}

Output is valid and updates as you type.

WordPress turns a bare URL on its own line into an embed when it knows the service. wp_oembed_add_provider() teaches it one it does not know.

Two things about it are worth knowing before you write the call. WordPress can often find the endpoint by itself: it fetches the page and looks for a <link rel="alternate" type="application/json+oembed"> tag, which is discovery, and it is on by default. So a provider entry is only needed when the service does not advertise an endpoint, when discovery has been turned off, or when you want to skip the extra request.

And the third argument decides how the pattern is read. Off, * is a wildcard. On, the whole string is a regular expression, delimiters included. Passing a regex with the flag off matches nothing and produces no error.

How to use

  1. Set a function prefix.
  2. Add a row per service: the URL pattern people will paste, and the service’s oEmbed endpoint from its documentation.
  3. Tick the regex box only if you are passing a real expression.
  4. Paste the file into your plugin, or into a site-specific plugin.

Example

add_action( 'init', 'my_plugin_register_oembed_providers' );

function my_plugin_register_oembed_providers() {
	// Loom.
	wp_oembed_add_provider(
		'https://www.loom.com/share/*',
		'https://www.loom.com/v1/oembed',
		false
	);
}

Registering on init rather than at file load is deliberate: wp_oembed_add_provider() writes into the oEmbed singleton, and calling it before WordPress has built that is how a provider goes quietly missing.

Pitfalls

Cached results do not change when the provider does. The HTML is cached twice: as _oembed_<hash> post meta on the post containing the URL, and in a transient for the HTTP response. Edit a provider and the old embed keeps rendering, which looks like the change did nothing. The generated file includes a one-shot clearing helper for that reason.

The regex flag is all or nothing. With it off, only * is special. With it on, your string is a full pattern and needs delimiters: #https?://video\.example\.com/(\d+)#i. A pattern that works in a regex tester and fails here is usually missing them.

The endpoint is the API, not the page. WordPress appends ?url= and &format=json itself. Passing a page URL produces HTML where JSON is expected, and the embed falls back to a plain link.

A failed embed is silent on the front end. You get a link instead of an embed. To see why, check the response from the endpoint by hand and look for a WP_Error from wp_remote_get, since a provider on a host that blocks outbound requests will never work.

The returned HTML goes through wp_kses for users without unfiltered_html, which strips iframes for ordinary editors. That is a sensible default. The option to skip it is in the generated file and it means trusting the provider with your page: an XSS at their end becomes one at yours.

Some services need an access token. oEmbed endpoints for Facebook and Instagram have required one since 2020, which is why those embeds broke in core and now need an app. A provider entry alone will not bring them back.

A provider is site-wide. Adding one in a theme means the embeds stop working when the theme changes. A small plugin is the right home.

Compatibility

Everything runs in the browser: nothing is uploaded and nothing is stored.

wp_oembed_add_provider() has been in WordPress since 2.9 and is unchanged. The init hook is the conventional place; anything before plugins_loaded is too early.

The generated cache-clearing helper deletes _oembed_% post meta with one query and records a version in an option so it runs once. That is deliberately blunt: there is no supported API for clearing oEmbed caches selectively, and the alternative is waiting for the transient to expire, which is a day by default and can be much longer for the post meta.

Every generated file was checked with php -l, including inputs containing quotes, */ and ?>, since a snippet that does not parse is worse than no snippet. Names go through a comment filter, keys through an identifier filter, and values through single-quoted string escaping.

Discovery, the <link rel="alternate"> route, is controlled by the oembed_discovery and related filters. If embeds work on one site and not another with the same code, that is the first thing to check.

Frequently asked questions

Do I need a provider at all?
Only if discovery does not do the job. Paste the URL into a post first: if it embeds, the service advertises an endpoint and you have nothing to write.
Where do I find a service’s endpoint?
Its developer documentation, usually under “oEmbed”. The oEmbed site also keeps a providers list, and a lot of services follow the same shape: /oembed or /v1/oembed.
Why does my embed work in the editor and not on the front end?
Usually the cache, occasionally wp_kses stripping the iframe for a non-administrator author. Clear the post meta and test as the role that will actually be viewing.
Can I change how an embed is rendered?
Yes: oembed_dataparse gives you the response and the HTML before it is returned, and embed_oembed_html wraps the final markup. Wrapping for a responsive aspect ratio is the common case.
Is there a way to embed without oEmbed?
A shortcode or a block. oEmbed is worth it when people are pasting URLs; a block is better when the embed needs options.

From the people who built this tool

WP Adminify

The WordPress admin, rebuilt: a dashboard worth looking at, menu and column control, a real file manager and the login page your client sees.

See WP Adminify Free version on WordPress.org

Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.