CMB2 (Custom Meta Boxes 2) is a popular developer toolkit for creating metaboxes, custom fields, and forms in WordPress. If you’ve been using ACF for custom fields, CMB2 is worth knowing about – it takes a code-first approach that many developers prefer.
In this guide, we’ll walk through how to register metaboxes with CMB2, add fields, display them on the frontend, and compare it to ACF.
What is CMB2 and Why Use It?
CMB stands for Custom Meta Boxes. The CMB2 plugin provides WordPress developers with a toolkit for building metaboxes, custom fields, and forms.
With CMB2, you can easily manage meta fields for posts, taxonomy terms, users, comments, and even create a settings page for your theme. If you take a look at the plugin page, you’ll find the following definition:
“CMB2 is a meta box, custom fields, and forms library for WordPress that will blow your mind”
The plugin was developed by WebDevStudios and has over 200,000 active installations. The latest version is 2.11.0, though it has not been tested with the last few major WordPress releases. Despite that, it continues to work well in practice.
CMB2 is a framework, not a typical plugin. Installing it adds no settings screen or UI. You write PHP code to register metaboxes and fields, typically in your theme’s functions.php or in a custom plugin. This is why some argue it doesn’t belong in the plugin repository – but the good news is it’s there, so you can install and update it like any other plugin.
Register a Custom Meta Box with CMB2
To create a metabox, hook a function to the cmb2_init action that CMB2 provides:
add_action( 'cmb2_init', 'cmb2_add_metabox' );
function cmb2_add_metabox() {
$prefix = '_yourprefix_';
}We defined a $prefix variable to ensure our field IDs are unique and don’t collide with other plugins or themes.
Next, create the metabox using new_cmb2_box():
add_action( 'cmb2_init', 'cmb2_add_metabox' );
function cmb2_add_metabox() {
$prefix = '_yourprefix_';
$cmb = new_cmb2_box( array(
'id' => $prefix . 'metabox',
'title' => __( 'Metabox Title', 'cmb2' ),
'object_types' => array( 'page', 'post' ),
'context' => 'normal',
'priority' => 'default',
) );
}Two things to note about the parameters:
- In line 7, we used the prefix so that the ID of the metabox would be unique.
- In line 9, we defined that this metabox would appear in the content types pages and posts using the parameter
object_types.
CMB2 supports many more box properties. See the full list of box properties in the official documentation.
Add Fields to the Meta Box
With the metabox registered, add fields to it. CMB2 supports over 30 field types. Here is a simple text field:
$cmb->add_field( array(
'name' => __( 'My First Text Field', 'cmb2' ),
'id' => $prefix . 'text_field',
'type' => 'text',
'desc' => __( 'My First Text Field Description', 'cmb2' ),
) );Here is a more complete example with multiple field types (Example A):
add_action( 'cmb2_init', 'cmb2_sample_metaboxes' );
/**
* Define the metabox and field configurations.
*/
function cmb2_sample_metaboxes() {
// Start with an underscore to hide fields from custom fields list
$prefix = '_yourprefix_';
/**
* Initiate the metabox
*/
$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
// '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' => $prefix . 'text',
'type' => 'text',
'show_on_cb' => 'cmb2_hide_if_no_cats', // function should return a bool value
// 'sanitization_cb' => 'my_custom_sanitization', // custom sanitization callback parameter
// 'escape_cb' => 'my_custom_escaping', // custom escaping callback parameter
// 'on_front' => false, // Optionally designate a field to wp-admin only
// 'repeatable' => true,
) );
// URL text field
$cmb->add_field( array(
'name' => __( 'Website URL', 'cmb2' ),
'desc' => __( 'field description (optional)', 'cmb2' ),
'id' => $prefix . 'url',
'type' => 'text_url',
// 'protocols' => array('http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet'), // Array of allowed protocols
// 'repeatable' => true,
) );
// Email text field
$cmb->add_field( array(
'name' => __( 'Test Text Email', 'cmb2' ),
'desc' => __( 'field description (optional)', 'cmb2' ),
'id' => $prefix . 'email',
'type' => 'text_email',
// 'repeatable' => true,
) );
// Add other metaboxes as needed
}You can find all available field types on the CMB2 GitHub page. The plugin also includes an example-functions.php file (view it on GitHub) that demonstrates how to create each field type.
The CMB2 Metabox Code Generator can generate metabox and field code for you, saving time on boilerplate. For a full reference of available field parameters, see the field parameters documentation.
Display CMB2 Fields on the Frontend
Once you’ve registered metaboxes and fields, you’ll want to display the saved values in your theme. Use the standard get_post_meta() function to retrieve the data.
Pass the post ID and the field key (with prefix) as arguments:
<?php
// Grab the metadata from the database
$text = get_post_meta( get_the_ID(), '_yourprefix_text', true );
// Echo the metadata
echo esc_html( $text );
?>For a page template that displays all the fields from Example A above:
<?php
/**
* Example for Single Page Template that shows CMB2 Fields in your Theme.
**/
get_header(); ?>
<div id="primary">
<div id="content" role="main">
<?php while ( have_posts() ) : the_post(); ?>
<?php get_template_part( 'content', 'page' ); ?>
<?php
$text = get_post_meta( get_the_ID(), '_yourprefix_text', true );
$email = get_post_meta( get_the_ID(), '_yourprefix_email', true );
$url = get_post_meta( get_the_ID(), '_yourprefix_url', true );
echo esc_html( $text );
echo is_email( $email );
echo esc_url( $url );
?>
<?php comments_template( '', true ); ?>
<?php endwhile; // end of the loop. ?>
</div><!-- #content -->
</div><!-- #primary -->
<?php get_footer(); ?>ACF vs CMB2: Which Should You Choose?
Both plugins have their strengths. ACF (now owned by WP Engine since 2022) provides a polished admin UI, built-in functions like get_field(), and features such as Flexible Content and Gutenberg Blocks. However, those convenience functions add extra database queries compared to WordPress core functions like get_post_meta().
Another consideration: if you disable ACF, your site may throw errors because the template calls get_field() which no longer exists. The data is still in the database, but it won’t display. You can work around this – see our guides on optimizing ACF database queries and using ACF without frontend dependency.
CMB2 does not have this problem. It stores data using standard WordPress meta functions, so your fields remain accessible even if the plugin is deactivated. Field definitions live in your code (not the database), making them easy to version-control.
The trade-off is clear: ACF is easier to set up and more beginner-friendly, while CMB2 is more developer-oriented and produces leaner, more portable code.
ACF has a much larger user base, more online documentation, and better Gutenberg integration. CMB2 shines when you want full control, minimal overhead, and no plugin lock-in. The right choice depends on your project requirements and workflow.
CMB2 Tips and Tricks
Here are a few useful techniques when working with CMB2:
1. Override Text Strings in Fields
Some CMB2 fields include default text that you can override. For example, a file field has a button labeled “Add or Upload file” by default. Override it with the options parameter:
$cmb->add_field( array(
'name' => 'PDF',
'id' => $prefix . 'pdf',
'type' => 'file',
'options' => array(
'add_upload_file_text' => 'Upload PDF',
),
'query_args' => array(
'type' => 'application/pdf', // Make library only display PDFs.
),
) );Note the options parameter on line 5.
2. Inject Static Content in a Field
CMB2 provides parameters for injecting HTML content around fields:
- before_field
- before_row
- before
- after
- after_row
- after_field
Here is how to use them:
$cmb->add_field( array(
'name' => 'Testing Field Parameters',
'id' => $prefix . 'test_parameters',
'type' => 'text',
'before_row' => '<p>Testing <b>"before_row"</b> parameter</p>',
'before' => '<p>Testing <b>"before"</b> parameter</p>',
'before_field' => '<p>Testing <b>"before_field"</b> parameter</p>',
'after_field' => '<p>Testing <b>"after_field"</b> parameter</p>',
'after' => '<p>Testing <b>"after"</b> parameter</p>',
'after_row' => '<p>Testing <b>"after_row"</b> parameter</p>',
) );The result looks like this:

3. Limit a Text Field to Numbers Only
Use the attributes parameter to restrict input to numbers:
$cmb_demo->add_field( array(
'name' => __( 'My Number Field', 'theme-domain' ),
'desc' => __( 'Numbers only', 'theme-domain' ),
'id' => $prefix . 'number',
'type' => 'text',
'attributes' => array(
'type' => 'number',
'pattern' => 'd*',
),
) );FAQs
Common questions about creating custom metaboxes with CMB2:
post_meta table, so all saved values remain in the database and are still accessible via get_post_meta(). Your frontend templates will continue to work as long as they use core WordPress functions to retrieve the data.init.php.repeatable parameter. CMB2 also supports repeatable field groups, which let you create sets of related fields that can be repeated. Groups are drag-sortable in the admin.object_types parameter to array('options-page') when registering the metabox. CMB2 supports tabbed navigation for options pages that share the same tab_group property. See the options page documentation for details.Summary
CMB2 is a lightweight, code-first toolkit for creating custom metaboxes and fields in WordPress. It stores data using standard WordPress functions, avoids plugin lock-in, and is safe to bundle inside themes or plugins. For the full API reference, field types, and advanced usage, see the official CMB2 documentation.

