73

I'd like to offer my custom post type as a Plugin, so that people can use it without touching their theme folder. But custom post type templates -- such as single-movies.php -- reside in the theme folder. Is there a way to get WP to check for a single-movies.php in the plugin's folder? Hooking a function into the Filer Hierarchy? Or get_template_directory(); ?

8 Answers 8

113

You can use single_template filter hook.

/* Filter the single_template with our custom function*/
add_filter('single_template', 'my_custom_template');

function my_custom_template($single) {

    global $post;

    /* Checks for single template by post type */
    if ( $post->post_type == 'POST TYPE NAME' ) {
        if ( file_exists( PLUGIN_PATH . '/Custom_File.php' ) ) {
            return PLUGIN_PATH . '/Custom_File.php';
        }
    }

    return $single;

}
7
  • It's awesome answer. Please share were can I get all the template available hooks like single_template.
    – Gowri
    May 2, 2013 at 18:18
  • 6
    @gowri The filter is called by the get_query_template() function. It's a variable hook, so the first part {$type}_template changes depending on what parameter is passed to that function. See wp-includes/template.php for some common ones
    – shea
    May 7, 2013 at 9:42
  • 1
    This tutorial covers the single_template hook with good explanation. Once you understand the hooks, @shea's comment suggests a simpler approach than @Bainternet's answer, using add_filter( 'single-movies_template', 'my_custom_template' ); as in the linked tutorial. (This question is closed to new answers, so I can't add it on its own.) Sep 26, 2017 at 4:34
  • 3
    You could replace PLUGIN_PATH with plugin_dir_path( __FILE__ ) and then remove the first slash from your file name. The full path would look something like this: return plugin_dir_path( __FILE__ ) . 'Custom_File.php';
    – Frits
    Feb 13, 2018 at 6:49
  • 1
    PLUGIN_PATH threw an error, here's a great post that shows how to do this and it worked for me out of the box: wpsites.net/free-tutorials/…
    – Nathan
    May 2, 2019 at 20:19
39

Updated answer

Cleaner and shorter version.

function load_movie_template( $template ) {
    global $post;

    if ( 'movie' === $post->post_type && locate_template( array( 'single-movie.php' ) ) !== $template ) {
        /*
         * This is a 'movie' post
         * AND a 'single movie template' is not found on
         * theme or child theme directories, so load it
         * from our plugin directory.
         */
        return plugin_dir_path( __FILE__ ) . 'single-movie.php';
    }

    return $template;
}

add_filter( 'single_template', 'load_movie_template' );

Old answer

Added a check for a custom post type specific template in theme folder to @Brainternet answer.

function load_cpt_template($template) {
    global $post;

    // Is this a "my-custom-post-type" post?
    if ($post->post_type == "my-custom-post-type"){

        //Your plugin path 
        $plugin_path = plugin_dir_path( __FILE__ );

        // The name of custom post type single template
        $template_name = 'single-my-custom-post-type.php';

        // A specific single template for my custom post type exists in theme folder? Or it also doesn't exist in my plugin?
        if($template === get_stylesheet_directory() . '/' . $template_name
            || !file_exists($plugin_path . $template_name)) {

            //Then return "single.php" or "single-my-custom-post-type.php" from theme directory.
            return $template;
        }

        // If not, return my plugin custom post type template.
        return $plugin_path . $template_name;
    }

    //This is not my custom post type, do nothing with $template
    return $template;
}
add_filter('single_template', 'load_cpt_template');

Now you can let the plugin users to copy the template from your plugin to their theme to override it.

With this example, templates must be in the root directory of both plugin and theme.

3
  • 1
    Using locate_template instead of get_stylesheet_directory sounds like the cleaner option to me (found here: code.tutsplus.com/tutorials/…).
    – Felix
    Jun 14, 2016 at 18:11
  • 1
    The updated answer didn't work, the accepted answer (or your old answer) works.
    – Edward
    Mar 29, 2018 at 14:43
  • 2
    You are right, @Edward, that was an horrendous update. I updated it again with Felix suggestion. Also this time I tested the code before posting it :)
    – campsjos
    Apr 3, 2018 at 15:40
