Wordpress - How to add custom content template part for a custom post type on main query using a plugin

Background

Unfortunately get_template_part() function doesn't have any suitable filter to achieve what you want. It's possible to use the get_template_part_{$slug} action hook to inject template parts, however, without any change to your theme or a child theme, the original template part will be added anyway. So this way you'll not be able to replace existing template parts.

However, you'll be able to achieve the desired result by following one of the options below:


Option 1: Use the default CSS classes:

If only thing you want is to change the style for the paper custom post type, then you may simply use the generated CSS class by WordPress. Any standard theme in WordPress will generate $post_type and type-{$post_type} CSS classes for the post content. For example, the custom post type paper will have paper & type-paper CSS classes and general post will have post and type-post CSS classes. Also home page will have home CSS class in the body. So to target paper entries in the home page, you may use the following CSS rules:

body.home .type-paper {
    /* Custom CSS for paper entries in the home page */
}

Option 2: Modify CSS classes:

If the default CSS classes are not enough for you, you can also modify (add / remove) CSS classes using the post_class filter hook in your plugin. Like this:

add_filter( 'post_class', 'paper_post_class' );
function paper_post_class( $class ) {
    if ( get_post_type() === 'paper' && is_home() ) {

        // remove these classes
        $remove = array( 'css-class-to-remove', 'another-class-to-remove' );
        $class = array_diff( $class, $remove );

        // add these classes
        $add = array( 'custom-paper', 'my-paper-class' );
        $class = array_merge( $add, $class );
    }
    return $class;
}

This way you'll be able to remove CSS classes you don't want for paper entries and add new CSS classes you want for paper entries without modifying the theme files. Then use those CSS classes to change the style of paper entries as needed.


Option 3: Modify template & template parts

If your desired style change is not possible by only targeting CSS classes, then you may change the template parts from your plugin too. However, since replacing template parts added by get_template_part() is not possible by using hooks, you'll have to change the template in a way so that you may modify the get_template_part() call from within the plugin.

To do that, you may target your pre_get_posts hook function and use template_include filter hook to modify the home page template, like the following:

function add_custom_post_types_to_query( $query ) {
    if ( is_home() && $query->is_main_query() ) {
        $query->set( 'post_type', array( 'post', 'paper' ) );

        // now also change the home page template, but only when this condition matches
        add_filter( 'template_include', 'wpse258844_custom_template', 999 );
    }
}
add_action( 'pre_get_posts', 'add_custom_post_types_to_query' ); 

Then use the following CODE (modify as you need):

// for better file management, use a separate "theme-{$theme_name_slug}" directory within your plugin directory
// so if the active theme name is "OnePress", the directory name will be "theme-onepress"
// This will save you a ton of headache if you change the theme,
// as different themes may use different function calls within the templates, which other themes may not have
define( 'wpse258844_TEMPLATE_DIR', plugin_dir_path( __FILE__ ) . sprintf( 'theme-%s/', sanitize_title( wp_get_theme() ) ) );

function wpse258844_custom_template( $template ) {
    // this file will replace your homepage template file
    $index_paper = wpse258844_TEMPLATE_DIR . 'custom-home.php';

    // file_exists() check may need clearing stat cache if you change file name, delete the file etc.
    // Once you are done, comment out or remove clearstatcache(); in production
    clearstatcache();

    if ( file_exists( $index_paper ) ) {
        $template = $index_paper;
    }
    return $template;
}

function wpse258844_get_template_part( $slug, $name = null ) {
    if( get_post_type() === 'paper' ) {

        // just to be consistant with get_template_part() function
        do_action( "get_template_part_{$slug}", $slug, $name );

        $located = '';
        $name = (string) $name;

        // file_exists() check may need clearing stat cache if you change file name, delete the file etc.
        // Once you are done, comment out or remove clearstatcache(); in production
        clearstatcache();

        if ( '' !== $name && file_exists( wpse258844_TEMPLATE_DIR . "{$slug}-{$name}.php" ) ) {
            $located = wpse258844_TEMPLATE_DIR . "{$slug}-{$name}.php";
        }
        else if ( file_exists( wpse258844_TEMPLATE_DIR . "{$slug}.php" ) ) {
            $located = wpse258844_TEMPLATE_DIR . "{$slug}.php";
        }

        if ( '' != $located ) {
            load_template( $located, false );
            return;
        }
    }

    get_template_part( $slug, $name );
}

Once you have the above CODE in your plugin (read the comments within the CODE):

  1. Create a new directory within you plugin directory to keep theme template files, e.g. theme-onepress. This will help you in the future if you want to test design changes with a different theme (I suppose that's the main purpose of all these mess ;) ).

  2. Within the new theme-onepress directory, create a file named custom-home.php. Copy the CODE of home page template from your theme (may be index.php, or home.php or whatever your theme is using for the home page template).

  3. Now in custom-home.php change all the call of get_template_part to wpse258844_get_template_part. No need to change the parameter, only function name. wpse258844_get_template_part() function in the above CODE is identical to get_template_part() function and falls back to default behaviour if custom template part is not found within your plugin's theme-{$theme_name_slug} (e.g. theme-onepress) directory.

  4. Finally, replace whatever template part file you wanted to replace in your plugin's theme-{$theme_name_slug} directory.

    For example, if the original call was get_template_part( 'template-parts/content', get_post_format() ) and you want to replace content.php template part, then simply place a file named content.php in your plugin's theme-onepress/template-parts directory. That means, the theme-onepress directory will behave like a child theme for template parts - i.e. simple drop-in replacement.