When it comes to creating a Custom Post Type within a WordPress plugin, there's always the same problem: you need to create a custom single-[cpt_slug].php file in your theme folder if you don't want to use the default single.php file from your theme.
In this post I'd like to cover two aspects of using custom templates. The first step is to show that we can use a custom file contained directly in the plugin itself instead of loading the default single.php, and the second one is how to create your own custom file in your theme folder.
Many plugins like Easy Digital Downloads or Shopp, use this method: the plugin checks if you define a custom template in your theme folder, if it is the case then the file is loaded, otherwise the default plugin template file is loaded. In both cases the theme default single.php file isn't loaded.
Define the Plugin and Its Structure
The very first step is to create a plugin, let's call it "Template Chooser". Create a "template-chooser" folder under /wp-content/plugins/, with the following structure:
Then open the main file template-choose.php and place the following plugin header code:
/* Plugin Name: CPT template Chooser Plugin URL: http://wp.tutsplus.com/ Description: Loads a custom template file instead of the default single.php Version: 0.1 Author: Remi Corson Author URI: http://wp.tutsplus.com/ */
Define the Plugin Constants
Later in the plugin we will need to easily retrieve the plugin URL and its path, that's why we need to define a few constants:
/* |-------------------------------------------------------------------------- | CONSTANTS |-------------------------------------------------------------------------- */ if ( ! defined( 'RC_TC_BASE_FILE' ) ) define( 'RC_TC_BASE_FILE', __FILE__ ); if ( ! defined( 'RC_TC_BASE_DIR' ) ) define( 'RC_TC_BASE_DIR', dirname( RC_TC_BASE_FILE ) ); if ( ! defined( 'RC_TC_PLUGIN_URL' ) ) define( 'RC_TC_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
Register a Custom Post Type
To go further, we have to setup a new custom post type, let's create a "Testimonial" CPT, with some very basic supports and features. As the aim of the post is not to teach how to create a custom post type, I'll use a pretty simple code divided in 3 parts: the custom post type labels, the supports, and the custom post type arguments. All that embed in a single function:
/* |-------------------------------------------------------------------------- | DEFINE THE CUSTOM POST TYPE |-------------------------------------------------------------------------- */ /** * Setup testimonial Custom Post Type * * @since 1.0 */ function rc_tc_setup_post_types() { // Custom Post Type Labels $labels = array( 'name' => esc_html__( 'Testimonials', 'rc_tc' ), 'singular_name' => esc_html__( 'Testimonial', 'rc_tc' ), 'add_new' => esc_html__( 'Add New', 'rc_tc' ), 'add_new_item' => esc_html__( 'Add New Testimonial', 'rc_tc' ), 'edit_item' => esc_html__( 'Edit Testimonial', 'rc_tc' ), 'new_item' => esc_html__( 'New Testimonial', 'rc_tc' ), 'view_item' => esc_html__( 'View Testimonial', 'rc_tc' ), 'search_items' => esc_html__( 'Search Testimonial', 'rc_tc' ), 'not_found' => esc_html__( 'No testimonial found', 'rc_tc' ), 'not_found_in_trash' => esc_html__( 'No testimonial found in trash', 'rc_tc' ), 'parent_item_colon' => '' ); // Supports $supports = array( 'title', 'editor' ); // Custom Post Type Supports $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'query_var' => true, 'can_export' => true, 'rewrite' => array( 'slug' => 'testimonials', 'with_front' => true ), 'capability_type' => 'post', 'hierarchical' => false, 'menu_position' => 25, 'supports' => $supports, 'menu_icon' => RC_TC_PLUGIN_URL . '/includes/images/testimonials_icon.png', // you can set your own icon here ); // Finally register the "testimonial" custom post type register_post_type( 'testimonial' , $args ); } add_action( 'init', 'rc_tc_setup_post_types' );
Do Not Use the Default single.php File
Now that our custom post type is registered, we need to create a function that will tell WordPress not to use the default single.php from the theme.
Because yes by default when displaying a custom post type on the frontend WordPress will check if a file called single-testimonial.php exists and will load it. If not, it will look for the single.php. But we don't want to use any of them.
We want WordPress to load a custom file from the plugin. To do so, we have to hook a new function to the "template_include
" filter. In this function the aim is to check the type of the post and act in consequence:
/* |-------------------------------------------------------------------------- | FILTERS |-------------------------------------------------------------------------- */ add_filter( 'template_include', 'rc_tc_template_chooser'); /* |-------------------------------------------------------------------------- | PLUGIN FUNCTIONS |-------------------------------------------------------------------------- */ /** * Returns template file * * @since 1.0 */ function rc_tc_template_chooser( $template ) { // Post ID $post_id = get_the_ID(); // For all other CPT if ( get_post_type( $post_id ) != 'testimonial' ) { return $template; } // Else use custom template if ( is_single() ) { return rc_tc_get_template_hierarchy( 'single' ); } }
Load the Right Template
As you can see, on line 33 we are calling a new function rc_tc_get_template_hierarchy()
. This is the function that will check if WordPress has to load the custom file from the plugin or the template from the theme folder.
Please note that when I'm talking about the "template from the theme folder", I'm talking about a custom file loaded instead of the single.php.
Let's say you don't want to load the template included in the plugin but create your own custom template, all you have to do is to create a new folder in the theme folder, name it "plugin_template" and within this folder create a single.php file. This will be your new default single.php loaded only for testimonials displayed on the frontend.
Are you still with me? Okay, so let's create the function:
/** * Get the custom template if is set * * @since 1.0 */ function rc_tc_get_template_hierarchy( $template ) { // Get the template slug $template_slug = rtrim( $template, '.php' ); $template = $template_slug . '.php'; // Check if a custom template exists in the theme folder, if not, load the plugin template file if ( $theme_file = locate_template( array( 'plugin_template/' . $template ) ) ) { $file = $theme_file; } else { $file = RC_TC_BASE_DIR . '/includes/templates/' . $template; } return apply_filters( 'rc_repl_template_' . $template, $file ); } /* |-------------------------------------------------------------------------- | FILTERS |-------------------------------------------------------------------------- */ add_filter( 'template_include', 'rc_tc_template_chooser' );
The Plugin Default Template
Now create a new testimonial in the administration. Then open includes/templates/single.php and then copy and paste this simple code:
<?php get_header(); ?> we are in the plugin custom file <?php get_footer(); ?>
If you visualize the testimonial on the frontend you should see the "we are in the plugin custom file". That's what we wanted. But if the plugin template file doesn't fit your needs or if you simply want to create a more personal design, you can create a file in your theme folder.
The Theme Default Template
To create a custom template that does not use the default one from the plugin, you can create a new folder called "plugin_templates" in your theme folder. Create a new file called single.php and place this code:
<?php get_header(); ?> we are in the theme custom file <?php get_footer(); ?>
Conclusion
So what did we do exactly? Well, we created a plugin that registers a custom post type "Testimonial". We achieved functionality to load a custom file stored in the plugin folder instead of the default single.php or single-testimonial.php files from the theme folder. We also managed to load a custom file instead from the theme folder located under "plugin_templates
".
Why is this nice? Because when you create your own plugin you can provide a default template to display the custom post type so you're giving the choice to the final user whether to use their own template or not.
Comments