Debugging the WordPress media library in Gutenberg

This year I’ve been spending a lot more time learning how to contribute to the block editor in WordPress, and nowadays much of my time at work is spent in the Gutenberg repo. For most folks working with WordPress, you likely won’t need to worry about debugging issues between the block editor and the core media library, however if you’re contributing to Gutenberg or investigating bugs, you just might find yourself needing to debug the plugin and the core media library at the same time.

The other week, I worked on a bug fix in Gutenberg related to the media library, and it took me a little while to understand how everything is hooked together between the React code in Gutenberg, and the Backbone JS based code of the core media library. I thought I’d write up these notes in case they help anyone else debugging this part of Gutenberg / WordPress for the first time.

How is the media library hooked up into Gutenberg?

In blocks like the Gallery and Image blocks, the MediaPlaceholder component is used for the block’s placeholder state. The Media Library button is rendered in the placeholder via the MediaUpload component, by passing it a render prop that renders the button. The callback passed to the render prop receives an open function, which is used in the onClick handler of the button itself here.

At first glance the block editor’s MediaUpload component can be a little confusing. It’s a placeholder component that returns null, but is expected to be replaced by the editor.MediaUpload filter, and any component added to that filter should conform to the interface defined in the MediaUpload’s readme file.

Since the actual core media library is written in Backbone JS, this adds a layer of abstraction that keeps the current media library implementation encapsulated from the rest of the block editor components, while also allowing it to be overridden with other media library implementations.

For example, in, in wpcom-block-editor which lives in the Calypso repo, if the editor is running in the Calypso iframe, then the editor.MediaUpload filter is used to swap out the core media library for the React-based Calypso one here. In the implementation, you can see much of the interface of MediaUpload being used within openModal, and the render prop gets called passing in the openModal function to the open prop discussed earlier.

Getting back to Gutenberg, by default, in the post, site, and widget editors, the empty placeholder for the MediaUpload component is replaced with the MediaUpload component from @wordpress/media-utils — this is the one that bridges the React code and the core media library’s Backbone JS by calling methods on the global object.

In packages/media-utils/src/components/media-upload/index.js, this is where the “real” MediaUpload component lives, that is responsible for configuring the media library, setting state, and dealing with events like opening the media library, selecting media, and closing the library. It’s here where we debug the Gutenberg-side of the media library.

Debugging the media library in core and Gutenberg at the same time

When debugging the media library in Gutenberg, it isn’t always clear whether an issue exists in Gutenberg, in the core media library, or if it’s to do with how we’re calling the library.

I found it helpful to run watch commands for both Gutenberg and wordpress-develop at the same time, so that I could log out values from either side and fairly quickly get a sense of what was happening on either side of the React / Backbone divide. There’s a bunch of different ways to set up your local environment (I’ve taken to VVV a bit lately), but the most straightforward way for me was to use the Docker-based wp-env, which is included in the Gutenberg repo. I used the following steps:

  1. Clone wordpress-develop to a folder adjacent to the Gutenberg repo. E.g. my folders are set up like:
    • ~/dev/wordpress-develop/
    • ~/dev/gutenberg/
  2. Add a .wp-env.override.json file in the Gutenberg repo with a core field that points to the build directory in wordpress-develop as described in the wp-env docs. This will ensure that the WordPress core files are loaded from your local checkout, and the additional override file is excluded from source control. The rest of the settings in .wp-env.json will be unaffected. The file should look like this:
  "core": "../wordpress-develop/build"
  1. In one terminal window, go to the wordpress-develop directory, install npm dependencies (npm install) and then run the watch command (npm run dev). This will take a while until it’s built all the JS and is ready to watch for changes.
  2. Once that’s ready, open another terminal window / tab and go to the gutenberg directory.
  3. Follow the Gutenberg repo’s instructions on setting up your local dev environment if you haven’t already. If you have, you should be able to run wp-env start (or npm run wp-env start if you haven’t installed wp-env globally), and once Docker has started up, run npm run dev to start the Gutenberg dev build and watch for changes.
Gutenberg and core watch commands running side-by-side.

You should now be able to make changes to either the media library code in core, or the media utilities in Gutenberg, and after a few seconds (or so) be able to reload your test site and see your changes. If you’re anything like me, this means popping a lot of console.log and debugger statements in the code to investigate how things are put together.

A screenshot of logging to the console isn’t particularly interesting, so here’s a quick hack updating the text in the media library in core, and the image block’s placeholder in Gutenberg.

A few notes on the media library

The core media library is written in Backbone JS, a framework that I’m not very familiar with to be honest! It uses a model view controller (MVC) pattern, and from hacking around in the code, I found it a gentle reminder that the best tools, frameworks, and “best practices” of today, might seem quaint in only a few years’ time. For a long-lived project like WordPress, components need to be continually maintained and replaced over time. With all the effort in modernising the editor, I’m quite impressed with the approach of wrapping the media library in Gutenberg, so that the React and Backbone code can happily co-exist (most of the time!). It’s a pragmatic approach for the project, given that it’ll be a lot of work to modernise if and when the time comes to port it to another framework.

The media library is made available in the editor via a global variable, which is exposed via the files in /src/js/_enqueues/wp/media/ — this exposes the public API for things like the media library’s views, controllers, and models, enabling us to configure the media library how we want to when opening it for the gallery block, for example. In Gutenberg, a good place to see how this is used is to look at the getGalleryDetailsMediaFrame function, which configures its own custom details frame (a composite view containing multiple regions).

Once the media library is configured, we can then access the current frame, and retrieve or manipulate its state, and do things like set the currently selected media. It’s useful to know that while there’s quite a lot of custom code in the media library, the underlying Backbone objects have heaps of methods available (including proxying to Underscore.js), so there’s lots that we can do to manipulate the data. I’ve found it helpful to regularly look up the docs on Backbone Collections and Models as reference while hacking around.

Then, over in wordpress-develop, the main places I’ve been poking around has been in the models, controllers, and views directories, and in particular the Attachments and Query models (for looking at how media is fetched), and the Selection model and the GalleryAdd controller for looking into how to manipulate the current state of selected media. It was through hacking around here and adding a bunch of console.log() calls that I managed to come up with a fix in Gutenberg for the Gallery block where currently selected images were not appearing when opening the media library.

I’m still quite new to hacking around with the media library, but hopefully some of the notes here might be of use to someone else digging around and trying to see how it’s all hooked together. At the very least, I found this to be a good example of where it’s possible to come up with a fix for a problem without having a deep understanding of the framework in use — by treading carefully, and reading up on just enough of the framework and source code pertaining to the problem so that you can come up with a fix, and have a reasonable idea of what kind of impact your change might have. And then, do lots of testing to make sure you haven’t broken anything!