Page Template as Custom Post Type Archive

Since WordPress version 4.4 the hook 'theme_page_templates' allows to set arbitrary page templates.

It means that it’s possible to show arbitrary values in the “Page Template” menu of the page edit screen, and when selected, the value will be stored in the page template meta for the page.

This means that you can “automagically” create a page template for any of the CPT’s registered.

Setup page templates

The code would look something like this:

add_action( 'current_screen', function( \WP_Screen $screen ) {
    if( 'page' !== $screen->id || 'post' !== $screen->base ) {
        return;
    }

    // retrieve CPT objects with archive
    $types = get_post_types( ['has_archive' => true], 'objects' );

    // store CPT slug and labels in an array
    $menu = array();
    foreach( $types as $cpt ) {
        $menu[ $cpt->name ] = 'Archive: ' . $cpt->label;
    }

    // merge with page templates
    $menu and add_filter(
        'theme_page_templates',
        function( array $templates ) use ( $menu ) {
            $templates = array_merge( $templates, $menu );
            return $templates;
        }
    );
} );

With this code in place, assuming you have registered a post type resources with has_archive set to true, when you create or edit a page, you’ll see in the “Page Template” dropdown a page template named Archive: Resources.

You can choose that template when creating a page, you want to use to display your resources posts.

Redirect direct page access

Since this page is an archive more than a page, you could use some code to redirect users to the actual CPT’s archive when the page is accessed:

add_action( 'template_redirect', function() {
    if( is_page_template() ) {

        // current page templates
        $template = get_page_template_slug( get_queried_object_id() );

        // array of CPT names, filtering the ones with archives
        $types = get_post_types( array( 'has_archive' => true ), 'names' );

        // if the current template is one of the CPT, redirect
        if( in_array( $template, $types, true ) ) {
            wp_safe_redirect( get_post_type_archive_link( $template ) );
            exit();
        }
    }
} );

Getting page data

You probably want to use content and title from that page when viewing the article.

So, let’s write a function that let’s you get the page for a specific post type.

function get_archive_page( $post_type="" ) {

    // if no CPT given, let's use the current main query to get it
    if( ! $post_type ) {
        global $wp_query;
        $query_var = ( array ) $wp_query->get( 'post_type' );
        $post_type = reset($query_var);
    }

    // if we have a valid post type
    if( post_type_exists( $post_type ) ) {

        // let's query the first page that has a page template
        // named after the the post type
        $posts = get_posts( array(
            'post_type'      => 'page',
            'post_status'    => 'publish',
            'posts_per_page' => 1,
            'orderby'        => 'menu_order',
            'order'          => 'ASC',
            'meta_query' => array( array( 
                'key'   => '_wp_page_template',
                'value' => $post_type
            ) )
        ) );

        // if we have results, return first (and only) post object
        if( $posts ) {
            return reset($posts);
        }
    }
}

You could use the function above to write two additional functions to get the title and the content of the page:

function get_archive_page_title( $post_type="" ) {
    $page = get_archive_page( $post_type );

    return ( $page ) ? get_the_title( $page ) : '';
}    

function get_archive_page_content( $post_type="" ) {
    $page       = get_archive_page( $post_type );
    $content="";

    if( $page ) {
        setup_postdata( $page );
        ob_start();
            the_content();
        $content = ob_get_clean();
        wp_reset_postdata();
    }

    return $content;
}

function the_archive_page_title() {
    echo get_archive_page_title();
}

function the_archive_page_content() {
    echo get_archive_page_content();
}

Function names should make clear what they do.

You could also write functions like get_archive_page_meta() if you need custom fields.

In the archive template

Now, in the archive.php (or in the archive-resources.php) template, you can make use of the above functions to show title and content of the archive page.

It will look something like:

<h1><?php the_archive_page_title(); ?></h1>

<div><?php the_archive_page_content(); ?></div>

<?php
    while( have_posts() ) : the_post();
        // loop stuff here
    endwhile;
?>

This way your CPT archive url will be example.com/resources/ and your single CPT url will be example.com/resources/post-name/ just like you want.

At the same time you’ll be able to write content and title (and custom fields, if you want) specific for the post type.

Also, consider this method is reusable for any post type, you have or might use in the future.

Leave a Comment