1

Today I got a client that wanted custom template for each page and section within. I proposed Laravel custom sh3t, but he wanted WordPress, as it seems more easy to main( not from my experience ).

.. So far so good. I'm a little confused there.

So I decided that my parent entity would be page, so I can apply different template to each page. Then I have a section within this page, which should be not hardcored. User should have the ability to choose section layout ( template ), and remove or reorder sections within the current page. And Finally I have posts as they are the smallest entity in the website. Posts would be rendered as columns like in bootstrap ( col-md-2, col-lg-6 ), etc..

I decided to create a custom post type to use it as a section, but then I read that post types cannot have template, only pages can have ones. This compromised my plan so far and spend 5 hours in researching a solution( no exit ). So I need another strategy for doing this. I need templates for two entities.

Can someone suggest a solution to this problem? ( I will buy you a beer! )

EDIT:

To create my own custom post type, I use plugin in wordpress called 'Custom Post Type UI', of course there's another way, by pasting a short code snippet into your functions.php file, but I'm not covering this here.

5

Typically I wouldn't follow such a strong answer like the one @Milo provided with a post like this. Since I already had this code written for another project I thought I'd share.

The code below does everything @Milo summarized in his answer and I've ported this across projects before with great success.

Here's the cheat sheet for what's going on:

1) Hook into the 'add_meta_boxes' action to render a new custom meta box on the edit screen (except on the native 'page' post type).

2) Hook into the 'admin_menu' action to remove the existing 'Page Attributes' meta box (except on the native 'page' post type).

3) Build out the custom meta box to replace the functionality of the native 'Page Attributes' meta box. This includes fields for defining the PARENT, TEMPLATE and ORDER of any custom post type you've initialized.

4) Hook into the 'save_post' action to save your template selection in the post meta.

5) Hook into the 'single_template' filter to load your custom template instead of the default WordPress template.

Here's is the fully functional copy/paste for you:

/** Custom Post Type Template Selector **/
function cpt_add_meta_boxes() {
    $post_types = get_post_types();
    foreach( $post_types as $ptype ) {
        if ( $ptype !== 'page') {
            add_meta_box( 'cpt-selector', 'Attributes', 'cpt_meta_box', $ptype, 'side', 'core' );
        }
    }
}
add_action( 'add_meta_boxes', 'cpt_add_meta_boxes' );

function cpt_remove_meta_boxes() {
    $post_types = get_post_types();
    foreach( $post_types as $ptype ) {
        if ( $ptype !== 'page') {
            remove_meta_box( 'pageparentdiv', $ptype, 'normal' );
        }
    }
}
add_action( 'admin_menu' , 'cpt_remove_meta_boxes' );

function cpt_meta_box( $post ) {
    $post_meta = get_post_meta( $post->ID );
    $templates = wp_get_theme()->get_page_templates();

    $post_type_object = get_post_type_object($post->post_type);
    if ( $post_type_object->hierarchical ) {
        $dropdown_args = array(
            'post_type'        => $post->post_type,
            'exclude_tree'     => $post->ID,
            'selected'         => $post->post_parent,
            'name'             => 'parent_id',
            'show_option_none' => __('(no parent)'),
            'sort_column'      => 'menu_order, post_title',
            'echo'             => 0,
        );

        $dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post );
        $pages = wp_dropdown_pages( $dropdown_args );

        if ( $pages ) { 
            echo "<p><strong>Parent</strong></p>";
            echo "<label class=\"screen-reader-text\" for=\"parent_id\">Parent</label>";
            echo $pages;
        }
    }

    // Template Selector
    echo "<p><strong>Template</strong></p>";
    echo "<select id=\"cpt-selector\" name=\"_wp_page_template\"><option value=\"default\">Default Template</option>";
    foreach ( $templates as $template_filename => $template_name ) {
        if ( $post->post_type == strstr( $template_filename, '-', true) ) {
            if ( isset($post_meta['_wp_page_template'][0]) && ($post_meta['_wp_page_template'][0] == $template_filename) ) {
                echo "<option value=\"$template_filename\" selected=\"selected\">$template_name</option>";
            } else {
                echo "<option value=\"$template_filename\">$template_name</option>";
            }
        }
    }
    echo "</select>";

    // Page order
    echo "<p><strong>Order</strong></p>";
    echo "<p><label class=\"screen-reader-text\" for=\"menu_order\">Order</label><input name=\"menu_order\" type=\"text\" size=\"4\" id=\"menu_order\" value=\"". esc_attr($post->menu_order) . "\" /></p>";
}

function save_cpt_template_meta_data( $post_id ) {

    if ( isset( $_REQUEST['_wp_page_template'] ) ) {
        update_post_meta( $post_id, '_wp_page_template', $_REQUEST['_wp_page_template'] );
    }
}
add_action( 'save_post' , 'save_cpt_template_meta_data' );

