WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

To expand: I'd like to utilize the same Modal code/appearance ( as used in wp.media.Modal, wp.media.FocusManager ) to open a modal of my own custom dialog, not the Media Editor. In the past, I've used thickbox for this sort of thing, but wp.media.Modal appears to be the way of the future for modals -- Not to mention it looks cool.

I've poked at the JS source a bit and have come to a couple possible solutions:

  1. "Borrow" the code media-views.js and use it in my plugin.
  2. "Extend" wp.media.Modal ( it is a Backbone View, after all ).
  3. Create a custom implementation, jQueryUI, etc.
  4. Just give up and use thickbox.

Borrowing seems slightly less dangerous than using wp.media.Model.extend({}), but wasteful. I'm not a big fan of jQueryUI's modals, but it would get the job done. At the same time, I could do a custom implementation of modals ( or base it off another lib ).

Feels like I'm missing something obvious: Has anyone else pulled this off or is the new media library modal code "too new" to allow for reuse?

share|improve this question
Looks like you're just missing trying it out. I'd recommend going for #2: probably the cleanest and most challenging/fun, plus it sounds like you know your way around Backbone. – montrealist Mar 19 '13 at 4:02
please share your findings! – paul Apr 14 '13 at 12:24
Interesting plugin/tutorial at github.com/ericandrewlewis/wp-media-javascript-guide - Interactive documentation for the Javascript powering WP Media. – jgraup Jan 20 at 18:08

Late answer & edit. Disclaimer: The following is no copy & paste-togo code.

Rough Sketch

As I never tried misusing the media modal for anything else, here's a short overview, sketched by breaking out a part from a project I'm currently on. It's not a ready to go example, but it should bring you close enough. Just read the comments carefully and implement the following PHP in your objects.


In our constructor we register our scripts, add meta boxes that hold info and a media button, filter in additional MIME Types (for e.g. ZIP) and care about saving the additional data:

public function __construct()
    add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );

    foreach( $this->post_types as $post_type )
        add_action( "add_meta_boxes_{$post_type}", array( $this, 'add_meta_box' ) );

    add_filter( 'media_view_settings', array( $this, 'filter_media_view_settings' ), 10, 2 );

    add_action( 'wp_insert_post_data', array( $this, 'wp_insert_post_data' ), 10, 2 );

Make sure that you abort if you don't need that script on a particular page. This saves memory, request time and helps keeping your installation clean.

