New Features and Enhancements with Customizer Changesets in 4.9

In WordPress 4.7 the concept of changesets was introduced in the Customizer (#30937). To understand the new Customizer improvements in 4.9, you must first go back and review what was proposed and implemented a year ago:

Customize Changesets Technical Design Decisions

Changesets are a way to persistently store changes made via the Customizer framework. Changesets contain the pending changes for any number of settings, and a setting can model any object in WordPress—whether options, theme mods, nav menu items, widgets, or even posts/pages and their postmeta. Changesets are identified by UUID (which is the post_name for the customize_changeset post type that stores the data as JSON in post_content). When a request is made to WordPress with the customize_changeset_uuid request param—whether to the frontend or to the REST API—the Customizer framework will bootstrap and all of the values from the changeset will be read and applied to the response via WordPress filters added by the settings’ respective WP_Customize_Setting::preview() methods.

Only an authorized user can write changes into a changeset for a given setting (according to its respective capability). But once it has been written then anyone can preview the site with the changes in the changeset applied: all that is needed is the UUID. Since previewing a changeset is now a readonly operation (whereas before 4.7 it was always a POST request), a changeset can be previewed on a site by authenticated and unauthenticated users alike. With the changeset UUID supplied when opening the Customizer, a user can keep iterating on a set of changes over several days or longer and only publish them once stakeholders are satisfied. Now, freelancers and agencies will be better able to communicate and collaborate on site changes with clients.

Once a customize_changeset post transitions to the publish status then all of the values in the changeset will be passed into their respective WP_Customize_Setting::update() methods to publish (“go live”) on the site: in version control terminology, the staged values from the changeset get committed and pushed. All of the changes go live together in a batch save operation (originally changesets were termed “transactions”).

As noted in the 4.7 merge proposal:

For the initial core merge, no UI changes are being proposed. The feature will only be exposed as the new query parameter on the URL. Adding a UI to this feature will happen in a future release.

The future [release] is now. Where the infrastructure of changesets was merged from the Customize Changesets feature plugin in 4.7, the key UI features from the plugin are now being merged in 4.9 after a significant number of design iterations.

This dev note contains sections on the following:

Continue reading

#4-9, #customize, #dev-notes

Improvements to the Customize JS API in 4.9

There are many user-facing Customizer improvements in 4.9, including: drafting/scheduling of changesets, autosave revisions, changeset post locking, frontend public previews, a new experience for browsing and installing themes, updated nav menu creation UX, and the code editing improvements for the Custom HTML widget and Additional CSS. But in addition to all of these, there are also many improvements for developers which will make extending the Customizer much more pleasant.

Something important to remember about the Customizer is that it is a single page application that is powered by JavaScript. Many developers may only interact with the PHP API for registering controls, settings, sections, panels, and partials. But controls, sections, and panels do not need to be registered in PHP at all. The PHP API for registration is essentially a wrapper for the underlying JS API. When you load the Customizer all of the params for the PHP-registered constructs are exported to the client for the JavaScript API to instantiate and initially add to the UI, but this JS API can dynamically instantiate additional constructs at any time thereafter in a Customizer session. This is how new widgets, nav menus, and nav menu items are added without requiring the entire Customizer to reload. You can also avoid statically registering settings and partials in PHP by instead adding filters to dynamically recognize settings and partials, allowing them to be registered on demand. All of this allows the Customizer application to scale out to be able to customize and preview an unlimited number of things on a site (e.g. any post or page with their postmeta in the Customize Posts feature plugin). The point here is that in order for the Customizer to scale, the JavaScript API must be used directly. So this is why the Customizer JS API improvements in 4.9 are important as they fix many longstanding annoyances and shortcomings with the JS API.

This dev note contains the following sections:

Continue reading

#4-9, #customize, #dev-notes

A New Themes Experience in the Customizer

WordPress 4.9 introduces a new experience for discovering, installing, and previewing themes in the customizer. Building on efforts during WordPress 4.7 development, this project prioritizes user flow, extensibility, and performance improvements.

A theme is the most fundamental aspect of customizing a site. This project seeks to unify the theme-browsing and theme-customization experiences by introducing a comprehensive theme browser and installer directly in the customizer.

The new flow seamlessly integrates theme management into the customization experience by bringing a new theme browsing framework into the customization interface along with the ability to install and live-preview a theme in a single click.

Screencast demonstrating the new themes experience in the customizer. Open the theme browser, search and browse installed and themes, and then install and preview in a single click.

The new theme browser is designed for extensibility. Third-party theme directories are encouraged to integrate with the core experience via plugins. Because the new browser is built on the core customize API, extending it is similar to extending any other part of the customization experience. As with every aspect of the customizer, this project approaches extensibiity modularly and in terms of both user and developer experience. The end of this post includes a technical overview of the new API.

Since WordPress 4.2, the customizer has loaded information about every installed theme every time the customizer loaded. In 4.9, theme data will only be loaded when a user visits the themes panel. The resulting performance improvement on every customizer load may be substantial on sites with a large number of installed themes, particularly on multisite networks.

For more information on the history and goals of this project, check out the original feature proposal from last year:

Feature Proposal: A New Experience for Discovering, Installing, and Previewing Themes in the Customizer

Theme Browsing Improvements

The changes since the previous merge proposal center around the experience of browsing themes. The customize sidebar now serves as the global index of theme sources (installed,, upload, and any additional sources added by plugins). In the customize preview area, a filter bar controls navigation within each of those sections.

Installed themes feature an instant search filter and a count of the results. themes also feature a search bar, in addition to the feature filter found in wp-admin and on Rather than mimicking the existing and wp-admin theme browsers, the customizer features a simplified filter-oriented approach. The popular, favorites, and (randomized) featured sections are excluded in favor of a single, filter-driven section that defaults to showing the latest themes.

Over time, the customization team hopes to work with the theme review and meta teams to broadly evaluate the process of finding a theme. This future project will be driven by research into other product discovery experiences, as well as the experiences that third-party plugins build within the customizer for browsing themes from other sources. Ultimately, the goal is to bring an improved and unified theme browsing experience to, wp-admin, and the customizer, complete with new tags and other taxonomies. For now, the theme browser within the customizer is a starting point for a user-driven theme discovery experience.

Customize Themes API

The remainder of this post is dedicated to the technical implementation of the new themes experience, with three objectives: providing an example implementation of the customize API, documenting the feature to assist in future iterations, and introducing the API for extending the experience.

Customize Object Structure

The context for the themes experience is contained with a custom customize panel object, WP_Customize_Themes_Panel in PHP and wp.customize.ThemesPanel in JS. This panel is responsible for:

  • The overall theme browser UI layout
  • Installing themes (via wp.updates)
  • Loading theme previews
  • Updating installed themes from (via wp.updates)
  • Deleting installed themes (via wp.updates)

The custom themes panel object joins WP_Customize_Widgets_Panel and WP_Customize_Menus_Panel as core examples of the intended use for panels – as contexts for distinct features, rather than as generic containers for sections. It should generally not be necessary to modify the core panel object when extending the themes experience.

Theme browsing is done within custom customize section objects, which are instances of WP_Customize_Themes_Section in PHP and wp.customize.ThemesSection in JS. In 4.9, core provides two sections for browsing themes: Installed Themes and Themes. An additional section to Upload Themes will be added in a future release. Themes Sections handle the following:

  • Searching and filtering
  • Loading themes (from wp_prepare_themes_for_js() and, as theme control objects
  • Rendering screenshots for theme controls as they become visible (rather than loading all screenshots as soon as the theme data is loaded, for improved performance)
  • Opening and navigating the theme details modal

The themes section was initially introduced in WordPress 4.2. In 4.9, it has been completely reworked, most notably with the addition of built-in support for loading theme data from

Each theme within the browser is represented with a customize control object, instances of WP_Customize_Theme_Control in PHP and wp.customize.ThemeControl in JS. Theme controls:

  • Display information about a single theme
  • Provide contextual buttons to install, preview, or install and preview themes depending on whether the theme is already installed
  • Contain an internal filter (and sorting) method in JS, which can be used for searching and filtering all theme controls within a section

While the theme control object was also initially introduced in WordPress 4.2, its 4.9 update completely refactors the control to use JS templates for rendering. This facilitates the updated themes section’s ability to quickly load data for large numbers of themes within a fully JS-driven experience. Hundreds of individual theme controls are dynamically created and deleted as users navigate the theme browsing interface, leveraging the customize API’s ability to scale by building on functionality initially introduced in WordPress 4.3 for the menus interface.

Extending the Core Experience

The modular structure of the customize API allows any aspect of the new themes experience to be modified or extended. In most cases, extensions will modify the themes section object, or create custom themes section instances or subclasses.

There are two types of WP_Customize_Themes_Sections: those that load all themes at once and search/filter theme locally (like the core installed section), and those that search and filter themes remotely, replacing every theme control object each time a search or filter changes. This distinction is managed with the filter_type parameter. When this set to local, all themes are assumed to be loaded from Ajax when the section is first loaded, and subsequent searching/filtering is applied to the loaded collection of themes within the section. This is how the core “Installed” section behaves – third-party sources with limited numbers of themes may consider leveraging this implementation. When filter_type is set to remote, searching and filtering always triggers a new remote query via Ajax. The core “” section uses this approach, as it has over 5000 themes to search.

With this parameter, it is theoretically possible to create a plugin that adds an instance of WP_Customize_Themes_Section that browses themes from a third-party source (using a custom action parameter). The customize_load_themes filter facilitates loading themes from third-party sources (or modifying the results of the core sections) within an Ajax call triggered by a themes section. In practice, it may be desirable to create a custom themes section object (subclassing WP_Customize_Themes_Section) to further customize the experience of browsing third-party themes within the customizer.

Additional Information & Next Steps

Most of the work for 4.9 was completed in #37661, with several follow up tickets to polish the feature. In addition to iterating on the theme browsing experience, there are a few improvements that are already planned for future releases:

  • #40278 – Introduce theme uploading in the customizer
  • #42046 – Clarify active and previewed themes
  • #42140 – Improve plurality of the themes count string

Here is the complete design flow for the new theme browser within the customizer, courtesy of @folletto:

Mockups of the user flow through the customize themes experience in 4.9, with additional elements for future releases

Please test the new themes experience in the 4.9 betas and share any feedback or bugs that you find on trac and in the comments.

#4-9, #customize, #dev-notes, #themes

Customizer sidebar width is now variable

A common request for the Customizer has been to grant more room for the controls that appear in its sidebar. Ticket #32296 was created to allow the sidebar pane to be user-resizable with a grabber just like the Dev Tools pane in Chrome can be resized. After a lot of back and forth, the scope was reduced to remove the user-resizable aspect and to instead address a more fundamental issue that the sidebar is exceedingly and unnecessarily narrow on high-resolution displays. So while 4.8 doesn’t include a user-resizable controls pane, there is still a feature plugin for possible future merge. Important to that end, the changes that have been merged begin to introduce the fundamental prerequisite for a variable-width sidebar pane: responsive controls.

As can be seen in [40511] and [40567], no longer should Customizer controls assume that their container will always be 300px wide. Given the new responsive breakpoints for high-resolution screens, the pane can now be between 300px and 600px wide depending on the screen width. And actually this has already been the case on smaller screens (e.g. mobile) where the preview and pane aren’t shown at the same time; it now also applies to very large screens where there is ample space for both the preview and a wider pane.

Custom controls in plugins and themes should utilize alternative approaches to doing layout than using pixel widths. Use of percentage-based widths or flexbox will help ensure that controls will appear properly in larger displays, while also making controls future-compatible when the sidebar width could be user-resizable.

#4-8, #customize, #dev-notes

Customizer Improvements in 4.7

WordPress 4.7 has been the most active release on record for the customize component, with four major feature projects merging and shipping with 4.7 and over 90 tickets closed as fixed. This post summarizes the highlights of the user and developer-facing changes.

4.7 Customizer Feature Projects

Create pages within live preview during site setup

Add new pages while building menus and setting a static front page; outline your site directly in the customizer.

This project began with the ability to create posts and pages direction from the available menu items panel in the customizer, as originally proposed near the end of the 4.6 cycle:

Subsequent changes also added the ability to create new pages when assigning the front page and posts page in the Static Front Page section. Because this is now built into the core dropdown-pages customizer control, themes and plugins can also allow users to create new pages for their options instead of relying on existing content. The homepage sections in Twenty Seventeen include this new allow_addition parameter. Here’s how to register a dropdown-pages control supporting new pages:

$wp_customize->add_control( 'featured_page', array(
	'label'          => __( 'Featured Page', 'mytextdomain' ),
	'section'        => 'theme_options',
	'type'           => 'dropdown-pages',
	'allow_addition' => true, // This allows users to add new pages from this dropdown-pages control.
) );

Additionally, a proposal for term statuses was developed as a first step toward expanding the menus functionality to work for creating and previewing taxonomy terms in a future release (see #37915).

Improvements to the Sliding Panels UI

Customizer navigation is now faster, smoother, and more accessible.

This project tackled a series of tickets focused on improving the usability of the “sliding panels” UI in the customizer controls pane. The first step was to refactor the section and panel markup so that sections and panels are not logically nested. This is the biggest internal change to the UI and has a dedicated post going into the details:

This primary change resolved numerous problems with sections and panels not opening and closing properly, and eliminated situations where navigation to leave a section could become hidden. The next step was making section and panel headers “sticky” so that navigation is easier to access within long sections (such as for a menu); see #34343.

Finally, hover and focus styling for navigation in the customizer has been updated to use the blue-border approach found elsewhere in core, including for the device-preview buttons in the customizer, in #29158. This completes a refresh of the customizer controls pane’s UI design that began in WordPress 4.3 with #31336. The core UI now uses the following consistent UI patterns in the customizer:

  • White background colors are used only to indicate navigation and actionable items (such as inputs)
  • The general #eee background color provides visual contrast against the white elements
  • 1px #ddd borders separate navigational elements from background margins and from each other
  • 15px of spacing is provided between elements where visual separation is desired
  • 4px borders are used on one side of a navigation element to show hover or focus, with a color of #0073aa
  • Customizer text uses color: #555d66, with #0073aa for hover and focus states on navigation elements

Plugins and themes should follow these conventions in any custom customizer UI that they introduce, and inherit core styles wherever possible.

Any custom sections and panels, as well as customizations to the core objects in plugins and themes, should be tested extensively to ensure that they continue functioning as intended with all of these changes in 4.7. It’s particularly important to ensure that things like the use of color match the core conventions so that the user experience is seamless between functionality added by plugins and core.

Customize Changesets (formerly Transactions)

Browse your site and switch themes more seamlessly within the customizer, as your changes automatically persist in the background.

This project rewrote the internals of the customizer preview mechanism to make changes persistent. Each change made to a setting in the customizer is saved to a changeset (a new customize_changeset post type), facilitating future features such as scheduled changes, revisions, or saving and sharing drafted changes. Changesets also open the door to using the customizer to preview Ajax requests, headless sites, and REST API calls for mobile apps. In 4.7, changesets enable switching themes in the customizer without needing to decide between publishing or losing your customizations, as they’re automatically persisted in the background.

For more details on changesets, check out the two dedicated posts:

Custom CSS

Fine-tune your site and take your theme customizations to the next level with custom css in the customizer.

#35395 introduced a user-oriented custom CSS option in the customizer. Now that the base functionality is in place, it will be further enhanced in #38707 in future releases. Read the feature proposal for details on the implementation and why it’s important for core:

There’s also a dedicated post that walks through the process of migrating existing custom CSS options in themes and plugins to the core functionality – be sure to follow those steps if your plugin or theme does custom CSS:

Other Changes with Dedicated Posts

4.7 features several other features deserving special attention. Read the posts for visible edit shortcuts (which expand the functionality of customizer partials), video headers (which extend the custom header feature), and starter content for more information:

Additional User-facing Changes

With over 90 tickets fixed in the customize component in 4.7, we can’t cover everything here. But, here are a few more highlights:

Improved Custom Background Properties UI

#22058 introduces a more comprehensive and more usable custom background properties UI when a custom background is set up. There are now presets to control all of the detailed options at once, and the individual options are presented in a more visual way. Background size and vertical position are also now available as standalone options when using a custom preset.

Theme developers should update their add_theme_support() calls for custom-background to specify the default size, vertical position, and preset to reflect their default background image CSS. Perhaps the most significant improvement here is the ability for users to easily set up fixed full-screen backgrounds – and the ability for themes to make that behavior default if desired.

And even more…

4.7 also:

  • Loads the frontend preview iframe more naturally, eliminating a lot of weirdness with JS running in an unexpected location and ensuring that JS-based routing will work (#30028)
  • Allows the search results page to be previewed, and any forms that use the GET method in general can now be submitted whereas previously they would do nothing when submitted (#20714)
  • Hides edit post links in the customizer by default. Plugins, such as Customize Posts, can restore the links if they make post editing available in the customizer (#38648), although the visible edit shortcuts should generally be used instead.
  • Shows a cursor: not-allowed for mouse users when hovering over external links in the preview, as these can’t be previewed
  • Officially removes support for the customizer in Internet Explorer 8, preventing users of this outdated browser from accessing the customizer at all (#38021)

Additional Developer-oriented Changes

Hue-only Color Picker

#38263 adds a hue-only mode to the Iris color picker, wpColorPicker, and WP_Customize_Color_Control. Built for Twenty Seventeen’s custom colors functionality, the hue-only mode allows users to select a hue and saves the hue degree as a number between 0 and 359. To add a hue-color control:

$wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'colorscheme_hue', array(
	'mode' => 'hue',
	'section' => 'colors',
) ) );

As demonstrated in Twenty Seventeen’s custom colors strategy, the hue-selection strategy opens up a whole new world of possibilities for custom color options in themes. Rather than introducing numerous text and background color options and requiring users to adjust them to ensure that adequate color contrast is provided, themes can consolidate their color options into one or more hue pickers. Then, the corresponding use of hsl colors in CSS allows themes to define color patterns where users customize color hues without impacting the lightness of a color option, thereby preserving the designer’s use of proper contrast between text and background colors, and any use of color lightness for visual hierarchy. Check out the implementation in Twenty Seventeen for inspiration (including instant live preview).

Fix Sections that .cannot-expand

When creating custom customizer sections that, for example, display an external link but don’t actually expand to show section contents, the cannot-expand class can be added to the section title container to prevent JS events and CSS hover/focus styles from being applied. Be sure to also remove the tabindex="0" from the container if you copy the core code since your custom section shouldn’t be focusable if it can’t expand (and any contained links or buttons would be keyboard-focusable automatically). See #37980 for details.

Allow Plugins to do Comprehensive Late Validation of Settings

To account for custom subclasses of WP_Customize_Setting that don’t apply the customize_validate_{{$setting_id}} filter, this filter now will be applied when WP_Customize_Manager::validate_setting_values() is called. This ensures that plugins can add custom validation for every setting. For more, see #37638.


Huge thanks to the 61 people (and counting) receiving props for the 120+ customize component commits in 4.7 (as of RC2): @westonruter, @celloexpressions, @afercia, @sirbrillig, @ryankienstra, @helen, @ocean90, @melchoyce, @bradyvercher, @folletto, @johnbillion, @delawski, @karmatosed, @georgestephanis, @dlh, @curdin, @valendesigns, @mattwiebe, @michaelarestad, @joemcgill, @sstoqnov, @lgedeon, @mihai2u, @coreymcollins, @stubgo, @utkarshpatel, @desrosj, @odysseygate, @johnregan3, @aaroncampbell, @mapk, @iseulde, @mrahmadawais, @vishalkakadiya, @sayedwp, @hugobaeta, @jonathanbardo, @jorbin, @tristangemus, @deltafactory, @kkoppenhaver, @seancjones, @Presskopp, @Mista-Flo, @nikeo, @adamsilverstein, @lukecavanagh, @coffee2code, @peterwilsoncc, @presskopp, @pento, @Kelderic, @sebastian.pisula, @mckernanin, @FolioVision, @MikeHansenMe, @welcher, @cdog, @grapplerulrich, @iamfriendly, @flixos90.


#4-7, #customize, #dev-notes

Video Headers in 4.7

WordPress 4.7 extends the Custom Header feature to introduce support for video.

Video headers are considered decorative elements — like header images, but with motion. With that in mind, they play automatically, loop by default, and don’t have sound. They work best when paired with an image, so they can progressively enhance the experience when video is supported.

Header media UI in the customizer when a theme supports video.

Adding theme support

Adding support for video headers to a theme requires three basic steps:

Registering theme support

Support for video headers can be registered when adding support for custom headers in a theme.

add_theme_support( 'custom-header', array(
 'video' => true,
) );

Adding support this way does a few things:

  • Renames the “Header Image” customizer section to “Header Media.”
  • Registers customizer controls for selecting a video from the media library or entering a URL to a YouTube video.
  • Enables support for Selective Refresh for header images.

Displaying the header

In previous versions of WordPress, generating the image tag manually was the recommended way to display a header image. WordPress 4.4 introduced the_header_image_tag() to take advantage of the responsive image improvements.

In WordPress 4.7, the_custom_header_markup() unifies support for header images and videos and is the recommended method for displaying custom headers.

It prints a div that will contain a header image if one is set in the customizer and will also enqueue the wp-custom-header.js script if a video is set in the customizer. The script determines if the environment supports video, and if so, it will progressively enhance the header by replacing the image with a video.

Styling the play/pause button

When videos are ready to play, the wp-custom-header.js script inserts a button for pausing and playing the video to help improve accessibility. Core does not apply any CSS to the button in order to make it easier for themes to style. Themes should ensure the button is visible, fits within the design, and add icons if desired.

Pause Button
<button type="button" id="wp-custom-header-video-button" class="wp-custom-header-video-button wp-custom-header-video-play">Pause</button>

Play Button
<button type="button" id="wp-custom-header-video-button" class="wp-custom-header-video-button wp-custom-header-video-pause">Play</button>

The text for the button can be modified using the header_video_settings filter.

Styling custom headers

When styling custom headers, it’s important to be aware of the various elements that can be used for header media.

A container div with a wp-custom-header class will always be rendered when a header image or video is available. The div may contain an image, video, or iframe depending on the source of the video, so each of those elements needs to be considered.

The following selectors should be fairly standard for creating responsive headers:

.wp-custom-header iframe,
.wp-custom-header img,
.wp-custom-header video {
	display: block;
	height: auto;
	max-width: 100%;

Accessibility considerations

A button to toggle the play/pause state of the video is automatically rendered to help users who may be distracted or disoriented by motion. Voice assistance is also available using wp.a11y.speak, and like the button text, the strings can be modified using the header_video_settings filter.

Bandwidth considerations

To alleviate concerns about bandwidth, videos are only loaded on the front page for viewports that are at least 900 pixels wide and 500 pixels tall. The maximum file size is also capped at 8MB; however, we strongly encourage smaller files be used whenever possible.

Filtering the front page restriction

By default, videos are only loaded on the front page and only the header image is shown on other pages calling the_custom_header_markup(). Themes that need to display the header video on pages other than the front page can:

  • Define a custom callback for the video-active-callback header argument.
  • Use the is_header_video_active filter.

Testing the environment for video support

Themes may also want to customize the criteria used to determine whether or not a video should be embedded. The header_video_settings filter can be used to modify the minimum viewport width and height.

On the front end, the wp.customHeader.supportsVideo() method can be redefined. For instance, it might be desirable to test the user agent to prevent videos from loading on mobile devices that don’t support autoplay. As browsers introduce bandwidth APIs, it may also be worthwhile to disable video on devices with limited bandwidth.

Selective Refresh enabled by default

When registering support for video headers in a theme, header image settings in the customizer are updated to use the postMessage transport to take advantage of the Selective Refresh API introduced in WordPress 4.5. This ensures header images and videos can be updated in the customizer without refreshing the preview window.

If the_custom_header_markup() template tag isn’t being used, themes will need to update the custom header partial to use a custom render_callback, or change the transport for the header_image and header_image_data settings back to refresh.

Creating custom video handlers

Locally hosted mp4 and mov files, as well as YouTube videos, can be used for video headers by default, but it’s possible to add support for additional sources as well.

The wp-custom-header.js script exports a wp.customHeader.handlers global variable that contains a list of video handlers. Each handler accepts information about the current video to determine if it can process it, and if so, it creates the video and inserts it into the DOM.

Core registers two handlers, one for native video, and one for YouTube videos. Each handler extends a base class exposed at wp.customHeader.BaseVideoHandler and implements a basic interface to make sure all videos receive the same level of support.

In the customizer, there is validation to ensure that local videos are a supported format and file size, and that external video links are to YouTube. This validation needs to be filtered to account for custom handlers, either with the customize_validate_external_header_video and customize_validate_header_video filters to filter the core validation functions, or by changing the validation_callback on the header_video and external_header_video customizer settings. See the documentation on customizer validation for more details.

For an example of registering a custom video handler in a plugin, take a look at how this plugin registers support for Vimeo.

New functions and hooks

  • has_header_video() – Checks whether a header video has been set in the customizer.
  • is_header_video_active() – Checks whether a header video is eligible to be shown for the current request.
  • get_header_video_url() – Retrieve the header video URL. May be a local attachment URL or a URL for an external source.
  • the_header_video_url() – Display the header video URL.
  • has_custom_header() – Checks whether a header image or video is set in the customizer and is available for the current request.
  • get_custom_header_markup() – Retrieve the markup for displaying a custom header image (this does not include video support).
  • the_custom_header_markup() – Display the custom header markup and enqueue a script for rendering video in supported environments.


  • is_header_video_active – Whether a header video should be shown for the current request if available.
  • header_video_settings – Settings that are exported to the wp-custom-header.js script during initial page load and when updating the custom header partial in the customizer preview. The default values are:
    • videoUrl – URL for the selected video.
    • mimeType – MIME type of the selected video.
    • posterUrl – URL for the fallback header image.
    • width – Video width.
    • height – Video height.
    • minWidth – Minimum viewport width to embed a video.
    • minHeight – Minimum viewport height to embed a video.
    • l10n – An array of button text and accessibility strings.

Theme support arguments

When calling add_theme_support( 'custom-header' ), two new arguments are available:

  • video – Registers support for video headers.
  • video-active-callback – Defines a callback used to determine whether a header video should be shown for the current request. Defaults to is_front_page.

#4-7, #custom-header, #customize, #dev-notes, #themes

Extending the Custom CSS editor

With the Custom CSS project merging into WordPress Core, some of y’all may be looking to extend it and do more advanced stuff.  Maybe you help run an existing plugin (like me) that has already provided a Custom CSS input to WordPress core and you’re now looking to migrate that data over.  Or maybe you want to change how it outputs.  Here’s what I’ve found so far in my work converting Jetpack’s Custom CSS module to be an enhancement layer on top of the Core implementation, providing legacy feature parity.

Disclaimer: This is just what I’ve found to be useful so far, the Jetpack update is still a work in progress as I write this.

Data Structure

Core’s data store is in a Custom Post Type named custom_css, and the CSS is stored in the post_content.  It sets up a new post for each theme’s custom css, and only the active theme’s one is used.  There’s no accounting for parent/child themes — it uses the slug from the current stylesheet (child theme) as the post_name; that is, Custom CSS lookups are indexed by the return value of get_stylesheet().  Core does not yet have have a UI for displaying the revisions for changes to Custom CSS or a way display the saved Custom CSS of inactive themes, but revisions are enabled on the post type, so no data is lost until the revision viewer makes its way into core (or the user activates a plugin that provides similar functionality). Follow #31089 for more on revisions in the customizer, for all settings not just for Custom CSS.

Getting The Custom CSS

The generated CSS itself can be gotten via the wp_get_custom_css() function, which just returns the CSS for the current theme as a string. This function is used in the wp_head callback when the CSS is printed into a style tag.  One of the more useful functions in the Core implementation for advanced development is wp_get_custom_css_post( $stylesheet = '' ) — this will return either null or the WP_Post object if the site has any Custom CSS saved for the current site.  If you’re building a custom revision viewer, this will be the post you’ll key off of to fetch the revisions.

Filters on Read and Update

The wp_get_custom_css() function applies a wp_get_custom_css filter to the styles just before they’re returned.  This allows for targeted tweaks such as minifying the output on the front-end before it’s echoed by stripping out excess whitespace or the like.  This filter is not meant for a theme or plugin adding styles to the front-end of the site — for that, consider enqueueing your stylesheet normally and adding any dynamic bits via wp_add_inline_style() — this way it will also handle if a child theme or plugin wants to dequeue the parent stylesheet.

Jetpack has historically provided LESS and Sass (SCSS) preprocessing for our Custom CSS module.  We’re extending the Core implementation via two filters in the WP_Customize_Custom_CSS_Setting class by storing the pre-compiled code in $post->post_content_filtered — so it is versioned correctly, but if the user disables Jetpack, the compiled CSS will still be available in $post->post_content with no data loss for the user.

When implementing a pre-processor extension to the Custom CSS functionality in core you have to do some swapping between the underlying setting value and the value that gets displayed:

  1. Replace the post_content with the post_content_filtered as the initial setting value via the customize_value_custom_css filter.
  2. Add a wp_get_custom_css filter in the customizer preview (when the customize_preview_init action triggers) to compile the value into CSS just-in-time.
  3. Override the default JavaScript live-preview functionality to instead register a partial for the wp-custom-css style element so that whenever the custom CSS is modified it can be re-compiled on the server and rendered via selective refresh.
  4. When the Custom CSS setting is saved in the customizer, send the saved pre-processed value to post_content_filtered and compile the value to store into post_content.

For a standalone example of building a pre-processor, see the Custom SCSS Demo plugin on GitHub.


The Core implementation also is including only very basic sanitization, to the point where it would be dangerous to allow users without unfiltered_html to edit CSS.  If your plugin is adding further sanitization to the saved CSS, you can broaden the user base by remapping the edit_css capability (which Core defaults to unfiltered_html) like so:

add_filter( 'map_meta_cap', 'mycss_map_meta_cap', 20, 2 );
function mycss_map_meta_cap( $caps, $cap ) {
  if ( 'edit_css' === $cap ) {
    $caps = array( 'edit_theme_options' );
  return $caps;

Migrating an Existing option to Core CSS

Does your plugin or theme have a custom CSS option stored as an option or a theme_mod? Consider migrating content from your custom setting to the core functionality and hiding your custom UI. Here’s a general migration script, which can be located where you see fit in the context of your original code:

if ( function_exists( 'wp_update_custom_css_post' ) ) {
	// Migrate any existing theme CSS to the core option added in WordPress 4.7.
	$css = get_theme_mod( 'custom_theme_css' );
	if ( $css ) {
		$core_css = wp_get_custom_css(); // Preserve any CSS already added to the core option.
		$return = wp_update_custom_css_post( $core_css . $css );
		if ( ! is_wp_error( $return ) ) {
			// Remove the old theme_mod, so that the CSS is stored in only one place moving forward.
			remove_theme_mod( 'custom_theme_css' );
} else {
	// Back-compat for WordPress < 4.7.

I hope some of this has been useful to folks interested in diving deeper into modifying the Core Custom CSS editor.  It’s still somewhat early days for the feature, so please reach out in #core-customize on Slack with any unexpected use cases or concerns!

#4-7, #css, #customize, #dev-notes

Visible Edit Shortcuts in the Customizer Preview

#27403 added visible edit shortcuts to the customizer preview, making it easier to see which elements of your site are editable in the customizer and how to edit them. Here’s a demo with Twenty Fifteen (note that the ability to toggle icons off has since been removed):

Implementation: Selective Refresh Partials

Visible edit shortcuts are an extension of the existing “shift-click-to-edit” functionality built into customizer partials. Partials are sections of the front end of the site, in the customizer preview, that are associated with customizer settings. When settings change, their associated partials are selectively refreshed via an Ajax call without reloading the entire preview. Partials are to the customizer preview what controls are to the customizer editing pane: a visual representation of a setting.

Buttons are now injected into partials within the preview to expose this relationship visually and to users of all input modes. However, the role of the customizer preview is to provide an accurate representation of the frontend of the site as it’ll appear once changes are published. Accordingly, visible edit shortcuts pose a challenge as they have the potential to significantly hamper the preview-ability of the preview.

To balance between discoverability and providing an accurate preview, the initial core iteration showed a flash of the buttons when the preview first loads, then hid them. To show the shortcuts, or to toggle them on and off, you could click/tap anywhere in the preview (except on a link or button). Keyboard users had a screen-reader-text button (visible on focus) to toggle the shortcuts on and off. This functionality was removed in [39131] and icons are currently persistently visible when customizing but hidden when the controls are collapsed, and supplemental usability testing validated this decision.

Background & Prior Implementations

Shift-click to edit an element in the customizer preview was first implemented with the widget customizer project in WordPress 3.9. Visual approaches to exposing this functionality were explored, but left for a future release. Selective refresh was also first proposed, and put on hold pending development of the concept.

The first core implementation of selective refresh came with menu management in the customizer in WordPress 4.3. Menus include shift-click-to-edit on a per-menu-item bases, further demonstrating the powerful potential of associating portions of the customizer preview with their associated settings and controls. currently supports a similar feature with visible edit icons in the customizer. This approach serves as the inspiration for the final UI being introduced in core, with additional UX adjustments and a complete rewrite of the implementation to make it compatible with as many themes as possible.

Adding Theme Support

Theme support for this feature is all about supporting selective refresh, which was added in WordPress 4.5. In some cases, a small amount of additional CSS will be required to ensure that the shortcuts are positioned properly. Edit shortcuts will be enabled by default for all themes, but are contingent on themes supporting selective refresh.

Selective Refresh for Widgets

See the post from WordPress 4.5 for adding support for selective refresh for widgets. In most cases, add_theme_support( 'customize-selective-refresh-widgets' ) is the only requirement:

Selective Refresh for Menus

Menus support selective refresh out of the box unless: a custom nav menu walker is used, the echo argument is false, or wp_nav_menu isn’t used. In those cases, you’ll need to add support directly. Some themes may still be missing full support for selective refresh of menus, which has been enabled by default since WordPress 4.3.  Reference the post for details, but note that it predates the core implementation of an API for selective refresh:

Selective Refresh for Custom Options

Custom logo (since 4.5) and header video (since 4.7) support selective refresh automatically if you use the core features via add_theme_support. Other core options such as the site title and tagline or header images can support selective refresh if you register partials for them and set their settings’ transport argument to postMessage. Here’s an example from Twenty Fifteen:

$wp_customize->get_setting( 'blogname' )->transport        = 'postMessage';
$wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage';

$wp_customize->selective_refresh->add_partial( 'blogname', array(
	'selector' => '.site-title a',
	'render_callback' => 'twentyfifteen_customize_partial_blogname',
) );
$wp_customize->selective_refresh->add_partial( 'blogdescription', array(
	'selector' => '.site-description',
	'render_callback' => 'twentyfifteen_customize_partial_blogdescription',
) );

Where the render callbacks call bloginfo( 'name' ); and bloginfo( 'description' ); For more details on adding support for selective refresh for custom theme options, reference the official customizer documentation.

Support in Default Themes

Twenty Eleven through Sixteen support selective refresh as of WordPress 4.5, and also support edit icons for most of their features as a result. Twenty Fourteen and Sixteen require a few very minor positioning tweaks to ensure that all of the icons are visible. This is typical of what most themes could expect needing to add.

Twenty Seventeen will be a great showcase for this new functionality, as the first theme to ship natively with selective refresh support and with visible edit shortcuts. A few additional adjustments in this new theme will ensure that every option can be previewed with selective refresh and provides visible edit shortcuts where appropriate.

Limitations & Future Iterations

The biggest limitation of the current approach is that implementation is entirely dependent on themes supporting it. However, unlike with many other theme-supported features, there is no add_theme_support for visible edit shortcuts. Where themes are already using selective refresh, shortcuts will be available out of the box in WordPress 4.7. To add theme support for edit shortcuts, themes need to add theme support for selective refresh, another newer customizer feature that allows the customizer preview to update granularly. Selective refresh provides superior user experience to the default refresh behavior because the preview context is not lost when changes are made.

Edit shortcuts currently rely on the presence of selective refresh partials for every setting that needs an icon. Some settings may be previewed exclusively with JavaScript via postMessage. Icons also aren’t needed for every option; for example, layout or color options are broader than a specific area of the site, so they aren’t associated with a particular edit icon in the preview. In the future, a structured JavaScript API for partials in the customizer preview could facilitate adding icons to JS-previewed settings that don’t use selective refresh.

Visible edit shortcuts are also the first step toward exploring the potential to edit elements of a site directly within the customizer preview. For this to be fully investigated, it’s imperative that a majority of themes and customizer option support selective refresh so that areas of the preview are associated with the appropriate customizer settings and so that context-disrupting full page reloads can be minimized.

Contributors & Call for Help

@sirbrillig led development of the feature for core based on the equivalent feature on Core props went to @sirbrillig, @mattwiebe, @celloexpressions, @melchoyce, @westonruter, and @afercia. Thanks to everyone who has contributed so far!

Now, your help is needed! Here’s what you can do to make this feature shine in WordPress 4.7:

  • Theme authors: add support for selective refresh to your themes. Start with widgets and make sure it’s working for menus, then make sure you’re using the core custom logo feature. Then, add selective refresh to the site title and tagline, header images, and any custom options with associated regions on the frontend.
  • Theme authors: adjust icon positioning in your theme’s CSS. You can add styles to.customize-partial-icon button to adjust all icons, and scope that to a specific container or even .customize-partial-icon-setting_id to adjust a particular edit icon. Note: these were updated with [39136].
  • Everyone: test edit shortcuts with your current theme with WordPress 4.7 Beta 1 (or newer). Most themes should be able to support them on widgets, menus, and logos with minimal effort. Contact your theme’s developer with any bugs or missing edit icon support, refer them to this post, and ask them to add support for visible edit shortcuts.
  • Everyone: test as many themes as possible and look for anywhere the shortcuts don’t display as expected, or at all. Contact the theme author with your findings, refer them to this post, and ask them to improve support for visible edit shortcuts in their themes.

#4-7, #customize, #dev-notes, #themes

Customize Update 2016-10-23

This is the weekly update post for the customize component. It includes a summary of this week’s meeting, recent commits, and next week’s meeting agenda.

4.7 Feature Proposals & Merges

Three proposal posts have been published and approved since our last update:

All five of the major customize feature projects proposed for WordPress 4.7 have been successfully merged (in order):

Work continues on follow up tickets for many of these projects. Please test everything in the customizer and report any bugs to trac. We still have a few pending enhancements that need to be completed by this Wednesday, 10/26, 4.7 beta 1, sorted by priority:

  1. #27403 Improve UI for linking areas of Customizer preview to corresponding controls (desktop and mobile) – has-patch, needs testing and adjustments
  2. #38263 Color picker: add a hue-only mode – has-patch, needed for Twenty Seventeen
  3. #28536 Add browser history and deep linking for navigation in Customizer preview – needs-patch punted
  4. #38164 Customize: assign static front page and posts page to new pages – has-patch
  5. #37964 Allow customizer controls to be encapsulated by accepting pre-instantiated settings – has-patch, needs adjustments
  6. #22058 Improve custom background properties UI – has-patch
  7. #29158 Customizer UI Design lacks contrast for visual hierarchy and does not match wp-admin – has-commit, has-patch for revisions

Weekly Customize Meetings

Our past two weekly meetings have focused on preparing our projects for commit. We’ll continue our weekly 4.7 meetings into beta and RC pending the volume of customize tickets remaining in the milestone. For this week’s meeting, our priority will be ensuring that the 7 remaining customize enhancements for 4.7, listed above, are committed before beta 1.

Our weekly update posts will continue on a reduced schedule now that the bulk of 4.7 development is complete, and we’ll also continue posting dev notes for changes in 4.7.

Recent Customize Commits

Here are the customize-related commits for the past two weeks:

  • [38765]: Customize: Ensure `customize_validate_{$setting->id}` filters apply on input post_values for WP_Customize_Setting subclasses that neglect to apply the filter themselves.
  • [38766]: Customize: Improve message displayed in widgets panel when there are no widget areas currently displayed in the preview.
  • [38767]: Customize: Show Pages section first and pre-expanded in list of available menu items.
  • [38794]: Customize: Move Pages below Custom Links in available nav menu items panel.
  • [38807]: Customize: Skip triggering initial click on pages section for available nav menu items if already open.
  • [38810]: Customize: Implement customized state persistence with changesets. 
  • [38811]: Customize: Split out link `click.preview` and form `submit.preview` event handlers…
  • [38813]: Customize: Introduce a new experience for discovering, installing, and previewing themes in the customizer.
  • [38829]: Customize: Introduce custom CSS for extending theme styles.
  • [38830]: Customize: Fix unit tests when `twentyfifteeen` is `WP_DEFAULT_THEME` instead of twentyfifteen.
  • [38831]: Customize: Improve the labeling of background and header images in the list mode of the media library.
  • [38837]: Twenty Seventeen: Fix a PHP warning on fresh installs.
  • [38850]: Tests: Prevent Twenty Seventeen from interfering with Customizer tests.
  • [38851]: Tests: Prevent Twenty Seventeen from interfering with Customizer ajax tests.
  • [38853]: Customize: Add sticky headers for panels and sections.
  • [38862]: Customize: Revert part of [38859] which caused sections to get deactivated in the customizer.
  • [38867]: Twenty Seventeen: Add theme support for customize selective refresh.
  • [38881]: Customize: Keep previously uploaded header images in place.

Big thanks to those who contributed to patches committed this week: @johnregan3, @celloexpressions, @folletto, @westonruter, @deltafactory, @coreymcollins, @desrosj, @pento, @delawski, @davidakennedy, @afercia, @karmatosed, @ryankienstra, @valendesigns, @utkarshpatel, @stubgo, @lgedeon, @ocean90, @mihai2u, @dlh, @aaroncampbell, @jonathanbardo, @jorbin.

We’re always looking for more contributors; check out the open customize tickets and swing by #core-customize in Slack to get involved. Tickets in the Future Release milestone will be considered first for 4.8 if they have a patch.

#4-7, #customize

Customize Changesets Technical Design Decisions

2016-11-15: Updated the description for how and when setting changes are written into the changeset, and also how the iframe window is loaded during a refresh when there are pending changeset yet to be written into the changeset. For more see comment. Also included information about storing modifying user with each setting change, among other tweaks.

This is a technical deep-dive into Customize Changesets (#30937), a proposed feature formerly known as Customizer Transactions. Because changesets make some very low-level changes to how the customizer works, I felt it was important for these technical details to be shared concurrently with a 4.7 merge proposal since developers of themes and plugins that extend the customizer heavily will need to be well advised of the changes. As such, again, this is a long and technical post. Consider it a pre-merge dev note.

Feedback on the transactions/changesets idea has been positive although somewhat quiet. After the initial patch and the Customize Snapshots feature plugin, there is now a third iteration on the patch, which is currently in testing and review, and I believe it will be ready for inclusion in WordPress 4.7. I’ll first recap the customizer in general and then explain how changesets will open up a lot of new possibilities for live preview and also fix a lot of defects while we’re at it.

The customizer is WordPress’s framework for doing live previews of any change on your site. Its purpose is to eliminate the “save and surprise” behavior that happens when making changes in the WP admin. All changes you make in the customizer are transient and nothing impacts the live site until you hit the Save & Publish button. A user can feel free to experiment with making changes to their site and be secure in the knowledge that they aren’t going to break their site for visitors since they can preview the impacts of their changes. A very powerful aspect of the customizer is that you can make as many changes to as many settings as you want, including changing the site title, header image, background image, nav menu, widgets, and other aspects of your site, and all of these changes are previewed together and will go live together when published: changes in the customizer are bundled/batched together.

The TL;DR for this post is that customize changesets make changes in the customizer persistent, like autosave drafts. For users, the customizer tab can be closed and re-opened and the changes will persist. Users can make changes to one theme and switch to another in the customizer without losing the changes upon switching. A customizer session can be bookmarked to come back to later or this URL can be shared with someone else to review and make additional changes (the URLs expire after a week without changes). The new APIs make possible many new user-facing features in future releases and feature plugins, including saving drafts, submitting changesets as pending for review, scheduling changes, and more.

Limitations before Changesets (WP≤4.6)

In saying that unsaved changes made in the customizer (before changesets) are transient, I did not mean that they somehow get saved in an actual transient. No, they are much more ephemeral than that. Changes in the customizer currently only exist in your browser’s memory. If you try leaving the customizer with unsaved changes you get prompted with an “Are you sure?” (AYS) dialog because once you leave customize.php all of the changes you made will be permanently lost. This issue is especially painful in the context of switching themes in the current customizer: if you modify the site’s title and static front page and then try switching to a different theme, that AYS dialog will appear and warn you that your changes will be lost (since switching a theme currently requires the customizer reload). Not a great user experience. (See #36485.)

Since none of the changes made in the current customizer are persistent in any way, this also has implications for how changes are previewed. When a full refresh or selective refresh is done in the preview, all of the changed settings have to be sent with each request so that the customized state is available to the preview filters to apply in the response, and as such each request has needed to use the POST method. What this in turn has meant is that links in the preview can’t work normally. When you click a link the URL gets sent via postMessage from the preview to the parent frame and where an Ajax POST request is then made to that URL and the response is then written into an about:blank iframe window via document.write(). This causes a couple issues: JavaScript running in the preview will return /wp-admin/customize.php when looking at window.location as opposed to the expected URL; this largely prevents using JavaScript for URL routing, and it causes problems with JS libraries like jQuery UI (see #23225, #30028). Additionally, any REST API calls to GET data will not include the customized state applied in the responses (without hacky workarounds with _method).

Fundamentals for Changesets

The core concept being introduced in Customize Changesets is that the customized state for a given live preview session should be persistent in the database. Each time the customizer is opened a random UUID is generated to serve as the identifier for the changes in that session: the changeset. When changes are made to settings the values will get sent in an Ajax request to be written into a customize_changeset custom post type whose post_name is the UUID for the customizer session. Once the changes have been written into the changeset post, then any request to WordPress (including to the REST API) can be made with a customize_changeset_uuid query param with the desired UUID and this will result in the customizer being bootstrapped and the changes from that changeset being applied for preview in the response. Changes are written into the changeset post at the AUTOSAVE_INTERVAL or whenever focus is removed from the controls window or at beforeunload.

The data from changesets is designed to be incorporated into the existing customizer API to maximize backwards compatibility. Namely, the way that the customizer obtains the customized state is by making calls to WP_Customize_Manager::unsanitized_post_values(). When there is a changeset associated with the given UUID, then the values from the changeset will serve as the base array that is returned by this method. If there is any $_POST['customized'] in the request, then it will be merged on top of this base array. Likewise, calls to $wp_customize->set_post_value() will also result in the supplied values being merged on top of underlying changeset data.

Since a UUID query param is all that is needed to apply the customized state, this means that the customizer iframe can now just use the frontend preview URL with UUID query param directly in its src attribute and load the iframe window naturally, as opposed to using about:blank with a document.write(). When there are pending changes not yet written into the changeset, then the iframe window is loaded by programmatically submitting a form with a POST method containing the pending customized data. Likewise, the UUID will also get added as a query parameter to customize.php itself via history.replaceState() which means you can freely reload the customizer and your changes will persist.

The customize_changeset post uses the post_content to store the changed values in a JSON-encoded object, with the setting IDs as keys mapping to objects that contain the value and other setting params like type.A submitted setting value will only be written into the changeset if it passes the validation constraints, if the setting is recognized, and if the user can do the capability associated with the setting. An example changeset:

    "blogname": {
        "value": "My Blog",
        "type": "option",
        "user_id": 1
    "twentysixteen::header_textcolor": {
        "value": "blank",
        "type": "theme_mod",
        "user_id": 2

You’ll note that the header_textcolor setting has a theme slug as a prefix. This is done so that changes to the theme mods won’t be carried over from theme to theme when switching between them in a customizer session. If you set the text color in Twenty Sixteen but then switch over to Twenty Seventeen to try changing colors there, you can then switch back to Twenty Sixteen in the same customizer session and the text color you set before the theme switch will be restored. Additionally, when activating a theme in the customizer and there are theme mods for other themes that do not get saved, these theme mods will get stashed in a customize_stashed_theme_mods option to then be restored the next time the theme is previewed in the customizer. Lastly, the user_id for the user who last modified a given setting is stored with the setting’s params in the changeset so that at the time of saving (publishing) the changes into the database the user can be switched-to so that filters (such as Kses) apply as expected.

The customize_changeset posts by default get created with the auto-draft status so that they will get automatically garbage-collected after a week of no modifications. When the Save & Publish button is pressed, a customize_save Ajax request will be sent with a status of publish for that post. When a customize_changeset post transitions to publish, the customizer will hook in to load the values from the changeset post into an active changeset and then iterate over each of the settings and save each one. Upon publishing, a changeset post will by default then get immediately trashed so that it will also get garbage-collected.

Since transitioning a changeset post to the publish status is what causes its setting values to be saved, this means that setting changes can be scheduled (see #28721). Since setting values are only written into the changeset if they pass validation and authorization checks, the values can be saved asynchronously without a user with the required capabilities being logged in. An update to the site title can be scheduled for publishing even though no user with edit_theme_options will be logged-in when WP Cron runs. Here’s how this can be done with WP-CLI:

wp post create \
    --post_type=customize_changeset \
    --post_name=$( uuidgen ) \
    --post_status=future \
    --post_date="2017-01-01 00:00:00" \
    --post_content='{"blogname":{"value":"Happy New Year!"}}'

There is no new UI for changesets being proposed for 4.7 (other than the addition of the changeset_uuid query param) and the removal of the theme switch AYS dialog. (Note that the AYS dialog remains for the customizer otherwise since there is no UI that lists changesets to allow the user to navigate back to that state.) Adding a UI for scheduling changesets to core can be explored in future releases.

Extension Opportunities for Plugins

There are also exciting new APIs that plugins will be able to leverage to create new UIs and extend functionality.

  • The show_ui flag on the custom post type can be turned on so that all changeset posts can be listed. When viewing the edit post screen for given changeset, a metabox can be added to show the contents of the changeset.
  • By adding adding support for revisions to the customize_changeset post type, published changesets will no longer be automatically trashed (and garbage-collected). With this there is automatic revisions and an audit trail for changes made in the customizer (see #31089).
  • A “Save Draft” button can be added next to “Save & Publish” that calls{status: 'draft'}). This will prevent the changeset from being an auto-draft and thus garbage-collected.
  • Users could collaborate together on a single changeset with setting-level concurrency locking.
  • Normally when updating a changeset post, a revision is prevented from being created even when they are enabled. However, when making an update to a changeset via where the status is supplied (such as draft) then a new revision will be made. These revisions can be browsed from the edit post screen. Since the JSON in the post_content is pretty-printed, the diffs are easy to read.
  • The publish_posts meta capability for customize_snapshots post type can be overridden to something other than customize, and for users without the new capability a “Submit” button can be added to the UI to submit a changeset for review via{status: 'pending'}).
  • Changesets can be scheduled for publishing from the customizer by hooking up a button to make a call like to{status: 'future', date: '2017-01-01 00:00:00'}).
  • The customize_changeset post type supports title even though it is not displayed anywhere by default. A plugin could add a “commit message” UI for changesets where there could be a title could be entered in an input field and sent via{status: 'draft', title: 'Add 2017 greeting'}).
  • The can_export flag can be turned on for the registered post type so that customize_changeset posts can be exported.

Several of these features already exist in the Customize Snapshots plugin among several others, but the plugin is currently using its own standalone snapshots API with a customize_snapshot post type rather than re-using what is being proposed in 4.7 for core, as the plugin is a prototype for changesets in core. The plugin will be able to be refactored after changesets are committed to core, allowing a lot of code to be removed from it. I’ve also put together a couple of these UI features in a little Gist plugin.

Selective Refresh and Seamless Refresh

In the original proposal for “transactions”, I said that selective refresh was prerequisite for getting transactions/changesets into core. The reason for this is that the initial patch got rid of the PreviewFrame construct in the customizer JS API since I thought it was no longer necessary. I thought that a refresh should just be invoked simply with location.reload() in the iframe. The problem with full page refreshes via location.reload() is that they are not seamless due to a “flash of reloading content”. However, when working on the latest patch I grew to appreciate the approach of loading a new iframe in the background and hot-swapping with the foreground once loaded. And anyway, I realized there was no need to remove the “seamless refresh” functionality. Creating a new iframe for each change to the previewed URL also prevents the browser history from unexpectedly being amended.

Aside: Now that the iframe is loaded with a regular URL supplied in its src attribute, you can now manually refresh the preview iframe just like you would refresh any other iframe. Just context click (right click) on the preview frame and select “Reload Frame” from the context window.

Frontend Preview

Again, now that the customizer preview is loaded into the iframe via a regular URL, you can grab the value of the iframe[src] (or iframe[data-src] in the case of loading a preview with changes not yet written into the changeset) and actually open it in a separate window altogether and not have the customizer controls pane present at all. When you look at the preview URL from the iframe you’ll see the normal frontend URL you’re previewing along with three additional query parameters such as:

  • customize_changeset_uuid=598cdda1-b785-431d-8c6d-6c421300ed9f
  • customize_theme=twentysixteen
  • customize_messenger_channel=preview-1

Only the first one, customize_changeset_uuid, is required to bootstrap the customizer and preview the state. The second one, customize_theme, must be supplied when previewing a theme switch. Otherwise, it can be omitted. And lastly, when the customize_messenger_channel param is present, the customizer preview will hide the admin bar and yield control of navigation to the parent frame: clicks on links and submitting forms will continue to preventDefault with the intended url destination being sent to the parent frame to populate wp.customize.previewer.previewUrl which then causes the iframe[src] to be set to that URL. Maintaining this method of navigation from the preview is important for compatibility with plugins that add custom params to the URL being previewed by intercepting updates to the previewUrl. When a changeset is published, a new UUID is sent in the response and it is sent to the preview in the saved message and it replaces the old UUID in the URL via history.replaceState(). Also note that calls to history.replaceState() and history.pushState() are wrapped so that the customize state query prams will be injected into whatever url is supplied. Whenever the URL is changed with JavaScript, the new URL will be sent to the parent frame along with the wp.customize.settings for the autofocused panels, sections, and controls. This allows for JS-routed apps to take control over contextual panels, sections, and controls.

When the customize_messenger_channel query param is absent, then links can be clicked and forms submitted without any preventDefault. The admin bar will be shown and the Customize link will include the changeset UUID param. The customizer preview JS will ensure that the customizer state query params above will persist by injecting them into all site links and by adding them as hidden inputs to all site forms. If a link or form points to somewhere outside of the site (and is thus not previewable) then the mouse will get a cursor:not-allowed and wp.a11y.speak() will explain that it is not previewable (see #31517). The customizer preview will keep sending keep-alive messages up to the controls pane parent window so that if a user does end up getting routed to an non-previewable URL, the customizer controls pane will know that the preview is no longer connected and so when a setting with postMessage transport is modified it will intelligently fallback to using refresh instead.

The way that REST API requests get the customized state included is via the jQuery.ajaxPrefilter(). If an Ajax request is made with a URL to somewhere on the site, then the customize state query params will be appended to the URL.

Since changesets can only be modified by an authorized user and since previewing is a read-only operation, the URL for the customize preview including the UUID can be shared with unauthenticated users to preview those changes. Since a UUID is random it serves not only as a unique identifier but it also effectively serves as a secret key so that such customizer previews cannot be guessed. Note that unauthenticated users would still be forbidden from accessing the preview URL if they supply a customize_theme that differs from the active theme, as they do not have the switch_themes capability.

Aside: Since the customizer preview can be loaded by itself in its own window, this opens the door to being able to use the customizer for frontend editing and bootstrapping the customizer while on the frontend.

Another aside: The customizer should be able to point to a headless frontend to preview, as long as the frontend looks for the customize_changeset_uuid query param and passed it along in any REST API calls, while also including the customize-preview.js file.

REST API Endpoints

The initial changesets patch does not include REST API endpoints for the 4.7 release. However, there should be a feature plugin developed that adds endpoints for listing all changesets, inspecting the contents of a changeset, and the other CRUD operations. With these endpoints in place, entirely new customizer interfaces can be developed that have nothing to do with customize.php at all. Namely, mobile apps could create changesets and include the associated UUID in REST API requests for various endpoints to preview changes to the app itself. Some initial read-only endpoints were added to the Customize Snapshots plugin.

Summary of API Changes


  • Added: The WP_Customize_Manager class’s constructor now takes an $args param for passing in the changeset UUID, previewed theme, and messenger channel. This $args param is intended to be used instead of global variables, although the globals will still be read as a fallback for backwards compatibility.
  • Changed: The query param for theme switches has been changed from theme to a prefixed customize_theme. This will reduce the chance of a conflict, although theme will still be read as a fallback.
  • Added: New custom post type: customize_changeset.
  • Changed: Modified semantics of WP_Customize_Manager::save() to handle updating a changeset in addition to the previous behavior of persisting setting values into the database.
  • Added: WP_Customize_Manager::changeset_uuid(), which returns the UUID for the current session.
  • Added: WP_Customize_Manager::changeset_post_id(), which returns the post ID for a given customize_changeset post if one yet exists for the current UUID.
  • Added: WP_Customize_Manager::changeset_data() which returns the array of data stored in the changeset.
  • Changed: WP_Customize_Manager::unsanitized_post_values() now takes parameters for whether to exclude changeset data or post input data. Also, post input data will be ignored by default if the current user cannot customize.
  • Added: customize_changeset_save filter to modify the data being persisted into the changeset.
  • Added: WP_Customize_Manager::publish_changeset_values() which is called when a customize_changeset post transitions to a publish status.
  • Added: Theme mods in a changeset which are not associated with the activated theme will get stored in a customize_stashed_theme_mods option so that they can be restored the next time the inactive theme is previewed.
  • Added: WP_Customize_Manager::is_cross_domain().
  • Added: WP_Customize_Manager::get_allowed_urls().
  • Added: wp_generate_uuid4(), a new global function for generating random UUIDs.
  • Changed: Settings are registered for nav menus and widgets even if the user cannot edit_theme_options so that a changeset containing values for them can be previewed.
  • Deprecated: WP_Customzie_Manager::wp_redirect_status().
  • Deprecated: WP_Customize_Manager::wp_die_handler().
  • Deprecated: WP_Customize_Manager::remove_preview_signature().


  • Added: wp.customize.state('previewerAlive') state containing a boolean indicating whether the preview is sending keep-alive messages.
  • Added: wp.customize.state('saving') state containing a boolean indicating whether is currently making a request.
  • Added: wp.customize.state('changesetStatus') state containing the current post status for the customize_changeset post.
  • Added: wp.customize.requestChangesetUpdate() method which allows a plugin to programmatically push changes into a changeset. This allows additional params to be attached to a given setting in a changeset aside from value, and it also allows settings to be removed from a changeset altogether by sending null as the setting params object.
  • Added: changeset-save event is triggered on wp.customize with the pending changes object being sent to to the server.
  • Added: changeset-saved event is triggered on wp.customize when a changeset update request completes successfully, with a changeset-error event triggered on a failure.
  • Added: changeset-saved message is sent to the preview once a changeset has been saved so that JS can safely make make REST API request with the customized state being available.
  • Added: The millisecond values used in debounce and setInterval calls are now stored in wp.customize.settings.timeouts rather than being hard-coded in the JS.
  • Changed: Seamless refreshes will wait for any pending changeset updates to complete before initiating.
  • Changed: The beforeunload event handler for the AYS dialog now has a customize-confirm namespace.
  • Added: Settings for the changeset are exposed under wp.customize.settings.changeset, including the current UUID and the post status.
  • Moved: wp.customize.utils has been moved from customize-controls to customize-base.
  • Added: wp.customize.utils.parseQueryString() to parse a query string into an object of query params.
  • Fixed: Prevent modified nav menus from initiating selective refresh with each page load when customizer is loaded via HTTPS.
  • Fixed: Links in the preview that are just internal jump links no longer have preventDefault called. See #34142.
  • Added: wp.customize.isLinkPreviewable() to customize-preview which returns whether a given link can be previewed.


The work on transactions/snapshots/changesets has been going on for almost 2 years now. I want to thank Derek Herman (@valendesigns) for a lot of his work on the Customize Snapshots plugin that took the key concepts from the initial transactions proposal and started to combine them with a lot of compelling user-facing features.

I hope that changesets will make the Customize API all the more powerful to build compelling cutting-edge applications in WordPress.

#4-7, #customize, #dev-notes