function custom_single_template($template) {
    global $post;

    $post_meta = ( $post ) ? get_post_meta( $post->ID ) : null;
    if ( isset($post_meta['_wp_page_template'][0]) && ( $post_meta['_wp_page_template'][0] != 'default' ) ) {
        $template = get_template_directory() . '/' . $post_meta['_wp_page_template'][0];
    }

    return $template;
}
add_filter( 'single_template', 'custom_single_template' );
/** END Custom Post Type Template Selector **/

The only assumption I've made here is that your templates follow a naming convention best practice of:

posttype-templatename.php

As an example you could define some custom templates for an "Event" custom post type using the following naming convention within your theme:

event-standard.php
event-allday.php
event-recurring.php

This code is smart enough to only allow "event" templates to be applied to the Event post type. In other words, a template called "section-video.php" template would never be visible to the Event post type. That template would instead appear as an option on the "Section" post type.

To remove this feature you simply need to remove the conditional logic from the code above:

if ( $post->post_type == strstr( $template_filename, '-', true) ) {  }
  • For the sake of feature use, I mark your answer as 'Best Answer', becouse it explains pretty smooth 'how to apply template to a custom post type'. And of course gives a pretty useful snippet of code that I can just copy and paste ( the true dream ) into my functions.php. Thank you, dswebsme for posting such a detailed answer. Give me bank account I will buy you a beer! [: Cheers! – Dimitrov Oct 6 '15 at 6:42
  • 1
    Just a small edit, in the last function in this line: $template = get_template_directory() . '/' . $post_meta['_wp_page_template'][0]; instead of using get_template_directory(), use get_stylesheet_directory(), ex: $template = get_stylesheet_directory() . '/' . $post_meta['_wp_page_template'][0]; I am using a child theme and get_template_directory() will retrieve the parent theme directory so if you put your custom template in child theme location the server won't find it will give php error, while get_stylesheet_directory() will return correct directory of template. Nice Code Thanks – Salam El-Banna Aug 4 '16 at 6:38
  • How can I use this to locate template files in the plugin directory? – UncaughtTypeError Jun 21 '18 at 9:25
13

As of WordPress 4.7, support for multiple templates has been given to Custom Post Types.

In order to make a template available for your Custom Post Type, you add this header to meta portion of the template file:

Template Post Type: post, foo, bar 

For example, we'll assume your Custom Post Type is labelled "my_events", and you'd like to make a template called "Fullwidth" available for both Pages and your Custom Post Type.

This:

/**
 * Template Name: Fullwidth
 * 
 * Template Description...
 **/

Becomes This:

/**
 * Template Name: Fullwidth
 * Template Post Type: page, my_events
 * 
 * Template Description...
 **/

More Info: Post Type Templates in 4.7 from WordPress Core

  • Perfect! This is the best answer as of 2017. – Ryan Mar 15 '17 at 21:24
1

Custom post types can have selectable templates, you'll just have to implement them yourself. The way WordPress does it internally with the page post type is by saving a template slug into post meta data, then checking if a value exists there when the template is loaded on the front end according to the hierarchy.

The basic process would be to add a meta box to your custom post type to let users select a template (maybe using get_page_templates to build a list).

The second step is to add a filter to single_template to load the selected template when that object is viewed on the front end.

If you look inside the core files wp-includes/template.php and wp-includes/post-template.php, you can see the code WordPress uses (and where the filter is applied) and adapt it to your needs. get_queried_object will give you the object's post_type within the filter, as well as the ID to enable you to fetch the post's meta data.

EDIT -

Here is an example filter for the post post type that loads whatever is in the meta key my_template (like whatever.php). You can test it by creating a new post and entering a filename under that key using the native Custom Fields meta box. You can modify this for your custom type (change 'post'), and whatever scheme you're using to store and name files.

function wpd_post_type_template( $template ){
    $object = get_queried_object();
    if( ! empty( $object->post_type )
        && 'post' == $object->post_type
        && $slug = get_post_meta( $object->ID, 'my_template', true ) ){
        if( $custom_template = locate_template( $slug, false ) ){
            $template = $custom_template;
        }
    }
    return $template;
}
add_filter( 'single_template', 'wpd_post_type_template' ) ;
  • This initially was my first shot. I implemented the meta box with dropdown of all the available templates inside my theme folder. Now I will research with the clues you gave me. But if you have some tutorial in mind, please feel free to share it here with me. Thank you! – Dimitrov Oct 5 '15 at 17:54
  • @Dimitrov see edit above for filter example. – Milo Oct 5 '15 at 18:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.