public function enqueue_scripts( $page )
    if (
        ! in_array( $page, array( 'post.php', 'post-new.php' ) )
        # Assuming that there's a class property array that holds post types we want to add to
        # OR ! in_array( get_current_screen()->post_type, array_keys( $this->post_types ) )

        plugins_url( 'assets/js/media-modal.js', dirname( __FILE__ ) ),
            # 'jquery',

Then we add the meta box. Inside the function, we can rely on the $post objects post_type property, which will be set for new posts as well. As we already registered the callbacks in the constructor to the appropriate contextual hooks, we can simply take whatever post type comes along.

public function add_meta_box( $post )
        __( 'Upload', 'our_textdomain' ),
        array( $this, 'render_content' ),

Additional MIME Types

Simply throw in an array that either overrides or adds to the default MIME types of the Media Modal. You can as well add or override other settings. Just var_dump( $settings ); to see what the callback provides. Also make sure we don't intercept on the wrong post type.

public function filter_media_view_settings( $settings, $post )
    if ( ! in_array( $post->post_type, array_keys( $this->post_types ) ) )
        return $settings;

    $settings['mimeTypes'] += array( 'application/zip' );

    return $settings;

Render the content

public function render_content()
    $props = array(
        'modalTitle'      => __( 'Select ZIP Archives', 'our_textdomain' ),

        // The following data is what we will access later
        // SomeIDfromLocalizedScriptOBJ
        'buttonID'        => 'open-media-lib',
        'buttonClass'     => 'open-media-button',
        'buttonText'      => __( 'Add ZIP', 'our_textdomain' ),
        'buttonDataText'  => __( 'Select', 'our_textdomain' ),
        'buttonDataTitle' => __( 'Select Whatever', 'our_textdomain' ),

        'mimeTypes'       => array(
            $zip => __( 'ZIP Archive', 'our_textdomain' ),

    wp_nonce_field( plugin_basename( __FILE__ ), $this->nonce_name );
    <input type="button"
           class="button <?php echo $props['buttonClass']; ?>"
           id="<?php echo $props['buttonID']; ?>"
           value="<?php echo $props['buttonText']; ?>"
           data-title="<?php echo $props['buttonDataTitle']; ?>"
           data-button-text="<?php echo $props['buttonDataText']; ?>" />

Save the data

Finally we make sure our data gets saved properly and will be checked. Use all the esc_*() functions, typecasting, nonces and what not.

public function wp_insert_post_data( $data, $post_array )
    if (
        ! in_array( $post_array['post_type'], array_keys( $this->post_types ) )
        # OR ( defined( 'DOING_AUTOSAVE' ) AND DOING_AUTOSAVE )
        OR ! isset( $_POST[ $this->nonce_name ] )
        OR ! wp_verify_nonce( $_POST[ $this->nonce_name ], plugin_basename( __FILE__ ) )
        return $data;

    $post_array['zip'] = array_map( 'array_filter', $post_array['zip'] );

    $id = $post_array['ID'];
        get_post_meta( $id, 'zip' )

    return $data;

Final note, before heading over to the JS example: The code is broken out of a current project. So it will - as already mentioned - not work by default! It's only a guide and nothing else.


The javascript itself is pretty straight forward. Not. But as you can see, I'm injecting both the jQuery as the custom localized script object into the function. From there on, you'll have to add whatever logic you might need. The basic surroundings for different states and callbacks are provided and console.log()s are present.

var ds = ds || {};

( function( $, obj ) {
    var media;

    ds.media = media = {};

    _.extend( media, {
        view: {},
        controller: {}
    } );

    media.buttonID    = '#' + obj.buttonID,

    _.extend( media, {
        frame: function() {
            if ( this._frame )
                return this._frame;

            var states = [
                new wp.media.controller.Library(),
                new wp.media.controller.Library( {
                    id:                 'image',
                    title:              'Images',
                    priority:           20,
                    searchable:         false,
                    library:            wp.media.query( { type: 'image' } ),
                    multiple:           true
                } ),
                /*new wp.media.controller.Library( {
                    id:                 'video',
                    title:              'Video',
                    priority:           40,
                    library:            wp.media.query( { type: 'video' } ),
                    multiple:           false,
                    contentUserSetting: false // Show the Upload Files tab.
                } ),*/
                new wp.media.controller.Library( {
                    id:                 obj.SomeIDfromLocalizedScriptOBJ,
                    title:              obj.SomeTitlefromLocalizedScriptOBJ,
                    priority:           30,
                    searchable:         true,
                    // filterable:         'uploaded',
                    library:            wp.media.query( { type: obj.SomeMIMETypesfromLocalizedScriptOBJ } ),
                    multiple:           true
                    // contentUserSetting: true
                } ),

            this._frame = wp.media( {
                // className: 'media-frame no-sidebar',
                states: states
                // frame: 'post'
            } );

            this._frame.on( 'open', this.open );

            this._frame.on( 'ready', this.ready );

            this._frame.on( 'close', this.close );

            this._frame.on( 'menu:render:default', this.menuRender );

            this._frame.state( 'library' ).on( 'select', this.select );
            this._frame.state( 'image' ).on( 'select', this.select );
            this._frame.state( obj.ZIPTabID ).on( 'select', this.select );

            return this._frame;

        open: function() {
            console.log( 'Frame opened' );

        ready: function() {
            console.log( 'Frame ready' );

        close: function() {
            console.log( 'Frame closed' );

        menuRender: function( view ) {
            /* view.unset( 'library-separator' );
            view.unset( 'embed' );
            view.unset( 'gallery' ); */

        select: function() {
            var settings = wp.media.view.settings,
                selection = this.get( 'selection' );

            selection.map( media.showAttachmentDetails );

        showAttachmentDetails: function( attachment ) {
            // This function normally is used to display attachments
            // Handle removal of rows
            media.removeAttachmentRow( /* some var */ );

        removeAttachmentRow: function( row ) {
            // Remove stuff callback

        init: function() {
            // Open media frame
            $( media.buttonID ).on( 'click.media_frame_open', function( e ) {

            } );
    } );

    $( media.init );
} )( jQuery, wpse_obj );


Dominik Schilling - the author of the WP 3.5 media manager - has written a set of demos for media modals. You can view them on GitHub.

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.