How do I get the HTML markup created in the Media Frame modal and insert it into the editor/page content?

I'm trying to insert media from a custom router-tab in the media frame. But I have no idea how to take the HTML markup created in the media frame and place it into the page content.

My code so far is based on the Github repo custom_media_menu.js created by Fab1en. https://gist.github.com/Fab1en/4586865


// custom state : this controller contains your application logic
wp.media.controller.Custom = wp.media.controller.State.extend({

    initialize: function(){
        // this model contains all the relevant data needed for the application
        this.props = new Backbone.Model({ custom_data: 'controller state model data.' });
        this.props.on( 'change:custom_data', this.refresh, this );

    // called each time the model changes
    refresh: function() {
        // update the toolbar

    // called when the select button in toolbar is clicked
    customAction: function(){
        console.log('controller.custom, toolbar button clicked. this.props; custom data', this.props.get('custom_data'));


// custom toolbar : contains the buttons at the bottom
wp.media.view.Toolbar.Custom = wp.media.view.Toolbar.extend({

    initialize: function() {
        _.defaults( this.options, {
            event: 'custom_event',
            close: false,
            items: {
                custom_event: {
                    text: wp.media.view.l10n.customButton, // added via 'media_view_strings' filter,
                    style: 'primary',
                    priority: 80,
                    requires: false,
                    click: this.customAction

        wp.media.view.Toolbar.prototype.initialize.apply( this, arguments );

    // called each time the model changes
    refresh: function() {
        // you can modify the toolbar behaviour in response to user actions here
        // disable the button if there is no custom data
        var custom_data = this.controller.state().props.get('custom_data');
        this.get('custom_event').model.set( 'disabled', ! custom_data );

        // call the parent refresh
        wp.media.view.Toolbar.prototype.refresh.apply( this, arguments );

    // triggered when the button is clicked
    customAction: function(){

// custom content : this view contains the main panel UI
wp.media.view.Custom = wp.media.View.extend({
    className: 'media-custom',

    // bind view events
    events: {
        'input':  'custom_update',
        'keyup':  'custom_update',
        'change': 'custom_update',
        'click': 'custom_update'

    initialize: function() {
        // create an input
        this.input = '<input type="text" value="' + this.model.get('custom_data') + '" />';

        // insert it in the view

        // re-render the view when the model changes
        this.model.on( 'change:custom_data', this.render, this );

    render: function(){
        this.input.value = this.model.get('custom_data');
        return this;

    custom_update: function( event ) {
        console.log('view.custom, custom update');
        this.model.set( 'custom_data', event.target.value );

// supersede the default MediaFrame.Post view
var oldMediaFrame = wp.media.view.MediaFrame.Select;
wp.media.view.MediaFrame.Select = oldMediaFrame.extend({

    initialize: function() {
        oldMediaFrame.prototype.initialize.apply( this, arguments );

            new wp.media.controller.Custom({
                id:         'my-action',
                menu:       'default', // menu event = menu:render:default
                content:    'custom',
                title:      wp.media.view.l10n.customMenuTitle, // added via 'media_view_strings' filter
                priority:   200,
                toolbar:    'main-my-action', // toolbar event = toolbar:create:main-my-action
                type:       'link'

        this.on( 'content:render:custom', this.customContent, this );
        this.on( 'toolbar:create:main-my-action', this.createCustomToolbar, this );
        this.on( 'toolbar:render:main-my-action', this.renderCustomToolbar, this );

    createCustomToolbar: function(toolbar){
        toolbar.view = new wp.media.view.Toolbar.Custom({
            controller: this

    customContent: function(){
        // custom content view
        var view = new wp.media.view.Custom({
            controller: this,
            model: this.state().props

        this.content.set( view );

}); // END document .ready

The first code-block you encounter extends on wp.media.controller.State and has a property called customAction that's called when the insert button is pressed. But I don't know which function to use to place the customAction stuff in the content and replacing the Gutenberg upload placeholder.

From looking around on the Stack-forums, I get the feeling that is has something to do with state. But when reading the Backbone JS documentation Controller and States isn't mentioned. So that must be something added in the WordPress implementation. I've been reading some of the core implementation, but I don't find it obvious whats going on. https://core.trac.wordpress.org/browser/tags/5.5.1/src/js/media/views/media-frame.js

Any hints on how to insert the markup, or where I can read up on the WordPress Media Frame implementation, will be much appreciated.

  • Hello! Unlike a forum this is a Q&A site and you need a well defined clear question that can be answered factually, an answer needs to fully answer your question, and both question/answers need to be self contained. Can you state clearly what your question is? ( note that recommendations are off-topic here ) And can you edit your question to include your code without linking offsite?
    – Tom J Nowell
    Sep 25, 2020 at 14:12
  • @TomJNowell Hi Tom, thanks for the input. I've added a code example and rewrote the question a bit. I hope the point is coming through better now. Sep 26, 2020 at 7:20


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.