From ACF to CMB2

I recently updated a large plugin from ACF (Advanced Custom Fields) to CMB2 (Custom Meta Boxes v2) and I’m glad I did — here’s why!

Advanced Custom Fields has been the one-stop-shop for custom fields for some time and that’s fine and dandy for non-technical people, but I’m certain the poor devs who have to maintain the bloated monstrosities thrust upon them by field-happy site owners would be happier with CMB2.

Why ACF is bad for devs

  1. End users can edit fields: someone could change the identifier and break your implementation.
  2. It encourages a front-end dependency: the get_field( $key ) function adds some convenience with formatted fields but functionally is just a wrapper for get_post_meta( $post_id, $key, true ) and if you’ve made the mistake of buying into it you’ll have some work to do if you ever want to leave.
  3. API is developer-unfriendly: no documentation for writing field definitions by hand (using register_field_group()), the syntax for its parametised array isn’t bad but it’s far too verbose to write by guessing and every time you make a change you have to re-export the code and update your plugin.
  4. Impractical to use as a front-end dependency: you’d have to either check whether ACF is loaded before registering fields or use something like the TGM Plugin Activation library to ensure it’s available. Either way, in one way or another, you’re delegating responsibility for the dependency to the user which is one of those WordPress-specific practices that makes the wider PHP community scratch their heads.
  5. Painful to use as a back-end dependency: there isn’t a clean way to add custom fields without unintended side-effects.

ACF can’t be a true back-end dependency

To illustrate #5, imagine you’ve enabled ACF, configured the fields you like then exported the PHP to register them. Say you want to “do PHP properly”, so you want to use Composer to manage your plugin’s dependencies. You find ACF on WordPress Packagist and think you can load ACF via Composer, put the export snippet in your plugin and insta-win.

But alas, loading ACF in any way adds menu items in the admin area, so you define the constant ACF_LITE to hide them, but what if ACF is enabled as a separate plugin? To solve this, you check if ACF is enabled and define the constant if not. But if ACF is enabled as a separate plugin then ACF_LITE doesn’t get defined, so ACF shows in the admin area with the custom fields from your plugin!

You wanted the fields defined in your plugin to exist only within the scope of your plugin, i.e. to be defined only in code and be hidden from the interface, but nope, sorry, can’t be done. And even then, what’s the point? It’s not as if users can make the fields do anything without changing the theme/plugin anyway.

Why CMB2 is good for devs

  1. It’s inner-workings are completely hidden: editors can change field values but only devs can manage the fields, so clients can’t change a field’s ID and break things because they didn’t fancy its name.
  2. No front-end dependencies: documentation encourages the use of get_post_meta().
  3. API is simple and well-documented: see the CMB2 Wiki.
  4. Super-easy to use as a dependency: register with Composer from WordPress Packagist, add an action and go!
  5. If you don’t fancy using Composer, you can install the CMB2 plugin, although you’d have to roll your own check-it’s-enabled code.

Quick Example

The example below is contrived, but does demonstrate how easy it is to add custom fields as a developer. Remember that there’s not much someone can actually do with custom fields in the front-end, so the back-end interface (by which I mean CMB2’s API) may as well be usable!

composer.json

{
  "repositories": [
    {
      "type": "composer",
      "url": "http://wpackagist.org"
    }
  ],
  "require": {
    "wpackagist-plugin/cmb2": "2.0.*"
  },
  "autoload": {
    "files": [
      "vendor/cmb2/init.php"
    ]
  }
}

plugin.php

// Register metabox
add_action( 'cmb2_init', function () {

// Add metabox
$cmb = new_cmb2_box(array(
	'id'           => 'page_rating',
	'title'        => __( 'Rating', 'text-domain' ),
	'object_types' => array( 'page ),
	'context'      => 'side',
	'priority'     => 'high',
	'show_names'   => false
));

// Add field to metabox
$cmb->add_field( array(
	'name' => __( 'Rating', 'text-domain' ),
	'id'   => '_rating', // Prefix with underscore to hide from Custom Fields
	'type' => 'text',
) );

// Add rating after content
add_filter( 'the_content', function ( $content ) {
	$rating = get_post_meta( get_the_ID(), '_rating', true );
	if ( !empty( $rating ) ) {
		$content .= '<p><b>Rating</b>: ' . esc_html( $rating ) . '</p>';
	}
	return $content;
} );

2 Comments

  1. I just worked with CMB2 for the first time. I was excited when ACF came along (I was using magic-fields at the time) – and I felt that same excitement while reading this article. After using it, I have to say that it’s not for me, at all. If you are a developer that uses WordPress for intermediate sites, ACF does everything you need – and you can have non-nerds help setup the admin. The experience made me LOVE get_field( $key ). Most WordPress sites don’t live THAT long, so I wouldn’t be concerned with the dependency. For me, writing CMB2 or PODS feels about 20 paces closer to hell.

    • Hi Derek, thanks for the alternative perspective! As with most things, it depends, so I’m not at all surprised that ACF will inevitably make sense in some scenarios.

      I think there’s a difference in approach to WordPress that may be a factor here, namely that some people treat WordPress sites as a codebase in themselves (therefore ACF plugin and config is “stateful”, backed-up and changes don’t matter) whereas others, myself included, treat each component (core, each plugin/theme, etc.) as their own codebase and are therefore more concerned with config changes breaking things.

      What’s your view?

Leave a Reply

Your email address will not be published.