Customizing WordPress oEmbed Content

We teamed up with SiteGround
To bring you up to 65% off web hosting, plus free access to the entire SitePoint Premium library (worth $99). Get SiteGround + SitePoint Premium Now

WordPress makes it very easy to embed content. For example, you can grab a YouTube URL, drop it into the post editor, and automatically see a video embedded in your post. This is all powered by oEmbed, an open standard for how a page should provide embed code data.

WordPress relies on this standard in order to fetch and display a provided URL. While WordPress has long been able to consume oEmbed providers like YouTube, with the inclusion of the WP-API in WordPress 4.4, WordPress can now function as an oEmbed provider. This means other sites can embed your WordPress content on their sites as easily as you can currently embed videos as mentioned in the example above.

The new oEmbed provider functionality leverages the WP-API to return JSON or XML structured embed data to oEmbed consumers, giving them the information they need to embed the page on their own. Like everything in WordPress, the oEmbed provider functionality is easily modifiable with actions and filters. Let’s take a look at how we can use these hooks to embed a custom post type.

oEmbed WordPress

What We’ll Be Building

We’re going to be building a simple plugin, ‘Status Update’, which registers a custom post type (CPT), status-update. This CPT simply removes the title input in the post editor, making it function like a Facebook or Twitter status update. We’ll customize the oEmbed output of this post to show the user’s name and avatar. You can see the final product in this GitHub repo.

Setting up the Plugin

Go to your plugins folder in wp-content and create a folder named sp-status-update. In that directory, create a file named sp-status-update.php. This is where we’ll be creating the plugin’s main class. Add the plugin header to the top of the file:

/**
 * Plugin Name: Status Update
 * Plugin URI:  https://github.com/mAAdhaTTah/sitepoint-status-update
 * Description: Post your own Facebook-like status updates
 * Version:     1.0.0
 * Author:      James DiGioia for SitePoint
 * Author URI:  http://jamesdigioia.com
 * Text Domain: wp-status-update
 * Languages:   /languages
 */

We’ll then set up the main plugin singleton on which we’ll build our plugin:

class SP_Status_Update {

    /**
     * Plugin instance.
     *
     * @var static
     */
    protected static $instance;

    /**
     * Retrieve the plugin instance.
     *
     * @return static
     */
    public static instance() {
        if (null === static::$instance) {
            static::$instance = new static;
        }

        return static::$instance;
    }

    /**
     * Plugin constructor.
     */
    protected function __construct() {
        // Add actions/filters here
    }
}

Finally, we need to start the plugin at the bottom of the file:

SP_Status_Update::instance();

We’ll be adding our hooks and filters in the constructor, as noted in the comment.

Register a Custom Post Type

Using the excellent GenerateWP, we can easily customize and register our custom post type. Register the method with WordPress:

    add_action( 'init', array( $this, 'register_post_type' ), 0 );

and add the registration method to your class:

    /**
     * Register the Status Update custom post type.
     */
