Adding CMB2 To Your Theme

Intro

If you already know why CMB2 is awesome, please feel free to skip this intro and move onto the Installation section

For many who use WP Rig, adding custom fields to any page, custom post type, etc. is as easy as just adding the Advanced Custom Fields ( ACF ) plugin as a dependency to your theme, and never thinking about writing a line a PHP. Although this might be fine for a situation where this theme is only being used on one site, that you are building, and will never be distributed to the masses, this scenario is very specific. So what if you want to add custom fields without requiring ACF to be installed/activated in WordPress? Why should we care?

Problems with relying to heavily on ACF

  • WordPress will break if you activate you theme before someone installs and activates ACF ( get_field() is not a core WordPress function )
  • Relying on 3rd party plugins in order for your theme to work is not great if you plan to sell or mass distribute your theme
  • You can’t version control your custom fields if the data that controls them resides in the WordPress database

Just to name a few…

So how can we easily get custom fields to be a part of our theme without ACF? Well, first lets understand that custom fields go inside custom meta boxes. So we first need to add a custom meta box to house our custom fields. WordPress does have a page on the codex about how this can be done manually. This is fine if you only need one or two fields total, but once you read that whole page, you’ll notice that there are a few steps and considerations that go into creating these the WP way. It’s not all that simple.

What if your theme is very complex and needs many custom fields across many different custom post types in many custom meta boxes!? One popular approach used by experienced WP developers is to use CMB2! But before we go just using some library we know little about, especially in terms of size/weight, lets answer some questions first.

Should we even use CMB2?

  • Why do we want custom fields?
    • If the answer is to add global configurations or settings to your theme, then checkout our article on using our EZ Customizer class to add custom settings to the WordPress Customizer.
    • If the answer is to add custom fields to pages, posts, or a custom post type included in your theme, then carry on.
  • How many custom fields do we need and what kinds of field controls do we want?
    • If you don’t need more than a handful, then using CMB2 might be a bit overkill and not optimal. CMB2 can be a bit heavy.
    • If you need a lot of custom fields and you want the controls for these fields to be fancy and lots of them, then carry on.

Installation

The proper way to go about this is just like you would add any WordPress sub-framework to your theme when using WP Rig. When adding big logical bodies of PHP to our theme, it’s best to do it using our Component structure in WP Rig. These “Components” can be found in the /inc directory of WP Rig. All WP Rig “Components” are initialized ( Newed ) from the Theme.php file in the /inc directory. So any time we want to add our own custom “Component”, we must add it to the array $components array in Theme.php in the get_default_components() method. So, here we go…

  1. Navigate in your terminal/command line to the /inc directory of WP Rig
  2. Clone the CMB2 repo into the /inc directory
    git clone https://github.com/CMB2/CMB2.git 
  3. Create a Component.php file for your component class in the CMB2 directory that was just added to the /inc directory
  4. Copy and paste the component structure from one of the other components. Here is an example you can copy:
<?php
/**
 * WP_Rig\WP_Rig\CMB2\Component class
 *
 * @package wp_rig
 */

namespace WP_Rig\WP_Rig\CMB2;

use WP_Rig\WP_Rig\Component_Interface;
use function add_action;

/**
 * Class for managing Customizer integration.
 */
class Component implements Component_Interface {

	/**
	 * Gets the unique identifier for the theme component.
	 *
	 * @return string Component slug.
	 */
	public function get_slug() : string {
		return 'CMB2';
	}

	/**
	 * Adds the action and filter hooks to integrate with WordPress.
	 */
	public function initialize() {
		require_once __DIR__ . '/init.php';
		add_action( 'cmb2_admin_init', array( $this, 'cmb2_sample_metaboxes') );
	}

	/**
	 * Create our new metabox and add our fields to it
	 */
	public function cmb2_sample_metaboxes(){
		$cmb = new_cmb2_box( array(
			'id'            => 'test_metabox',
			'title'         => __( 'Test Metabox', 'cmb2' ),
			'object_types'  => array( 'page', ), // Post type
			'context'       => 'normal',
			'priority'      => 'high',
			'show_names'    => true, // Show field names on the left
			'show_in_rest'  => true,
			// 'cmb_styles' => false, // false to disable the CMB stylesheet
			// 'closed'     => true, // Keep the metabox closed by default
		) );

		// Regular text field
		$cmb->add_field( array(
			'name'       => __( 'Test Text', 'cmb2' ),
			'desc'       => __( 'field description (optional)', 'cmb2' ),
			'id'         => 'yourprefix_text',
			'type'       => 'text',
			'show_on_cb' => 'cmb2_hide_if_no_cats'
		) );

	}
	
}

As you can see above, we are hooking into CMB2’s custom init action, then passing a reference to our method that handles the registration of our meta box and associated meta fields. The example above is only utilizing the basic text field, but CMB2 has a vast selection of fields to choose from. See the CMB2 documentation or the example-functions.php file in the CMB2 directory for more example. To learn more about how components work in WP Rig, see our documentation on our architecture.

Some important things to note here:

  • Since our hook is referencing a method (not a basic function), we need to also pass a reference to the class ( $this ) the method is part of in addition to the name of the method itself. This is why we are passing an array for the second argument of the add_action hook instead of the more standard string reference approach. OOP complicates things in that way a bit, but the concise structure is what we are after here (not to mention autoloading all of our class files).
  • We do have to require the main CMB2 library file in order for our class to know what a new_cmb2_box even is.
  • Depending on how many fields you plan on adding, it may be cleaner to not combine the registration of the fields in the same method as the registration of the meta box. Just something to consider.

Now, the last thing we need to do after creating this class is to add our new “Component” to our Theme.php file in the $components array in the get_default_components() method. Like so:

protected function get_default_components() : array {
		$components = array(
			new Localization\Component(),
			new Base_Support\Component(),
			new Editor\Component(),
			new Accessibility\Component(),
			new Image_Sizes\Component(),
			new AMP\Component(),
			new PWA\Component(),
			new Comments\Component(),
			new Nav_Menus\Component(),
			new Sidebars\Component(),
			new Custom_Background\Component(),
			new Custom_Header\Component(),
			new Custom_Logo\Component(),
			new Post_Thumbnails\Component(),
			new Customizer\Component(),
			new Styles\Component(),
			new CMB2\Component(), // <- our new CMB2 component!
		);

		if ( defined( 'JETPACK__VERSION' ) ) {
			$components[] = new Jetpack\Component();
		}

		return $components;
	}

Conclusion

Although this is not quite as simple as installing ACF and configuring your fields in the ACF settings page, we can now rest assured that:

  • Our theme will never break because ACF is not installed or active
  • We can now distribute our theme anywhere without forcing the admin to install ACF
  • Our custom fields can be version controlled with the rest of our code

CMB2 comes with a number of other benefits as well, but I won’t get into detail about that here. See their repo for more info on that.