Getting WordPress Custom Post Types in the customizer

It’s no surprise I love a good theme. I love it even more when I get a choice of what can go where – extra cookie points if I don’t have to input that information. A while back I wrote out a post about presenting content within themes.

In the course of reviewing so many themes, I noticed one thing in common with many themes. They love trying to control my content, and that is great, as that is what they are meant to do; but when they start creating some content this is where my headache begins. Yes, there is some trivial content like a small span of text but that is very dismissable. What I’m talking about is when I see a theme using textarea when they register a theme setting. This concerns me. This worries me.

Why worry?

This worries me because I can insert a novel in that option. An entire novel if I wanted to. Okay, perhaps not an entire novel but about 4,294,967,295 characters in that theme option. Granted I’m never going to be doing that inside of an option but why would you even give me that option? What happens if I have a recipe that I would like to display? Would I have to copy and paste it or find a hackish way of doing it? I mean you could potentially use shortcodes but then you get an even bigger headache.

As a theme reviewer I try to think about different types of content a user could have. It makes it easy to know what to look for rather than having to rely on having the user ( me in this case ) input all that content or info all over again. Portability. It’s a great thing.

Using the customizer to get a live preview is what drove me to actually writing out this post. Earlier in the day I was trying out a different theme because I’m looking for a grid style. Not for the entire site but only some parts. Sort of a e-commerce if you will. I have the content, I have what I need. I am only looking to display it but when I’m asked to input what I already have over and over I just gave up.

Getting content

One of the things I would love to see more is a dropdown of my content. The customize menu component that was added to core is a great example of this. It gives you a list of all your pages, posts and even other post types that you have registered.

The other day I was talking to a few other theme reviewers and shared a snippet of code. I’ll post a simplified version first and then break it down and expand upon it as we progress.

add_action( 'customize_register', 'jmc_customize' );
function jmc_customize( $wp_customize ) {
	// get list of post types
	// Check if we have any
	// create an associative array for later use
	// register our setting
	// register our control
}

What’s super neat about that little snippet is that it will create a dropdown of post types that are registered. The second part of the snippet would be:

// get our setting and make sure it's not empty
if ( get_theme_mod( 'section-one-type' ) ) {
	// create a new WP_Query
	// create a new loop
	// make sure we wp_reset_postdata()
}

That little snippet would be in the template file of your choosing. What do you say we break it down a little to get an idea of what the code is actually doing, yeah?

What are we doing?

The first thing we do is hook to the customize_register hook and create our function callback. Inside of that function a lot of our logic will reside. We get all the available post types by using get_post_types() and create a variable to contain them if there are any.

// We create a variable to house our post types
$post_types = get_post_types( array(
	'public'   => true,
	'_builtin' => false
), 'object' );

Then we check to see if there are any and if there are we will create our setting and create our control.

