Make Two Views of Post Type Archive At Two URLs

Updates 6 Feb 14 to make more specific as requested in comments.

I have a post type, we’ll call it “Projects”, with the slug “projects” for which I’d like to offer two views:

Read More
  1. list view (default)
  2. map view

I’d like those views to be at the following permalinks:

  1. example.org/projects/ (default post type archive page, archive-projects.php)
  2. example.org/projects/map/

How do I do handle instructing WordPress to use a specific template when accessing that one specific URL that would otherwise imply a specific Projects post? It would be nice if that redirect managed to keep the default post type archive query, but I can write a secondary loop in the template if necessary. I had initially thought endpoints were the answer, but I’m pretty sure that’s not true.


Original Post

I have a post type—”Projects”—for which I’d like two views of its post type archive. The first is a pretty-straight forward list of Projects sorted by category and the second is a mapped view of the projects. Making templates for those two views is not a problem.

How can I make these two different views of the Post Type Archive, each with a unique URL?

Ideally I’d like the first template to be the default at /projects/ and the second view at /projects/map/.

It seemed like a custom endpoint was the way to go, but this comment on an old question with a title that perfectly describes what I’m looking for suggests that there is no way to have an end point for only a custom post type archive.

Should I use a custom rewrite, then, or should I even consider going with a more jQuery driven solution?

Related posts

2 comments

  1. Updated approach

    The first thought/suggestion I made actually – like you said – doesn’t work as I understood it. At least I tried it and couldn’t figure it out. That said, what you want is still achievable, but not by using a endpoint or at least not by making use of add_rewrite_endpoint().

    However I figured it should be possible via a “ordinary” rewrite setup. Below code shows how to setup a post type, a query variable and a rewrite rule, to make a custom view for the post type archive possible. Last but not least we need to make sure to load the correct template. This it is possible to kind of emulate an endpoint. The exemplary code should get you in the right direction, so you can adapt it to your needs.

    Code:

    // minimal setup to register the new post type
    add_action( 'init', 'wpse130664_pt_rw_ep_cpt');
    function wpse130664_pt_rw_ep_cpt() { 
        register_post_type( 'rw_ep_cpt', 
            array(
                'label' => __( 'rw_ep_cpt', 'rw_ep_cpt_textdomain' ),
                'public' => true,
                // the archive slug defaults to the post type name »rw_ep_cpt«
                'has_archive' => true
            )
        );  
    } 
    
    // add a new query variable
    add_filter('init', 'wpse130664_pt_archive_qv');
    function wpse130664_pt_archive_qv() {
        global $wp;
        $wp->add_query_var('aview');
    }
    
    // add a new rewrite rule
    add_filter('init', 'wpse130664_pt_archive_rw_rule');
    function wpse130664_pt_archive_rw_rule() {
        // we're adding »aview« as a possibility to the slug 
        // of the custom post type archive we registered above
        // no need to match anything, e.g. with »$matches[1]«
        // so we're defaulting the aview query variable to 1
        // this kind of emulates a endpoint
        add_rewrite_rule( "rw_ep_cpt/aview$", "index.php?post_type=rw_ep_cpt" . '&aview=1', 'top' );
    }
    
    // load the »aview« template
    add_filter( 'template_include', 'wpse130664_aview_tmpl_red' );
    function wpse130664_aview_tmpl_red( $original_template ) {
        // we're loading the template conditionally, 
        // but only if we're actually at the »aview« "endpoint"
        if ( 1 == intval(get_query_var('aview')) ) {
            // you've to create the template you want to use here
            return get_template_directory().'/archive-aview.php';
        } else {
            return $original_template;
        }
    }
    

    Answer to comment

    In the mean time, I’m curious why you chose to use the methods on the $wp global rather than the functions get_query_var() and add_query_var()?

    To be honest I reused some code I’ve written some time ago, I only adapted it a bit to make it fit to your needs. There I used the above approach, with the $wp global. But that of course is no explanation for the why, so I try to address that too.

    If I’m not totally absolutely mistaken add_query_var() is only available as function/method of the WP class. So the above way is just the way to do it.

    Regarding get_query_var(), this one you could use instead, but it globalizes $wp_query and uses the get() function/method of the WP_Query class. So in a way the difference isn’t or shouldn’t be a big one.

    Besides that, one more thought, the WP_Query object is likely to be bigger than the WP object, if not always. So doing it via global $wp might actually be a good thing.
    But actually I didn’t thought and tested this trough, I’ve used it like this and it is working in my setup, so I was satisfied when I did that code. So there might be a drawback in another setup/use case. Or doing it another way might be more valuable/beneficial in other cases. But like I said above, this is to get you started, you have to adapt it to your needs yourself.

    Update after comment

    A well-timed blog post from @MarkJaquith turned me on to the template_include filter which is better-suited for including a new template file. I’ve updated my code to use that filter and I’d recommend updating the example code too.

    As @mrwweb pointed out and above linked article explains it, the template_redirect filter isn’t optimal for choosing a different template. Or as @MarkJaquith simply concludes:

    → template_redirect is for redirects.
    → template_include is for includes.

    He convinced me, so I changed the way the »aview« template loads to the template_include filter in above code.


    First thought/approach


    Disclaimer:
    I’m keeping this, because of it’s informational value, but this won’t work for the custom post type archive. However using ep_mask comes in handy for creating endpoints for the single custom post type posts.


    From what I understand, endpoints can be done in such cases. I recently read into that, but never actually had to approach doing it. So this is to the best of my knowledge, maybe someone with a lot more of it will come by and clear things up.

    You’re right with what you said:

    My understanding is that endpoints do not work for post type archives. They’re intended for posts within that post type but not the archive itself.

    So making use of the ep_mask parameter like described below can be used for the posts of a custom post type, but not for the archive. Beside that, playing with this made me realize how handy a custom ep_mask can be for the single custom post type endpoint creation.

    Basically what I read pinnacled into making use of the ep_mask argument you’ve available when using

    and

    rewrite argument.

    Take a look at the related trac ticket 19275. You can find additional possibly helpful information at the register_post_type() codex page at the permalink_epmask description, especially trac ticket 12605.

    As for an overview about endpoints:

    Here is an answer on:

    for categories there already is EP_CATEGORIES of course, but it shows that it really should be possible, if you make use of the ep_mask argument.

  2. Update 03/12/17

    The plugin mentioned below is abandoned, what I currently maintain is another package, Cortex


    Consider my answer just as an alternative.

    I developed a plugin, Clever Rules, that allows to specify a route, a query and (among other things) a specific template.

    What you need using Clever Rules is register a rule, but please note that with current version you can register rules only in plugins, not in themes.

    Download Clever Rules, activate it. Now you need a plugin to register your rules: you can create a new plugin, or insert the following code in a plugin you are developing…

    add_action('plugins_loaded', 'register_my_rules');
     
    function register_my_rules() {
     
        // next line ensure function runs only if Clever Rules is installed
        if ( ! function_exists('register_clever_rule') ) return;
     
        $args = array( 'route' => '/projects/map/', 'query' => 'post_type=projects' );
    
        register_clever_rule( $args )->template('your-specific-template.php');
     
    }
    

    This 3 lines of code is the only thing you need. If you created a new plugin for that, activate it, of course.

    No one of the default urls will be affected.

    ‘your-specific-template.php’ can be a template file in parent or even child theme.
    Of course you can customize the query just like you want and use other plugin features.

    And if you like the system, you can use the same plugi to register all the rules you need.

    Notes

    I am developing a new version of that plugin, that will be a bit more performant (when there are a lot of rules, only one is not a problem), allow rules registration in themes, has a little different API and some additional features. It will be public released soon, however, the current version works too.

Comments are closed.