public function register_post_type() {
    $labels = array(
        'name'                  => _x( 'Status Updates', 'Post Type General Name', 'sp-status-update' ),
        'singular_name'         => _x( 'Status Update', 'Post Type Singular Name', 'sp-status-update' ),
        'menu_name'             => __( 'Status Update', 'sp-status-update' ),
        'name_admin_bar'        => __( 'Status Update', 'sp-status-update' ),
        'archives'              => __( 'Satus Update Archives', 'sp-status-update' ),
        'parent_item_colon'     => __( 'Parent Update:', 'sp-status-update' ),
        'all_items'             => __( 'All Updates', 'sp-status-update' ),
        'add_new_item'          => __( 'Add New Status Update', 'sp-status-update' ),
        'add_new'               => __( 'Add New', 'sp-status-update' ),
        'new_item'              => __( 'New Status Update', 'sp-status-update' ),
        'edit_item'             => __( 'Edit Status Update', 'sp-status-update' ),
        'update_item'           => __( 'Update Status Update', 'sp-status-update' ),
        'view_item'             => __( 'View Status Update', 'sp-status-update' ),
        'search_items'          => __( 'Search Status Updates', 'sp-status-update' ),
        'not_found'             => __( 'Not found', 'sp-status-update' ),
        'not_found_in_trash'    => __( 'Not found in Trash', 'sp-status-update' ),
        'featured_image'        => __( 'Featured Image', 'sp-status-update' ),
        'set_featured_image'    => __( 'Set featured image', 'sp-status-update' ),
        'remove_featured_image' => __( 'Remove featured image', 'sp-status-update' ),
        'use_featured_image'    => __( 'Use as featured image', 'sp-status-update' ),
        'insert_into_item'      => __( 'Insert into Status Update', 'sp-status-update' ),
        'uploaded_to_this_item' => __( 'Uploaded to this Status Update', 'sp-status-update' ),
        'items_list'            => __( 'Status Updates list', 'sp-status-update' ),
        'items_list_navigation' => __( 'Status Updates list navigation', 'sp-status-update' ),
        'filter_items_list'     => __( 'Filter Status Updates list', 'sp-status-update' ),
    );
    $args = array(
        'label'                 => __( 'Status Update', 'sp-status-update' ),
        'description'           => __( 'Simple Status Update', 'sp-status-update' ),
        'labels'                => $labels,
        'supports'              => array( 'editor', 'author', ),
        'hierarchical'          => false,
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 5,
        'show_in_admin_bar'     => true,
        'show_in_nav_menus'     => true,
        'can_export'            => true,
        'has_archive'           => true,
        'exclude_from_search'   => false,
        'publicly_queryable'    => true,
        'capability_type'       => 'page',
    );
    register_post_type( 'sp_status_update', $args );
}

Now we’re ready to start customizing our oEmbed output!

Before We Start: How oEmbed Works

When you drop a link into the TinyMCE editor, how does it know how to embed that URL live in your editor?

The first step is discovery. oEmbed asks providers “to make their oEmbed support discoverable by adding elements to the head of their existing (X)HTML documents.”. Those elements direct oEmbed consumers to the API endpoints, which then provide a structured representation of the embedded post. oEmbed supports both JSON and XML formats.

WordPress includes elements and support for both formats. The function [wp_oembed_add_discovery_links], which is registered on wp_head, is responsible for including the proper <head> tags and can be customized with the oembed_discovery_links filter.

From there, the editor grabs the structured version from the API endpoint. The JSON response looks like this:

{
  "version": "1.0",
  "provider_name": "Website Name",
  "provider_url": "http://example.com",
  "author_name": "admin",
  "author_url": "http://example.com/author/admin/",
  "title": "",
  "type": "rich",
  "width": 600,
  "height": 338,
  "html": "long string of html"
}

The XML response is structured the same way, and WordPress provides support for both. It leverages the WP-API to provide both the JSON and XML formatted responses, although the XML response required special handling.

The editor uses this response, sanitizing the html key and injecting it into the page. In WordPress, the oEmbed HTML contains a blockquote with the post title , a <script> tag for safely and correctly handling iframe messages, and a sandboxed iframe. This HTML can be fetched with [get_post_embed_html] and filtered with embed_html.

The iframe URL is where all the magic happens. By default, it imports the built-in embed template, but you can override it completely and use your own embed template. Use the embed_template filter and return your own template file to swap it out completely:

add_filter( 'embed_template', 'my_embed_template' );
function my_embed_template( $template ) {
    if ( 'custom_post_type' === get_post_type() ) {
      return '/path/to/custom-embed-template.php';
    }

    return $template;
}

For the most part, you’re not going to need to customize any of the above filters, but it’s good to familiarize yourself with the internals of the oEmbed provider in order to leverage it more effectively. If you need that power, it’s there. We’re going to modify the built-in template with the hooks and filters it makes available to us.

Customizing the oEmbed Title

Since the plugin is embedding a status update, the first thing we should do is remove the title from the output of the oEmbed. To do that, we need to hook into the the_title filter:

add_filter( 'the_title', array( $this, 'remove_embed_title' ), 10, 2 );