5

I would like to point out that when you are using the filter method for this, it is extremely important to prioritize the filter like so:

add_filter('single_template', 'my_custom_template', 99);

If you don't do this, sometimes WP will attempt to recheck after this filter. Was pulling my hair out because of this for like 2 hours.

1

In my own plugins I usually use locate_template() which makes it possible to override the plugins template by copying it into the current theme:

function templateInclude(string $template): string
{
    if (is_single() && get_query_var('post_type') === 'custom_post_type') {
        $templates = [
            'single-custom_post.php',
            'templates/single-custom_post.php'
        ];
        $template = locate_template($templates);
        if (!$template) {
            $template = __DIR__ . '/templates/single-custom_post.php';
        }
    }
    return $template;
}

add_filter('template_include', 'templateInclude');

Now the user can override the plugins template by copying it from the plugin folder templates/single-custom_post.php and place it either directly in the root or under a templates folder in the active theme.

I guess you should be able to use the same approach when using the single_template filter as well.

0

There is a much better way to do it that I am using for my plugins.

As @campsjos mention here, instead of file existence check, you may check locate_template that will check into the theme overrode template file. Which is quite obvious.

function my_plugin_templates() {
    if (is_singular('movie')) {
        if (file_exists($this->template_dir . 'single-movie.php')) {
            return $this->template_dir . 'single-movie.php';
        }
    }
}

Use template_include filter hook to load above code.

add_filter('template_include' , 'my_plugin_templates');
0

thanks to this page i was able to solve the same question for me.

For reference, this is what i ended up with:

function pluginName_myposttype_single_template($single_template) {
  $myposttype_template = PLUGIN_DIR . 'templates/single-myposttype.php';
  return get_post_type() === 'myposttype' && file_exists($myposttype_template) ? $myposttype_template : $single_template;
}
add_filter('single_template', 'pluginName_myposttype_single_template');

the {$type}_template filter hook did not work for me

function pluginName_myposttype_single_template($single_template) {
  $myposttype_template = PLUGIN_DIR . 'templates/single-myposttype.php';

  if ( file_exists($myposttype_template) ) {
    $single_template = $myposttype_template;
  }
  return $single_template;
}
add_filter('single-myposttype_template', 'pluginName_myposttype_single_template');
0

This work for me, kindly try it, Thanks
Templates is loaded into cpt file, which was located at
custom_plugin -> app -> cpt -> cpt_article.php
Template is located
custom_plugin -> app -> templates

add_filter( 'single_template', 'load_my_custom_template', 99, 1 );
 function load_custom_templates($single_template) {
   global $post;
   if ($post->post_type == 'article' ) {
   $single_template = trailingslashit( plugin_dir_path( __FILE__ ) .'app/templates' ).'single_article.php';
  }
   return $single_template;
}
-2

Above is a great answer, but checking for the $single var allows the template to override a template if one is provided by the theme/child theme.

/* Filter the single_template with our custom function*/
add_filter('single_template', 'your_cpt_custom_template');

function your_cpt_custom_template( $single ) {
    global $wp_query, $post;
    /* Checks for single template by post type */
    if ( !$single && $post->post_type == 'cpt' ) {
        if( file_exists( plugin_dir_path( __FILE__ ) . 'single-cpt.php' ) )
            return plugin_dir_path( __FILE__ ) . 'single-cpt.php';
    }
    return $single;
}
2
  • 1
    This does not check if single-cpt.php is present in a child or the theme. It only checks against single.php which will always be present in any themes.
    – newpxsn
    Oct 28, 2015 at 20:01
  • That's right, when I wrote this I was using a really bare theme, there has to be a better way! Nov 20, 2015 at 21:19

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