// check to make sure we have any and if we don't we won't even register anything
if ( ! empty( $post_types ) ) { // register setting and control }

Now that we have our post types we need to create an associative array to pass our control. These are the options our users will see.

// create empty array to house types and names.
$types = array();
// we loop and assign.
foreach ( $post_types as $type ) {
	// name is the registered name and label is what the user sees.
	$types[ $type->name ] = $type->label;
}

We then register our setting.

// We register our setting.
$wp_customize->add_setting( 'section-one-type',
array(
	// we set a default just in case.
	'default' => key( $post_types ),
	// make sure we validate/sanitize our setting.
	'sanitize_callback' => 'jmc_post_type_validate',
) );

You will notice that I’m using a custom function in order to validate the setting. I called it jmc_post_type_validate and it looks like:

/**
 * Validate that we really are using a registered post type otherwise we return a default set
 * by the registered setting.
 *
 * @param  [type] $value   Value that is passed by customizer.
 * @param  [type] $setting The setting object.
 * @return [type] Return a validated value.
 */
function jmc_post_type_validate( $value, $setting ) {
	// Get a list of available post types.
	$post_types = get_post_types( array(
		'public'   => true,
		'_builtin' => false,
	), 'object' );

	// Check if what the user selected is valid.
	if ( array_key_exists( $value, $post_types ) ) {
		// If it is we return it.
		return $value;
	} else {
		// Otherwise return a default setting.
		return $setting->default;
	}
}

Finally, we create our control.

// register our control.
$wp_customize->add_control( 'section-one-type',
	// we pass our array.
	array(
		// The label for the markup.
		'label'       => __( 'The post type for section one', 'jmc' ),
		// A helpful description that display in the customizer.
		'description' => __( 'Choose what post type you would like to show', 'jmc' ),
		// What setting this control is attached to.
		'settings'    => 'section-one-type',
		// The type of control we want to display.
		'type'        => 'select',
		// We pass our $types associative array we made earlier.
		'choices'     => $types,
		// The section we want this to show on.
		'section'     => 'static_front_page',
	)
);

Super cool, right?

That is great and all but now what about displaying that on the front end? Enter get_metadata() and WP_Query. Yes, a new loop. Even better is that we can now use metadata within post types. How amazing is that?

Take the following for example:

if ( ! get_theme_mod( 'section-one-type' ) === false ) {
	// create a new WP_Query.
	$type = new WP_Query(
		array(
			// use the post type passed in the setting.
			'post_type'      => get_theme_mod( 'section-one-type' ),
			// depending on how many columns we want set it accordingly.
			'posts_per_page' => 3,
		)
	);

	// create our custom loop.
	if ( $type->have_posts() ) {
		while ( $type->have_posts() ) {
			$type->the_post();
			the_title( '<h3>', '</h3>' );
		}
	}
	// because we are nice, we reset the post data not the query.
	wp_reset_postdata();
}

This can be expanded of course but you sort of get the idea. I’ll post the snippet that does include a little more inline documentation in hopes it does help somebody out.

add_action( 'customize_register', 'jmc_customize' );
/**
 * Register our customizer settings for the theme.
 *
 * @method jmc_customize
 * @param  [type] $wp_customize The customizer object.
 */
function jmc_customize( $wp_customize ) {
	// get list of post types.
	$post_types = get_post_types( array(
		'public'   => true,
		'_builtin' => false,
	), 'object' );

	// check if there are types that can be used.
	if ( ! empty( $post_types ) ) {
		// create empty array.
		$types = array();

		// loop over post types.
		foreach ( $post_types as $type ) {
			// name is the registered type and label is what user sees.
			$types[ $type->name ] = $type->label;
		}

		// We register our setting.
		$wp_customize->add_setting( 'section-one-type',
			array(
				// we set a default just in case.
				'default'           => key( $post_types ),
				// make sure we validate/sanitize our setting.
				'sanitize_callback' => 'jmc_post_type_validate',
			)
		);

		// register our control.
		$wp_customize->add_control( 'section-one-type',
			// we pass our array.
			array(
				// The label for the markup.
				'label'       => __( 'The post type for section one', 'jmc' ),
				// A helpful description that display in the customizer.
				'description' => __( 'Choose what post type you would like to show', 'jmc' ),
				// What setting this control is attached to.
				'settings'    => 'section-one-type',
				// The type of control we want to display.
				'type'        => 'select',
				// We pass our $types associative array we made earlier.
				'choices'     => $types,
				// The section we want this to show on.
				'section'     => 'static_front_page',
			)
		);
	}
}

/**
 * Validate that we really are using a registered post type otherwise we return a default set
 * by the registered setting.
 *
 * @param  [type] $value   Value that is passed by customizer.
 * @param  [type] $setting The setting object.
 * @return [type] Return a validated value.
 */
function jmc_post_type_validate( $value, $setting ) {
	// Get a list of available post types.
	$post_types = get_post_types( array( 'public' => true, '_builtin' => false ), 'object' );
	// Check if what the user selected is valid.
	if ( array_key_exists( $value, $post_types ) ) {
		// If it is we return it.
		return $value;
	} else {
		// Otherwise return a default setting.
		return $setting->default;
	}
}

Oh, if you do want to use get_post_meta() you can use it a little something like:

get_post_meta( get_the_ID(), 'subtitle', true );

Published by

Jose

Born in El Salvador. Loves to read, write, draw, paint, build, test, typography, hike, photography, art, design, sewing, and many other things.