In the remove_embed_title, we’ll meet the first helper function provided: is_embed. This function returns true when we’re in the oEmbed context, which we can use to customize the oEmbed output.

Here’s how we remove the title:

/**
 * Remove the title from the Status Update oembed.
 *
 * @param string $title Post title.
 * @param int    $id Post ID.
 *
 * @return string
 */
public function remove_embed_title( $title, $id ) {
    $post = get_post( $id );

    if ( is_embed() && 'sp_status_update' === $post->post_type ) {
      return '';
    }

    return $title;
}

This ensures the title is only removed in the oEmbed. Now, if we were building this plugin out in a more complete way, we may not need to include the is_oembed check, as you’ll probably just remove it everywhere, but this demonstrates how we can target and customize the oEmbed title output.

Customizing the oEmbed Excerpt

Because the Status Updates are supposed to be small pieces of writing, we don’t want to display an excerpt but the entirety of the post. We can modify what gets excerpted by filtering the the_excerpt_embed hook:

add_filter( 'the_excerpt_embed', array( $this, 'get_excerpt_embed' ) );

We then check if the current post type is our custom post type, and if it is, we return the full content output:

/**
 * Returns the custom excerpt for the custom post type.
 *
 * @param  string $output Default embed output.
 * @return string         Customize embed output.
 */
public function get_excerpt_embed( $output ) {
    if ( 'sp_status_update' === get_post_type() ) {
      return get_the_content();
    }

    return $output;
}

This ensures the excerpt is always the full post content for the Status Update. In the iframe, that excerpt is output in a div with class wp-embed-excerpt, to which you could apply custom styles. If you wanted to do something more complex, like a calendar event, you could output your custom content in that div, or you could append your own content with the embed_content action.

Adding Extra Content to oEmbed

The oembed_content action fires right after the excerpt is output, providing a location between the excerpt and the footer to output your own custom HTML. For the Status Update plugin, we’re going to use it to output the author’s name and avatar. First, we add the action:

add_action( 'embed_content', array( $this, 'embed_author' ) );

We can use this action hook to echo out our custom HTML:

/**
 * Add the author div to the embed iframe.
 */
public function embed_author() {
    if ( 'sp_status_update' !== get_post_type() ) {
      return;
    }

    $output = '<div class="wp-embed-author">';
    $output .= '&mdash; ';
    $output .= get_the_author();
    $output .= get_avatar( get_the_author_meta( 'ID' ), 20 );
    $output .= '</div>';

    echo $output;
}

We have a simple emdash, the authors name, and the author’s avatar. Again, if you need more extensive customization, this action gives an opportunity to output any additional custom HTML.

Adding Custom Styles and Scripts to oEmbed

Like a standard template page, the oEmbed template has a header and footer. The hook embed_head gives you a location to enqueue your own styles in the head of the template. Like we have been, attach your method to the hook:

add_action( 'embed_head', array( $this, 'embed_styles' ) );

We can use this hook to enqueue our own styles with the standard wp_enqueue_style function, or if we don’t have that many styles to include, we can echo it out directly in the head:

/**
 * Embed the plugin's custom styles
 */
public function embed_styles() {
    echo <<<CSS
<style>
  .wp-embed-excerpt, .wp-embed-author {
    font-size: 24px;
    line-height: 24px;
    margin-bottom: 5px;
  }

  .wp-embed-author {
    float: right;
  }
</style>
CSS;
}

On the JavaScript side, we can do the same using the embed_footer action, including using the wp_enqueue_script function to enqueue new JavaScript files, or output any custom JavaScript directly in-line.

Conclusion

These are the primary hooks you’ll need to interact with in order transform the output to your preferences. All of the code in this tutorial can be found in the GitHub repo.

If you want to see these customizations in action, keep an eye on the next version of WP-Gistpen, which will leverage the WordPress oEmbed provider in order to embed syntax highlighted code snippets.

How are you planning on using the oEmbed provider feature in WordPress? Let me know in the comments!

We teamed up with SiteGround
To bring you up to 65% off web hosting, plus free access to the entire SitePoint Premium library (worth $99). Get SiteGround + SitePoint Premium Now