Extending Media Explorer

The recommended way to extend Media Explorer is to create a new plugin that extends the MEXP_Service class. Start by adding the following to your plugin:

add_filter( 'mexp_services', 'mexp_service_new' );

function mexp_service_new( array $services ) {
	$services['new'] = new MEXP_New_Service;
	return $services;

new being the name of your service and MEXP_New_Service, the class name of the new service.

If you look in the core Media Explorer files, you’ll notice that there are two additional files for each extension, service.php and template.php. For the services that are bundled with Media Explorer, these extra files are auto-included by the plugin.

For extensions that you write for your own sites, this pattern isn’t enforced and the files aren’t automatically loaded. For the rest of this documentation, we’ll continue to refer to these two separate files as it’s a decent way of introducing the different parts of the plugin.

To help you get started, we’ve put together a barebones plugin that is a functional example of how to extend the Media Explorer; get it from Github.


The class contained in this file will handle the AJAX request, then call to the API of the service (if exists), and then returns the response to the JS template. This is also the best place to enqueue your scripts or CSS files.

This file contains a class that must extend MEXP_Service:

class MEXP_Servicename_Service extends MEXP_Service {

    public function __construct() {
        // Set the template for the service
        require_once dirname( __FILE__ ) . '/template.php';

        $this->set_template( new MEXP_Servicename_Template );

    public function load() {

        // This is the perfect place for enqueuing scripts or styles for the
        // service.

        // There are some actions and filters you may want to know about:
        add_action( 'mexp_enqueue', array( $this, 'enqueue_statics' ) );

        add_filter( 'mexp_tabs', array( $this, 'tabs' ) );

        add_filter( 'mexp_labels', array( $this, 'labels' ) );


    public function enqueue_statics() {
        // instance the class
        $mexp = Media_Explorer::init();

            $mexp->plugin_url( 'services/newservice/new.js' ),
            array( 'jquery', 'mexp' ),
            $mexp->plugin_ver( 'services/newservice/new.js' ),

    public function request( array $request ) {

        // This will be method that will handle the AJAX request. All the
        // parameters of the form will be available in $request['params'].
        // Here you can connect to the API of the service to retrieve all
        // the media.
        // This method must return:
        // * an MEXP_Response object in case that everything's gone ok or
        // * false in case that there are no results or
        // * an instance of WP_Error in case that there is an error


    public function tabs( array $tabs ) {

        // This method must append an associative array to the $tabs variable
        // passed as parameter.
        // That array will be used for displaying the upper tabs of
        // the service. Every tab has a mandatory field 'text' that
        // must contain its text

        $tabs['newservice'] = array(
            'default' => array(
                'text'       => _x( 'default', 'Tab title', 'mexp'),
                'defaultTab' => true
            'other_tab' => array(
                'text'       => _x( 'This is the other tab', 'Tab title', 'mexp'),
        return $tabs;

    public function labels( array $labels ) {

        // This method must append a multidimensional array to the
        // $labels variable passed as prameter containing all the
        // labels that you could need for the UI

        $labels['newservice'] = array(
            'title'     => __( 'Insert Servicename', 'mexp' ),
            'insert'    => __( 'Insert', 'mexp' ),
            'noresults' => __( 'No results.', 'mexp' ),
        return $labels;

Note: This file must be included in the `mexp_services` filter. Requiring it before then can lead to undefined class errors.

It also must contain a call to add_filter(), which will add an object of the just created class into the array of services of the Media Explorer engine:

add_filter( 'mexp_services', 'mexp_service_name_callback', 10, 1 );

function mexp_servicename_callback( $services ) {
    require_once __DIR__ . '/path/to/my/service.php';

    return $services['servicename'] = new MEXP_Servicename_Service();


The template.php file contains a class that must extend MEXP_Template and must override 3 public methods of the parent class. Each one of those methods must output the Backbone template for a different part of the UI:

  • item(), the template for the results.
  • thumbnail(), the template for the thumbnail.
  • search(), the template for the search form.
class MEXP_Servicename_Template extends MEXP_Template {

     * This method should output the Backbone template for the items. Its important
     * that each item has the a#mexp-check-{{id}}, which will be used as the outer toggle
     * blue box
    public function item( $id, $tab ) {
<div id="mexp-item-servicename-<?php echo esc_attr( $tab ); ?><br />-{{ data.id }}" data-id="{{ data.id }}">
<div><img alt="" src="{{ data.thumbnail }}" /></div>
<div>{{ data.content }}</div>
<div>{{ data.date }}</div>
        <a title="<?php esc_attr_e( 'Deselect', 'mexp' ); ?>" href="#" data-id="{{ data.id }}">
<pre><a id="mexp-check-{{ data.id }}" title="<?php esc_attr_e( 'Deselect', 'mexp' ); ?>" href="#" data-id="{{ data.id }}">

        <!--?php     }     /**      * This is the method that should output the template for the thumbnails. If the      * output is empty, it will use the default template.      */     public function thumbnail( $id ) {         ?-->
        <!--?php     }     /**      * This method should output the template for the search form of the service. If you      * want to display different search forms for each tab, use the second parameter,$tab,      * which will contain a string with the name of the current tab.      */     public function search( $id, $tab ) {         ?--></pre>
<form action="#"><input
 value="{{ data.params.q }}"

 placeholder="<!--?php esc_attr_e( 'Enter Username', 'mexp' ); ?-->"
 <input type="hidden" name="tab" value="by_user" />
 <input id="page_token" type="hidden" name="page_token" value="" />
        <!--?php <br ?-->    }
  • The item() method outputs the template for each one of the results.
  • The thumbnail() method outputs the template for media thumbnails.
  • The search() method outputs the markup for the search form.
You must follow the Backbone.js template syntax for these templates.

Javascript and CSS

Although not mandatory, some Javascript or CSS would be useful for your new implemented service to work in the same way as included services. The best way to add statics to the project is in using the action mexp_enqueue, in the load() method, like we did in the MEXP_Servicename_Service class.

For example, the custom Javascript in your service can add support for infinite scroll (see /services/youtube/js.js) or geolocation (see /services/twitter/js.js).

The Javascript application of Media Explorer is written in Backbone.js, so it’s really easy to extend.

There are several views inside the Javascript application (js/mexp.js), but the most important is likely wp.media.view.MEXP, the view of the container. You can extend it as follows:

wp.media.view.MEXP = wp.media.view.MEXP.extend({
    * This function is a good place for subscribing to events and
    * get things ready.
    initialize: function() {
        // If you don't want to change the behavior of a method
        // in the parent class Just override it as follows:
        wp.media.view.MEXP.initialize.apply( this, arguments );

    * This method fires when the user switches between tabs or when
    * the collection of results is rendered.
    render: function() {

    * This method returns the rendered HTML of a item passed as
    * parameter.
    * Must return the plain HTML of the parsed model.
    * Override this method in case you want to change its behavior.
    renderItem: function( model ) {

    * This method performs the AJAX request
    fetchItems: function( ) {

    * fetchedSuccess fires when the AJAX request returns a success.
    * response contains all the response data
    fetchedSuccess: function( response ) {

    * fetchedError fires when the AJAX request returns  an error
    fetchedError: function( response ) {

Ready to get started?

Drop us a note.

No matter where you are in the planning process, we’re happy to help, and we’re actual humans here on the other side of the form. 👋 We’re here to discuss your challenges and plans, evaluate your existing resources or a potential partner, or even make some initial recommendations. And, of course, we’re here to help any time you’re in the market for some robust WordPress awesomeness.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.