How to Add Custom Post Types to the Main Query in WordPress

WordPress does not add custom post types to the main query automatically.

Main query

Queries in WordPress are instances of the class WP_Query. The main query is the query responsible for processing each request. This query is created and maintained automatically by WordPress.

During each request, WordPress translates the URL to a set of query variables, configures the main query using these variables, and retrieves the result. The main query is stored in the global variable $wp_the_query.

Queries other than the main query are known as “custom queries” or “secondary queries”.

Problem

WordPress does not add custom post types to the main query by default. This means that if you create an entry of a custom post type, the entry will not appear on the homepage, archive pages, etc.

To verify this issue, register the custom post type Book using the code below, create a new book, and visit the homepage. The book will not appear.

/*
 * Registers the custom post type Book.
 */
function ns_register_book_cpt(){
    
    // See all possible labels in the PHPDoc of the function get_post_type_labels
    $labels = array(
       'name'                  => __('Books', 'ns'),
       'singular_name'         => __('Book', 'ns'),
       'menu_name'             => __('Books', 'ns'),
       'all_items'             => __('All Books', 'ns'),
       'add_new'               => __('Add New', 'ns'),
       'add_new_item'          => __('Add New Book', 'ns'),
       'edit_item'             => __('Edit Book', 'ns'),
       'new_item'              => __('New Book', 'ns'),
       'view_item'             => __('View Book', 'ns'),
       'view_items'            => __('View Books', 'ns'),
       'search_items'          => __('Search Books', 'ns'),
       'not_found'             => __('No books found.', 'ns'),
       'not_found_in_trash'    => __('No books found in Trash.', 'ns'),
       'archives'              => __('Book Archives', 'ns'),
       'filter_items_list'     => __('Filter books list', 'ns'),
       'items_list_navigation' => __('Books list navigation', 'ns'),
       'items_list'            => __('Books list', 'ns')
    );
    
    // See all possible attributes in the PHPDoc of the function register_post_type
    $args = array(
        'label'                 => __('Books', 'ns'),
        'labels'                => $labels, 
        'public'                => true,
        'exclude_from_search'   => false,
        'publicly_queryable'    => true,
        'show_ui'               => true,
        'show_in_nav_menus'     => true,
        'show_in_menu'          => true,
        'show_in_admin_bar'     => true,
        'hierarchical'          => false,
        'supports'              => array('title', 'editor', 'author'), 
        'taxonomies'            => array('post_tag'),
        'has_archive'           => true, 
        'rewrite'               => array('slug' => 'books'),
        'query_var'             => 'book'
    );
    
    register_post_type('ns_book_cpt', $args);
}
add_action('init', 'ns_register_book_cpt', 10, 0);

Solution

Use the 'pre_get_posts' action to add custom post types to the main query. The steps are as follows:

  1. Check that the desired query is being processed
  2. Fill an array with the post types that can appear on the query
  3. Set the 'post_type' query variable

The following code adds the custom post type Book to the main query when the homepage is being displayed

/*
 * Sets the post types that can appear on the homepage.
 */
function ns_set_home_post_types($query){
    
    $is_target_query = 
            !is_admin() && 
            $query->is_main_query() && 
            $query->is_home;

    if($is_target_query){
        $target_types = array('post', 'ns_book_cpt');
        $query->set('post_type', $target_types);
    }
}
add_action('pre_get_posts', 'ns_set_home_post_types', 10, 1);

and the code below adds the custom post type Book to the main query when an archive page other than a post type archive is being displayed. If a post type archive is being displayed does not make sense to alter the 'post_type' query variable.

/*
 * Sets the post types that can appear on archive pages.
 */
function ns_set_archive_post_types($query){
    
    // We cannot use 'is_archive' here because post type archives should not be included
    $is_target_query = 
            !is_admin() && 
            $query->is_main_query() && 
            ($query->is_date || 
            $query->is_author || 
            $query->is_category || 
            $query->is_tag || 
            $query->is_tax); 
            
    if($is_target_query){
        $target_types = array('post', 'ns_book_cpt');
        $query->set('post_type', $target_types);
    }
}
add_action('pre_get_posts', 'ns_set_archive_post_types', 10, 1);

Further reading

I recommend the other tutorials in this series to learn more about post types in WordPress.

Source code

The source code developed in this tutorial is available here.