Skip to content

Archive Pages

Alex Prokopenko edited this page Aug 30, 2018 · 2 revisions

Building nice Custom Post Archive pages

Altering custom post type archives is a bit trickier than overriding default tags, categories and taxonomies. By default WordPress has a support of post type archive templates like archive-{posttype}.php where you replace the {posttype} portion with the name of your custom post type. All you can place inside is a famous The Loop part.

Now for the hard part. Because custom post types don’t have any type of form in the WordPress backend, it’s impossible to easily add a description to these custom types nor is there a recommended way of storing the data.

Modern web design has some extra elements even on archive pages like visual images, intro text, etc. Furthremore, you need to remember about SEO and easy way to set meta tags for your archive.

Using Page templates

We suggest to use usual Page templates for a custom post type archive page. You get many benefits from it:

  • It's intuitive to a WordPress administrator to search archive page as "Page". Same as for Blog index page, when you configure a static home page option.
  • You can set custom title, URL and featured image. And even write some content inside editor.
  • You can create custom fields for a specific page template and use page data.
  • You can edit your SEO tags for archive like any other page with Yoast SEO plugin for example.

Of course such method has some drawbacks:

In case you set the page slug as your post type slug you can have problems with pagination on your archive page or with correct single post definitions (may load wrong single post)

  • You need to write custom queries in your template*

  • Pagination doesn't work by default and you need to do a trick to make it works*

* Below you will find solution to resolve these issues

Creating a template

According to our template hierarchy if you want to use a standard post type archive it will search for such templates:

/views/
    /{posttype}/
        archive.php    # first prio
        index.php      # if no archive.php found

If you don't use standard archive and turn it off within post type registration - these templates won't be linked to any URL. So we can register a page template with one of these filenames. Usually index is a main template of a section, where you can find all entries.

Let's assume we have a post type product. Then we should register such template

/views/product/index.php

<?php
/**
 * Template Name: Products Archive
 *
 * @var \JustCoded\WP\Framework\Web\View $this
 */
$this->extends( 'layouts/main' );

// Page content The loop.
while ( have_posts() ) :
	the_post();

	$this->include( 'page/_content' );
endwhile;

This will allow us to create a page and select our page template and print title, content and featured image.

Query for custom post type archive

Now if you try to do a usual WP_Query and use standard post navigation functions you will find that pagination is not working at all. It happens because WordPress thinks that you're reading a single page (actually we do, because we use single page to display an archive). And for pagination WordPress looks for content break tags to define does your page splited in several pages.

Once we printed all we need for archive intro we can explain WordPress that this is not a single. To do that you need to overwrite a corresponding property in global $wp_query. And next step is to get $paged variable and pass it to a custom WP_Query.

To separate queries and business logic from a templates we use models. Model base class already have a method to do all actions described about. It called archive_query.

Let's create a model for running our query (remember to replace namespace with your own):

/app/Models/Product.php

<?php
namespace Boilerplate\Theme\Models;

use JustCoded\WP\Framework\Objects\Model;
use Boilerplate\Theme\Post_Type;

/**
 * Example of getting data for archive pages of custom post type
 *
 * @property \WP_Query $query
 */
class Product extends Model {
	/**
	 * Get query to be used in views in the loop
	 *
	 * @return \WP_Query  query object to be used in loop
	 */
	public function get_query() {
		return $this->archive_query( array(
			'post_type'      => Post_Type\Product::$ID,
			'post_status'    => Post_Type\Product::STATUS_PUBLISH,
			'order'          => Post_Type\Product::SORT_DESC,
			'orderby'        => Post_Type\Product::ORDERBY_DATE,
			'posts_per_page' => 4,
		), __METHOD__ );
	}

}

And now we can use it inside our template:

/views/product/index.php

<?php
/**
 * Template Name: Products Archive
 *
 * @var \JustCoded\WP\Framework\Web\View $this
 */
$this->extends( 'layouts/main' );

$model = new Boilerplate\Theme\Models\Product();

// Page content The loop.
while ( have_posts() ) :
	the_post();

	$this->include( 'page/_content' );
endwhile;

// Custom archive The loop.
while ( $model->query->have_posts() ) :
	$model->query->the_post();

	$this->include( 'product/_content' );
endwhile;

// Pagination.
if ( $model->query->max_num_pages > 1 ) : // check if the max number of pages is greater than 1.
?>
	<nav class="pagenav">
		<div class="alignleft">
			<?php echo cpt_prev_posts_link( $model->query, 'Prev Page' ); // display newer posts link. ?>
		</div>
		<div class="alignright">
			<?php echo cpt_next_posts_link( $model->query, 'Next Page' ); // display older posts link. ?>
		</div>
	</nav>
<?php
endif;

Pagination functions

As you can see in the template code above - we used our own functions to print pagination links. Unfortunately, we can't use WordPress standard functions, because they are hard-coded to use global $wp_query. Our custom archive page has single page inside global variable, not a posts loop.

So we created simple functions with same arguments as standard WordPress functions, except a new one to pass a WP_Query object.