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.

Permissions

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