Roots Discourse

Custom post type archive on a custom page template

Heya.

What is the best approach in Sage 9 themes to use a custom page template and display archive of custom post type there?

I could create a WP_Query and some custom pagination (managing in a custom controller for the created page template) but was wondering if it’s perhaps better to just somehow modify the query via filters so that the main loop on a custom page template already acts and displays archive posts with pagination without adding many a lot of perhaps unnecessary custom code.

Any thoughts appreciated

This is seen as the “right” way to achieve what you describe: https://codex.wordpress.org/Plugin_API/Action_Reference/pre_get_posts

The above would alter your main loop so that you don’t have to create a new WP_Query in your template part. If you need more assistance with this hook then stackexchange covers a lot of similar questions

Alternatively you could leverage the Controller to make your data available in your template files, however the controller approach wouldn’t be as efficient as the pre_get_posts approach. More chatter about Controller usage here:

2 Likes

@trainoasis @craigpearson

What’s your custom template for? If its primary purpose is to display a list of your custom posts, the best approach may be to just add an archive-myposttype.blade.php template for this and let WordPress do the querying, etc., for you.

Otherwise, I would almost certainly go the controller route; I don’t think pre_get_posts would be a good option here. If you’re making an independent page, so to speak, and not just a customized archive for the CPT, the page will have its own data that the query fetches. If you use pre_get_posts to hijack the query that is happening when your page is loaded to get your list of posts, it will no longer contain your page’s data. Instead, leave the page’s query alone and use the controller to fetch the posts you need and pass them to your template.

Hey @craigpearson @mmirus

thanks for your answers; some more information if that helps:

It’s for displaying a list of custom posts, yeah, but in a custom way (added filtering etc.). Default archive would be enough with it’s functionalities (since it provides the loop with pagination that I need anyways), but that means you gotta use the archive URL that is given to that specific custom post type, for example /faq. I know I can do a rewrite to get archive page on something like /support/faq (here a list of FAQs), but that’s not a solution for multi-language pages (need to be a ble to translate all slugs).

Also, I want to be able to move the FAQ page to a different parent (change hierarchy) and still show the archive as I’ll prepare it.

Does this make any sense?

Yep that makes perfect sense, I read “on a custom template” in your question title and assumed you’d like the freedom of having a dynamic archive type page for URLs etc.

Using pre_get_posts is likely to be the best solution to allow for dynamic URLs… I don’t think the word hijack is accurate with pre_get_posts, it makes it seem as though using pre_get_posts is categorically wrong. In fact when used correctly you can alter the main query perfectly fine so long as you’re mindful of pagination and deal with any offsets. Maybe pre_get_posts is still recovering from the bad press due to incorrect usage in publicly available themes and plugins all those years ago.

Here’s what I would look into:

  1. Alter the main query with a conditional check of is_page_template in a function, then hook in to pre_get_posts - similar example: https://wordpress.stackexchange.com/questions/64076/pre-get-posts-on-a-page
  2. Deal with pagination and any other offsets - see get_query_var('paged')and https://codex.wordpress.org/Making_Custom_Queries_using_Offset_and_Pagination

The above would get your custom archive page working.

You may also need to access the existing page data. i.e. the page template has content which you need to pull in besides the custom posts. If this is the case you can use the returned post id outside of the loop to grab that data in your template file - this data is available and not destroyed. Due to WPs caching and object creation this will be handled in a performant manner in most cases.

Finally you may also like your newly pulled in custom posts to take on a new URL structure based on the location/slug of your called template. If so you may find this helpful: https://wordpress.stackexchange.com/questions/9870/how-do-you-create-a-virtual-page-in-wordpress/9959#9959

Hey, thanks for your time.

Indeed I tried the pre_get_posts solution, but the page just throws 404 when simply changing the post_type for the main query.

If I use this for example:

/**
 * On template-faq.blade.php custom page template, display FAQ CPT as an archive
 */
add_action( 'pre_get_posts', function($query) {
    if( is_admin() ) {
        return;
    }
    
    if ( is_page_template('views/template-faq.blade.php') && $query->is_main_query() ) {
        $query->set( 'post_type', 'faq' );
    }
    return $query;
});

And yes, CPT is registered as ‘faq’, don’t really know why this fails :slight_smile:

Then, I found this https://wordpress.stackexchange.com/questions/30851/how-to-use-a-custom-post-type-archive-as-front-page and tried it and it just uses index.php to display the data or my custom archive-faq.blade.php when I add it - which would be OK if it worked; unfortunately it just displays 0 results (as if no posts were added).

It seems I ll have to go for a custom WP_query solution there … :slight_smile:

@craigpearson @mmirus wanted to let you know I found a nicer solution perhaps:

Adding simple WP_Query to page template controller:

public function faqs_query() 
    {
        $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
        $query = new WP_Query( array(
            'post_type'     => 'faq',
            'paged' => $paged
        ) );
        return $query;
    }  

Then in my custom page template:

@while($faqs_query->have_posts()) @php($faqs_query->the_post())
  // custom code to display each custom post info
@endwhile

// this is a simple "paginate_links" wrapper
@include('components/pagination', [ 
    'total' => $faqs_query->max_num_pages,
    'current' => max( 1, get_query_var( 'paged' ) ),
])
        
{{ wp_reset_postdata() }}

This works as expected with not much work and it’s pretty clean I think.
It shows my custom post (‘faq’) archive under any page I assign this template to, for example /support/faq/.

But there is one last thing I’d like to make this perfect, which is similar to what @craigpearson already mentioned: rewrites for certain FAQ urls. But not for each faq, since they won’t have separate pages, but for custom taxonomy that is assigned to it.

I’m displaying custom taxonomy (lets call it ‘FAQ categories’) terms on the side and by clicking them I’d like the URL to change to whatever the current page is (support/faq) + term name.
So for example: /support/faq/faq_category_1 would display the same archive page but filtered to show posts for this term only.

I can easily solve this by adding GET param to URL instead of doing rewrites, but it’s much nicer with actual URLs.

Any thoughts on how to accomplish this perhaps not an easy task?

@trainoasis have you looked at the WordPress rewrite docs, like for add_rewrite_rule and add_rewrite_tag?


https://www.pmg.com/blog/a-mostly-complete-guide-to-the-wordpress-rewrite-api/
https://codex.wordpress.org/Rewrite_API/add_rewrite_tag
https://codex.wordpress.org/Rewrite_API/add_rewrite_rule

Strongly recommend looking at Cortex for complicated routing/URLs. Essentially allows you to structure URLs and associated queries however you like.

2 Likes

Thanks @mmirus and @alwaysblank.

@mmirus I do know about rewriting but that didn’t seem as nice when I thought about it, perhaps I need to rethink how I approach things when things get too custom.

On the other hand, @alwaysblank’s Cortex solution might just be what I needed? I do need to dig a bit deeper before actually using it on production though. Is there an example how to properly implement some basic multilingual (using WPML if that’s important) custom routes with Sage? Otherwise I’ll
probably over-engineer things since I’m quite new to Sage :smiley: