Register Meta Boxes for Custom Post Types

by | Sep 8, 2020 | Plugin Development, WordPress | 0 comments

The Mission: To add a custom meta box for a custom post type in WordPress.

For background I’m currently developing a sermon management plugin for churches to better manage and display their sermons and livestreams on their websites. Apart of this new plugins development I need to register a register a new post type called sermons and add some additional meta boxes with details specific to the sermon such as YouTube video link for every sermon.

Options

There are a lot of great options when it comes to registering multiple meta boxes for a custom post type, if you are doing this for a single project I’d recommend checking out Advanced Custom Fields nothing comes close to their feature set and ease of scaffolding your code requirements. However, in product development such as a plugin to sell, you can’t expect the user’s to use ACF so we need a solution that is self-contained CMB2 can be a good solution requiring it in with composer. However, for my use case I wanted to do everything from scratch using native WordPress functions not bundling any existing plugin… that’s where custom meta_boxes come in and it’s surprisingly easier then you might think. The following code demonstrates us registering a post type of Sermon and Registering a field of Video Link.

<?php //phpcs:ignore WordPress.Files.Filename
/**
 * Custom Post Type for Insights
 *
 * @since NEXT
 * @package ConnectedChurch\ConnectedSermons\Sermons\PostType
 */

namespace ConnectedChurch\ConnectedSermons\Sermons\PostType;

/**
 * Custom post type for Sermons Insights
 *
 * @since NEXT
 */
class Sermons {

	/**
	 * Permalink slug for this post type
	 *
	 * @var string $slug Permalink prefix
	 * @since NEXT
	 */
	private $slug = 'cc_sermons';

	/**
	 * Post ID of this post type.
	 *
	 * @var int $post_id Id of Post.
	 * @since NEXT
	 */
	private $post_id;

	/**
	 * Construct
	 *
	 * @since  0.1.0
	 * @author Scott Anderson <[email protected]>
	 */
	public function __construct() {
		$this->hooks();
	}

	/**
	 * Register Hooks
	 *
	 * @author Scott Anderson <[email protected]>
	 * @since  NEXT
	 */
	private function hooks() : void {
		add_action( 'init', [ $this, 'register_post' ] );
		add_action( 'add_meta_boxes', [ $this, 'register_post_meta' ] );
		add_action( 'save_post', [ $this, 'update_meta' ] );
	}

	/**
	 * Post Labels
	 *
	 * @author Scott Anderson <[email protected]>
	 * @since  NEXT
	 * @return array
	 */
	private function post_labels() : array {
		return [
			'name'                  => _x( 'Sermons', 'Post type general name', 'Sermon' ),
			'singular_name'         => _x( 'Sermon', 'Post type singular name', 'Sermon' ),
			'menu_name'             => _x( 'Sermons', 'Admin Menu text', 'Sermon' ),
			'name_admin_bar'        => _x( 'Sermon', 'Add New on Toolbar', 'Sermon' ),
			'add_new'               => __( 'Add New', 'Sermon' ),
			'add_new_item'          => __( 'Add New Sermon', 'Sermon' ),
			'new_item'              => __( 'New Sermon', 'Sermon' ),
			'edit_item'             => __( 'Edit Sermon', 'Sermon' ),
			'view_item'             => __( 'View Sermon', 'Sermon' ),
			'all_items'             => __( 'All Sermons', 'Sermon' ),
			'search_items'          => __( 'Search Sermons', 'Sermon' ),
			'parent_item_colon'     => __( 'Parent Sermons:', 'Sermon' ),
			'not_found'             => __( 'No Sermons found.', 'Sermon' ),
			'not_found_in_trash'    => __( 'No Sermons found in Trash.', 'Sermon' ),
			'featured_image'        => _x( 'Sermon Cover Image', 'Overrides the “Featured Image” phrase for this post type. Added in 4.3', 'Sermon' ),
			'set_featured_image'    => _x( 'Set cover image', 'Overrides the “Set featured image” phrase for this post type. Added in 4.3', 'Sermon' ),
			'remove_featured_image' => _x( 'Remove cover image', 'Overrides the “Remove featured image” phrase for this post type. Added in 4.3', 'Sermon' ),
			'use_featured_image'    => _x( 'Use as cover image', 'Overrides the “Use as featured image” phrase for this post type. Added in 4.3', 'Sermon' ),
			'archives'              => _x( 'Sermon archives', 'The post type archive label used in nav menus. Default “Post Archives”. Added in 4.4', 'Sermon' ),
			'insert_into_item'      => _x( 'Insert into Sermon', 'Overrides the “Insert into post”/”Insert into page” phrase (used when inserting media into a post). Added in 4.4', 'Sermon' ),
			'uploaded_to_this_item' => _x( 'Uploaded to this Sermon', 'Overrides the “Uploaded to this post”/”Uploaded to this page” phrase (used when viewing media attached to a post). Added in 4.4', 'Sermon' ),
			'filter_items_list'     => _x( 'Filter Sermons list', 'Screen reader text for the filter links heading on the post type listing screen. Default “Filter posts list”/”Filter pages list”. Added in 4.4', 'Sermon' ),
			'items_list_navigation' => _x( 'Sermons list navigation', 'Screen reader text for the pagination heading on the post type listing screen. Default “Posts list navigation”/”Pages list navigation”. Added in 4.4', 'Sermon' ),
			'items_list'            => _x( 'Sermons list', 'Screen reader text for the items list heading on the post type listing screen. Default “Posts list”/”Pages list”. Added in 4.4', 'Sermon' ),
		];
	}

	/**
	 * Post Arguments
	 *
	 * @author Scott Anderson <[email protected]>
	 * @since  NEXT
	 * @return array
	 */
	private function post_arguments() : array {
		return [
			'labels'             => $this->post_labels(),
			'description'        => 'Sermon custom post type.',
			'public'             => true,
			'publicly_queryable' => true,
			'show_ui'            => true,
			'show_in_menu'       => true,
			'query_var'          => true,
			'rewrite'            => array( 'slug' => 'sermon' ),
			'capability_type'    => 'post',
			'has_archive'        => true,
			'hierarchical'       => false,
			'menu_position'      => 20,
			'supports'           => array( 'title', 'editor', 'author', 'thumbnail' ),
			'taxonomies'         => array( 'category', 'post_tag' ),
			'show_in_rest'       => true,
		];
	}

	/**
	 * Register Post Type
	 *
	 * @author Scott Anderson <[email protected]>
	 * @since  NEXT
	 */
	public function register_post() : void {
		register_post_type( $this->slug, $this->post_arguments() );
	}


	public function update_meta( $post_id ) : void {
		$this->post_id = $post_id;
		$this->update_video_link();
	}

	private function update_video_link() : void {
		if ( isset( $_POST['ccs_video_link'] ) ) {
			$publish_date = sanitize_text_field( $_POST['ccs_video_link'] );
			update_post_meta( $this->post_id, 'ccs_video_link', $publish_date );
		}
	}


	/**
	 * Register Post Meta Fields.
	 *
	 * @author Scott Anderson <[email protected]>
	 * @since  NEXT
	 */
	public function register_post_meta() : void {
		add_meta_box( 'sermon_media', 'Sermon Media', [ $this, 'sermon_media' ], [ $this->slug ] );
	}


	/**
	 * Renders Sermon Media Details.
	 *
	 * @author Scott Anderson <[email protected]>
	 * @since  NEXT
	 */
	public function sermon_media() {
		?>
			<label for="post_editor">Video Link:</label>
			<input type="text" style='width:80%' name='ccs_video_link' value='<?php echo wp_kses_post( get_post_meta ( get_the_ID(), 'ccs_video_link', true ) ); ?>'></input>
		<?php
	}

}

Breaking It Down

For whatever post type you want to add a custom meta box simply use the add_meta_box hook to register your fields to be displayed.

Note: Ensure the name attribute matches the $_POST parameter you intend to check.

add_action( 'add_meta_boxes', [ $this, 'register_post_meta' ] );

	/**
	 * Register Post Meta Fields.
	 *
	 * @author Scott Anderson <[email protected]>
	 * @since  NEXT
	 */
	public function register_post_meta() : void {
		add_meta_box( 'sermon_media', 'Sermon Media', [ $this, 'sermon_media' ], [ $this->slug ] );
	}

	/**
	 * Renders Sermon Media Details.
	 *
	 * @author Scott Anderson <[email protected]>
	 * @since  NEXT
	 */
	public function sermon_media() {
		?>
			<label for="post_editor">Video Link:</label>
			<input type="text" style='width:80%' name='ccs_video_link' value='<?php echo wp_kses_post( get_post_meta ( get_the_ID(), 'ccs_video_link', true ) ); ?>'></input>
		<?php
	}

Finally to save your new field simply use the hook save_post and update your field accordingly. That’s it!

add_action( 'save_post', [ $this, 'update_meta' ] );

	public function update_meta( $post_id ) : void {
		$this->update_video_link();
	}

	private function update_video_link() : void {
		if ( isset( $_POST['ccs_video_link'] ) ) {
			$publish_date = sanitize_text_field( $_POST['ccs_video_link'] );
			update_post_meta( $this->post_id, 'ccs_video_link', $publish_date );
		}
	}