How to Use a Page for a Custom Post Type Archive with URL Rewrites

It’s hard to edit content on an archive page in WordPress. Create a page to use for your custom post type archive instead.

Custom Post Types are an extremely powerful tool for WordPress developers. They’re a snap to setup and can be used to extend WordPress sites to manage all kinds of data outside of posts. 

However, Custom Post Type archives are limited in their ability to allow customization by the user. SImply put there is no where to edit content on an archive page from the dashboard. This is a problem for theme designs that rely on unique headers and layouts, or even if you want to include a call to action custom to the archive.

Creating the Custom Post Type

First, we’ll need to create our new custom post type. This is easy to do using the register_post_type function by hooking into the 'init' action.

You’ll want to do this code in the functions.php of your theme or a child theme or as a plugin. Doing this as a plugin will keep the data with your site regardless of the theme.

Here’s a look at the full code:

function the_authority_child_register_books() {

    /**
     * Register a custom post type
     *
     * Supplied is a "reasonable" list of defaults
     * @see register_post_type for full list of options for register_post_type
     * @see add_post_type_support for full descriptions of 'supports' options
     * @see get_post_type_capabilities for full list of available fine grained capabilities that are supported
     */
    register_post_type('book', array(
        'labels' => array(
            'name' => __('Books', 'the-authority-child'),
            'singular_name' => __('Book')
        ),
        'description' => __('A library of books'),
        'menu_icon' => 'dashicons-book-alt',
        'public' => true,
        'exclude_from_search' => false,
        'has_archive' => false,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_nav_menus' => true,
        'hierarchical' => false,
        'supports' => array(
            'title',
            'editor',
            'comments',
            'revisions',
            'author',
            'excerpt',
            'thumbnail',
            'custom-fields'
        ),
        'rewrite' => array( 'slug' => 'library/books' ),
    ));
}
add_action('init', 'the_authority_child_register_books');

On line 1 we’re creating a function called the_authority_child_register_books to call our register_post_type function on line 11.

Line 38 is where we connect our functio to the WordPress init action using add_action('init', 'the_authority_child_register_books');.

The rest of the code between lines 12-37 is for setting various details of the custom post type like your labels, icons, and more. For more info on these parameters check the Codex.

The most important paramters for this particular tutorial are has_archive on line 20, and rewrite on line 35.

We’re setting has_archive to false. This tells WordPress that we don’t need a normal archive page. With this set navigating to http://domain.com/post-type-slug/ will result in a 404.

The rewrite parameter allows us to set the URL structure for the single post pages for the post type. In this case using array( 'slug' => 'library/books' ); will make the single page url http://domain.com/library/books/post-title.

I’m using library as that’s the URL for the page I’ll be creating to act as the archive page in the next step.

Creating a Page to Use as the Custom Post Type Loop

Since we won’t be using a typical archive page we’ll need to create a Page Template that uses a custom loop to pull in our custom post type.

Assuming we want the page title and content to behave like our general page.php file we could duplicate the page and name it something like template-library.php1. One thing we’ll need to change is to add the line Template Name: Library to the comments at the top of the page. This tells WordPress this is a custom page template.

<?php
/**
 * Template Name: Library
 *
 * @link https://codex.wordpress.org/Template_Hierarchy
 *
 * @package The Authority
 */

 get_header(); ?>

     <div id="primary" class="content-area-full">
         <main id="main" class="site-main" role="main">

         <?php while ( have_posts() ) : the_post(); ?>

             <?php // Page Content goes here ?>
             <h1><?php the_title(); ?></h1>
             
             <?php the_content(); ?>

         <?php endwhile; // End of the loop. ?>

         </main><!-- #main -->
     </div><!-- #primary -->

 <?php get_footer(); ?>

Now that will work to display our new page title and content, but we also need it to pull in our post type using a custom WP_Query.

The code for the custom query looks like this:

<?php
     // Custom Post Query 
    $args = array (
        'post_type' = 'book',
    );

    $the_query = new WP_Query($args);

    while( $the_query->have_posts() ) : $the_query->the_post();
?>
    
    <!-- Post Content goes here -->
    <div class="library-book">
        <figure><?php the_post_thumbnail(); ?></figure>
        <h2><?php the_title(); ?></h2>
    </div>

<?php endwhile; wp_reset_postdata(); ?>

The only argument I’m passing it is the 'post_type' => 'book' argument which will query only our new custom post type using the post type slug passed to the register_post_type function on line 11 of that particular code.

Put it all together and you’re page template should look like this:

<?php
/**
 * Template Name: Library
 *
 * @link https://codex.wordpress.org/Template_Hierarchy
 *
 * @package The Authority
 */

 get_header(); ?>

     <div id="primary" class="content-area-full">
         <main id="main" class="site-main" role="main">

         <?php while ( have_posts() ) : the_post(); ?>

             <!-- Page Content goes here -->
             <h1><?php the_title(); ?></h1>
             
             <?php the_content(); ?>

         <?php endwhile; // End of the loop. ?>
         
         <?php
             // Custom Post Query 
            $args = array (
                'post_type' = 'book',
            );
        
            $the_query = new WP_Query($args);
        
            while( $the_query->have_posts() ) : $the_query->the_post();
        ?>
            
            <!-- Post Content goes here -->
            <div class="library-book">
                <figure><?php the_post_thumbnail(); ?></figure>
                <h2><?php the_title(); ?></h2>
            </div>
        
        <?php endwhile; wp_reset_postdata(); ?>

         </main><!-- #main -->
     </div><!-- #primary -->

 <?php get_footer(); ?>

Note: You can add this code wherever you would like within your page template. Just make sure to include the wp_reset_postdata() function. This resets the current $post variable to that of the page instead of whichever post your custom query ended on.

Now create a page with the URL of /library using your Library page template and Bam! you’re done. But whatever you do, don’t create a child page under Library at with the slug books. Our post type is using /library/books and creating a page that also wants to use /library/books would throw WordPress into a fit.

If you’ve got any questions feel free to reach out or post in the comments.

  1. If you plan to submit to WordPress.org make sure to use template- instead of page- for custom templates.