Migrating from CMB to CMB2

The following posts are coming up!

Recent Posts

I didn’t plan to, the first time. I’d inherited a site that I offered to help someone clean up and they had CMB2. They didn’t actually. They had CMB, the original. This was a while ago. Looking at their site, I realized they had one (yes, one) custom meta box, so I removed CMB and coded in that one meta box and called it a day.

Flash forward a while. A long while. I have a site that was mostly built out by someone else, and it worked great except on mobile. After trying to update content on the site on my iPad and getting frustrated to the point of angor, I re-did the theme as Metro Pro (yes, it’s that site), and folded in a lot of the meta boxes into mu-plugins, so we could keep them no matter what the theme.

But again, she’d used CMB. Not CMB2. And since I know CMB2 has a lot more features, I decided to upgrade. Three hours later, I had it done and had it done rather nicely.

Decide how to install CMB2

I did it as an mu-plugin – Look. It’s a library. I have my font library (aaah!) in there as well. This is how I organize things. I don’t want people disabling it on accident, so by having it in my Must Use folder, only people with SSH or Git access can screw with it. This is a protection thing.

I tossed the cmb2 folder in there and whipped up a fast cmb2.php bootstrap file:

if ( file_exists( dirname( __FILE__ ) . '/cmb2/init.php' ) ) {
	require_once dirname( __FILE__ ) . '/cmb2/init.php';
} elseif ( file_exists( dirname( __FILE__ ) . '/CMB2/init.php' ) ) {
	require_once dirname( __FILE__ ) . '/CMB2/init.php';

// Extra Get post options.
function cmb2_get_post_options( $query_args ) {
    $args = wp_parse_args( $query_args, array(
        'post_type'   => 'post',
        'numberposts' => -1,
    ) );
    $posts = get_posts( $args );
    $post_options = array();
    if ( $posts ) {
        foreach ( $posts as $post ) {
          $post_options[ $post->ID ] = $post->post_title;
    return $post_options;

// Handle the CSS for this
function cmb2_site_scripts( $hook ) {
	if ( $hook == 'post.php' || $hook == 'post-new.php' || $hook == 'page-new.php' || $hook == 'page.php' ) {
		wp_register_style( 'cmb-styles', plugins_url('/cmb2.css', __FILE__ ) );
		wp_enqueue_style( 'cmb-styles' );
add_action( 'admin_enqueue_scripts', 'cmb2_site_scripts', 10 );

You’ll notice that’s more than just bootstrapping. This is the other reason I wanted it as an MU plugin. The first part with the require_once is the call for CMB2. I could simplify it since the folder is lowercase, but it’s fine as is. The second part with function cmb2_get_post_options is so that I can use the names of all my posts as options in a dropdown. I didn’t invent this code, it’s from the CMB2 documentation. The last bit of function cmb2_site_scripts is just to make sure my custom CSS gets loaded on the right pages (I wanted to change the layout of some things).

Convert the Calls

This was the weird part. I’d never really used CMB before. CMB2, through various things including reviews, I’m pretty familiar with the general aspects of it, though not all the specific calls. From CMB to CMB2, the major change was that instead of multiple nested arrays with a return, the code was wrapped in a function that had an array, but then it had callbacks.


	$meta_boxes[] = array(
		'id'         => 'chars_metabox',
		'title'      => 'Character Details',
		'pages'      => array( 'post_type_characters', ), // Post type
		'context'    => 'normal',
		'priority'   => 'high',
		'show_names' => true, // Character field names on the left
		'fields'     => array( ... )

	return $meta_boxes;

became this:

	$cmb_characters = new_cmb2_box( array(
		'id'            => 'chars_metabox',
		'title'         => 'Character Details',
		'object_types'  => array( 'post_type_characters', ), // Post type
		'context'       => 'normal',
		'priority'      => 'high',
		'show_names   ' => true, // Show field names on the left
	) );

You can totally see how one transmuted into the other, right?

The biggest change is the 'fields' => array( ... ) section, which is totally missing from the new version. Remaking the fields was trickier since some changed a great deal. And some changed in fantastic ways.

Here’s the original actor name field:

				'name' => 'Actor Name',
				'id'   => $prefix . 'actor',
				'type' => 'text',

And here is the new one:

	$cmb_characters->add_field( array(
		'name'       => 'Actor Name',
		'desc'       => 'Include years (in parens) for multiple actors',
		'id'         => $prefix . 'actor',
		'type'       => 'text',
		'repeatable' => 'true',
	) );

I’ve added in two things. First is the desc (description), which was needed because I was adding in the repeatable field! Sometimes actors are replaced, and with that in mind we’d been using ‘Foo (2013), Bar (2014-2015)’ and so on. But now with repeatable fields we could easily add in a new line for every actor. Problem solved!

Why not hand code?

Because the Fields API is a sack of wet, smelly, rotting, hair. It’s actually worse than the Settings API. I can’t wait for the Fields API plugin to hit release candidate and have a UI built in. Until then, hand coding more than one meta box is a headache. Making three groups with three to eight fields in each with cross dependencies? A nightmare.

Simply put, CMB2 does it well, obviously, and simply. The code is easy to understand and implement. I wish it was in core. It’s around 3 megs, but 2 of them are from translations, so it’s really not as horrible as all that.






2 responses to “Migrating from CMB to CMB2”

  1. Mike Avatar

    And there are some efforts at using it for Settings API too.
    And putting metaboxes on the front-end two too.

    1. Ipstenu (Mika Epstein) Avatar

      @Mike: I’m not sure what I’d use a box for on the front end. The data, sure, I use it all over the place, but an entry box on the front end would have to be for front-end post editing, which I’m still not sold on being a great idea for complex data.

      There’s a reason Excel excels without being pretty 😉

%d bloggers like this: